From 2c2552a57eeebe9badb10013e490fac3e88d9cf5 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 7 Jan 2025 00:35:44 +0530 Subject: [PATCH 1/4] show last battery status when a pod or case is disconnected --- .../aln/composables/BatteryView.kt | 16 +++--- .../aln/services/AirPodsService.kt | 50 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryView.kt b/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryView.kt index 70de50d..e00e628 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryView.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryView.kt @@ -127,21 +127,21 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { .fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { - if (left?.status != BatteryStatus.DISCONNECTED) { +// if (left?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator( left?.level ?: 0, left?.status == BatteryStatus.CHARGING ) - } - if (left?.status != BatteryStatus.DISCONNECTED && right?.status != BatteryStatus.DISCONNECTED) { +// } +// if (left?.status != BatteryStatus.DISCONNECTED && right?.status != BatteryStatus.DISCONNECTED) { Spacer(modifier = Modifier.width(16.dp)) - } - if (right?.status != BatteryStatus.DISCONNECTED) { +// } +// if (right?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator( right?.level ?: 0, right?.status == BatteryStatus.CHARGING ) - } +// } } } } @@ -160,9 +160,9 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { .fillMaxWidth() .scale(1.25f) ) - if (case?.status != BatteryStatus.DISCONNECTED) { +// if (case?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator(case?.level ?: 0, case?.status == BatteryStatus.CHARGING) - } +// } } } } diff --git a/android/app/src/main/java/me/kavishdevar/aln/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/aln/services/AirPodsService.kt index c2494a2..157f603 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/services/AirPodsService.kt @@ -171,31 +171,31 @@ class AirPodsService: Service() { it.setTextViewText( R.id.left_battery_widget, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { +// if (it.status != BatteryStatus.DISCONNECTED) { "${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } +// } else { +// "" +// } } ?: "" ) it.setTextViewText( R.id.right_battery_widget, batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { +// if (it.status != BatteryStatus.DISCONNECTED) { "${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } +// } else { +// "" +// } } ?: "" ) it.setTextViewText( R.id.case_battery_widget, batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { +// if (it.status != BatteryStatus.DISCONNECTED) { "${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } +// } else { +// "" +// } } ?: "" ) } @@ -205,7 +205,7 @@ class AirPodsService: Service() { fun updateNotificationContent(connected: Boolean, airpodsName: String? = null, batteryList: List? = null) { val notificationManager = getSystemService(NotificationManager::class.java) - val textColor = this.getSharedPreferences("settings", MODE_PRIVATE).getLong("textColor", 0) +// val textColor = this.getSharedPreferences("settings", MODE_PRIVATE).getLong("textColor", 0) var updatedNotification: Notification? = null if (connected) { @@ -262,23 +262,23 @@ class AirPodsService: Service() { updatedNotification = NotificationCompat.Builder(this, "background_service_status") .setSmallIcon(R.drawable.airpods) .setContentTitle("""L: ${batteryList?.find { it.component == BatteryComponent.LEFT }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { +// if (it.status != BatteryStatus.DISCONNECTED) { "${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } +// } else { +// "" +// } } ?: ""} R: ${batteryList?.find { it.component == BatteryComponent.RIGHT }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { +// if (it.status != BatteryStatus.DISCONNECTED) { "${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } +// } else { +// "" +// } } ?: ""} C: ${batteryList?.find { it.component == BatteryComponent.CASE }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { +// if (it.status != BatteryStatus.DISCONNECTED) { "${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } +// } else { +// "" +// } } ?: ""}""") .setCategory(Notification.CATEGORY_SERVICE) .setPriority(NotificationCompat.PRIORITY_LOW) From af59b705377da59d54c05d0ce7e950f3de866ab3 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 7 Jan 2025 01:07:09 +0530 Subject: [PATCH 2/4] do not show "off" option when off listening mode is disabled --- .../aln/composables/NoiseControlSettings.kt | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/aln/composables/NoiseControlSettings.kt b/android/app/src/main/java/me/kavishdevar/aln/composables/NoiseControlSettings.kt index aa76ff3..4e1689e 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/composables/NoiseControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/composables/NoiseControlSettings.kt @@ -60,6 +60,10 @@ import me.kavishdevar.aln.utils.NoiseControlMode @SuppressLint("UnspecifiedRegisterReceiverFlag") @Composable fun NoiseControlSettings(service: AirPodsService) { + val context = LocalContext.current + val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", true) + val isDarkTheme = isSystemInDarkTheme() val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFE3E3E8) val textColor = if (isDarkTheme) Color.White else Color.Black @@ -68,15 +72,18 @@ fun NoiseControlSettings(service: AirPodsService) { val noiseControlMode = remember { mutableStateOf(NoiseControlMode.OFF) } - val d1a = remember { mutableFloatStateOf(0f) } val d2a = remember { mutableFloatStateOf(0f) } val d3a = remember { mutableFloatStateOf(0f) } fun onModeSelected(mode: NoiseControlMode, received: Boolean = false) { - noiseControlMode.value = mode - if (!received) service.setANCMode(mode.ordinal+1) - when (mode) { + if (!received && !offListeningMode && mode == NoiseControlMode.OFF) { + noiseControlMode.value = NoiseControlMode.ADAPTIVE + } else { + noiseControlMode.value = mode + } + if (!received) service.setANCMode(mode.ordinal + 1) + when (noiseControlMode.value) { NoiseControlMode.NOISE_CANCELLATION -> { d1a.floatValue = 1f d2a.floatValue = 1f @@ -106,8 +113,7 @@ fun NoiseControlSettings(service: AirPodsService) { if (intent.action == AirPodsNotifications.ANC_DATA) { noiseControlMode.value = NoiseControlMode.entries.toTypedArray()[intent.getIntExtra("data", 3) - 1] onModeSelected(noiseControlMode.value, true) - } - else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { + } else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { try { context.unregisterReceiver(this) } catch (e: IllegalArgumentException) { @@ -118,16 +124,13 @@ fun NoiseControlSettings(service: AirPodsService) { } } - val context = LocalContext.current - val noiseControlIntentFilter = IntentFilter() - .apply { - addAction(AirPodsNotifications.ANC_DATA) - addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) - } + val noiseControlIntentFilter = IntentFilter().apply { + addAction(AirPodsNotifications.ANC_DATA) + addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter, Context.RECEIVER_EXPORTED) - } - else { + } else { context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter) } @@ -157,20 +160,22 @@ fun NoiseControlSettings(service: AirPodsService) { .fillMaxWidth() .background(backgroundColor, RoundedCornerShape(14.dp)) ) { - NoiseControlButton( - icon = ImageBitmap.imageResource(R.drawable.noise_cancellation), - onClick = { onModeSelected(NoiseControlMode.OFF) }, - textColor = if (noiseControlMode.value == NoiseControlMode.OFF) textColorSelected else textColor, - backgroundColor = if (noiseControlMode.value == NoiseControlMode.OFF) selectedBackground else Color.Transparent, - modifier = Modifier.weight(1f) - ) - VerticalDivider( - thickness = 1.dp, - modifier = Modifier - .padding(vertical = 10.dp) - .alpha(d1a.floatValue), - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), - ) + if (offListeningMode) { + NoiseControlButton( + icon = ImageBitmap.imageResource(R.drawable.noise_cancellation), + onClick = { onModeSelected(NoiseControlMode.OFF) }, + textColor = if (noiseControlMode.value == NoiseControlMode.OFF) textColorSelected else textColor, + backgroundColor = if (noiseControlMode.value == NoiseControlMode.OFF) selectedBackground else Color.Transparent, + modifier = Modifier.weight(1f) + ) + VerticalDivider( + thickness = 1.dp, + modifier = Modifier + .padding(vertical = 10.dp) + .alpha(d1a.floatValue), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f), + ) + } NoiseControlButton( icon = ImageBitmap.imageResource(R.drawable.transparency), onClick = { onModeSelected(NoiseControlMode.TRANSPARENCY) }, @@ -214,13 +219,15 @@ fun NoiseControlSettings(service: AirPodsService) { .padding(horizontal = 8.dp) .padding(top = 1.dp) ) { - Text( - text = "Off", - style = TextStyle(fontSize = 12.sp, color = textColor), - textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, - modifier = Modifier.weight(1f) - ) + if (offListeningMode) { + Text( + text = "Off", + style = TextStyle(fontSize = 12.sp, color = textColor), + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) + ) + } Text( text = "Transparency", style = TextStyle(fontSize = 12.sp, color = textColor), From fb4611677efe5f171a0f2ce49b0d785ded3f8bb8 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 7 Jan 2025 01:07:24 +0530 Subject: [PATCH 3/4] try to add animations --- .../aln/composables/BatteryIndicator.kt | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryIndicator.kt b/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryIndicator.kt index 89b5d38..26248fa 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryIndicator.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/composables/BatteryIndicator.kt @@ -18,6 +18,8 @@ package me.kavishdevar.aln.composables + +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -33,74 +35,74 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.kavishdevar.aln.R - @Composable fun BatteryIndicator(batteryPercentage: Int, charging: Boolean = false) { val batteryOutlineColor = Color(0xFFBFBFBF) val batteryFillColor = if (batteryPercentage > 30) Color(0xFF30D158) else Color(0xFFFC3C3C) val batteryTextColor = MaterialTheme.colorScheme.onSurface - // Battery indicator dimensions val batteryWidth = 40.dp val batteryHeight = 15.dp val batteryCornerRadius = 4.dp val tipWidth = 5.dp val tipHeight = batteryHeight * 0.375f + val animatedFillWidth by animateFloatAsState(targetValue = batteryPercentage / 100f) + val animatedScale by animateFloatAsState(targetValue = if (charging) 1.2f else 1f) + Column( horizontalAlignment = Alignment.CenterHorizontally ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(0.dp), - modifier = Modifier.padding(bottom = 4.dp) // Padding between icon and percentage text + modifier = Modifier.padding(bottom = 4.dp) ) { - // Battery Icon Box( modifier = Modifier .width(batteryWidth) .height(batteryHeight) - .border(1.dp, batteryOutlineColor, RoundedCornerShape(batteryCornerRadius)) ) { + Box ( + modifier = Modifier + .fillMaxSize() + .border(1.dp, batteryOutlineColor, RoundedCornerShape(batteryCornerRadius)) + ) Box( modifier = Modifier .fillMaxHeight() .padding(2.dp) - .width(batteryWidth * (batteryPercentage / 100f)) + .width(batteryWidth * animatedFillWidth) .background(batteryFillColor, RoundedCornerShape(2.dp)) ) if (charging) { - Box( + Text( + text = "\uDBC0\uDEE6", + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = Color.White, modifier = Modifier - .padding(0.dp) + .scale(animatedScale) .fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text( - text = "\uDBC0\uDEE6", - fontSize = 15.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = Color.White, - modifier = Modifier - .align(Alignment.Center) - .padding(0.dp) - ) - } + textAlign = TextAlign.Center + ) } } - Box( modifier = Modifier .width(tipWidth) From 5dbfe69ed473d9b9ca0a86dd7ce92a99f479c53f Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 7 Jan 2025 01:20:31 +0530 Subject: [PATCH 4/4] center align title in debugscreen --- .../src/main/java/me/kavishdevar/aln/screens/DebugScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/aln/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/aln/screens/DebugScreen.kt index a71eaa5..ba9caac 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/screens/DebugScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/screens/DebugScreen.kt @@ -49,6 +49,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -58,7 +59,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -98,7 +98,7 @@ fun DebugScreen(navController: NavController) { Scaffold( topBar = { - TopAppBar( + CenterAlignedTopAppBar( title = { Text("Debug") }, navigationIcon = { TextButton(