Skip to content

Commit a9b78ef

Browse files
committed
android: implement setting hearing test results
1 parent 942ff82 commit a9b78ef

File tree

14 files changed

+670
-260
lines changed

14 files changed

+670
-260
lines changed

android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import android.content.Context
2727
import android.content.Context.MODE_PRIVATE
2828
import android.content.Intent
2929
import android.content.ServiceConnection
30-
import android.content.SharedPreferences
3130
import android.net.Uri
3231
import android.os.Build
3332
import android.os.Bundle
@@ -74,11 +73,9 @@ import androidx.compose.material3.Card
7473
import androidx.compose.material3.CardDefaults
7574
import androidx.compose.material3.ExperimentalMaterial3Api
7675
import androidx.compose.material3.Icon
77-
import androidx.compose.material3.IconButton
7876
import androidx.compose.material3.MaterialTheme
7977
import androidx.compose.material3.Text
8078
import androidx.compose.runtime.Composable
81-
import androidx.compose.runtime.derivedStateOf
8279
import androidx.compose.runtime.LaunchedEffect
8380
import androidx.compose.runtime.getValue
8481
import androidx.compose.runtime.mutableStateOf
@@ -94,6 +91,8 @@ import androidx.compose.ui.graphics.drawscope.rotate
9491
import androidx.compose.ui.graphics.toArgb
9592
import androidx.compose.ui.graphics.vector.ImageVector
9693
import androidx.compose.ui.platform.LocalContext
94+
import androidx.compose.ui.platform.LocalWindowInfo
95+
import androidx.compose.ui.res.stringResource
9796
import androidx.compose.ui.res.vectorResource
9897
import androidx.compose.ui.text.TextStyle
9998
import androidx.compose.ui.text.font.Font
@@ -111,11 +110,11 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
111110
import com.google.accompanist.permissions.MultiplePermissionsState
112111
import com.google.accompanist.permissions.isGranted
113112
import com.google.accompanist.permissions.rememberMultiplePermissionsState
114-
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
115113
import com.kyant.backdrop.backdrops.layerBackdrop
114+
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
116115
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
117-
import me.kavishdevar.librepods.constants.AirPodsNotifications
118116
import me.kavishdevar.librepods.composables.StyledIconButton
117+
import me.kavishdevar.librepods.constants.AirPodsNotifications
119118
import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen
120119
import me.kavishdevar.librepods.screens.AdaptiveStrengthScreen
121120
import me.kavishdevar.librepods.screens.AirPodsSettingsScreen
@@ -131,9 +130,9 @@ import me.kavishdevar.librepods.screens.OpenSourceLicensesScreen
131130
import me.kavishdevar.librepods.screens.RenameScreen
132131
import me.kavishdevar.librepods.screens.TransparencySettingsScreen
133132
import me.kavishdevar.librepods.screens.TroubleshootingScreen
133+
import me.kavishdevar.librepods.screens.UpdateHearingTestScreen
134134
import me.kavishdevar.librepods.services.AirPodsService
135135
import me.kavishdevar.librepods.ui.theme.LibrePodsTheme
136-
import me.kavishdevar.librepods.utils.CrossDevice
137136
import me.kavishdevar.librepods.utils.RadareOffsetFinder
138137
import kotlin.io.encoding.Base64
139138
import kotlin.io.encoding.ExperimentalEncodingApi
@@ -310,7 +309,7 @@ fun Main() {
310309
val context = LocalContext.current
311310

312311
val navController = rememberNavController()
313-
312+
314313
Box (
315314
modifier = Modifier
316315
.fillMaxSize()
@@ -406,6 +405,9 @@ fun Main() {
406405
composable("open_source_licenses") {
407406
OpenSourceLicensesScreen(navController)
408407
}
408+
composable("update_hearing_test") {
409+
UpdateHearingTestScreen(navController)
410+
}
409411
}
410412
}
411413

@@ -424,7 +426,10 @@ fun Main() {
424426
exit = fadeOut(animationSpec = tween()) + scaleOut(targetScale = 0.5f, animationSpec = tween(100)),
425427
modifier = Modifier
426428
.align(Alignment.TopStart)
427-
.padding(start = 8.dp, top = (context.resources.configuration.screenHeightDp.dp * 0.05f).value.dp)
429+
.padding(
430+
start = 8.dp,
431+
top = (LocalWindowInfo.current.containerSize.width * 0.05f).dp
432+
)
428433
) {
429434
StyledIconButton(
430435
onClick = { navController.popBackStack() },
@@ -556,7 +561,7 @@ fun PermissionsScreen(
556561
Spacer(modifier = Modifier.height(8.dp))
557562

558563
Text(
559-
text = "The following permissions are required to use the app. Please grant them to continue.",
564+
text = stringResource(R.string.permissions_required),
560565
style = TextStyle(
561566
fontSize = 16.sp,
562567
fontWeight = FontWeight.Normal,
@@ -733,7 +738,11 @@ fun PermissionCard(
733738
modifier = Modifier
734739
.size(40.dp)
735740
.clip(RoundedCornerShape(8.dp))
736-
.background(if (isGranted) accentColor.copy(alpha = 0.15f) else Color.Gray.copy(alpha = 0.15f)),
741+
.background(
742+
if (isGranted) accentColor.copy(alpha = 0.15f) else Color.Gray.copy(
743+
alpha = 0.15f
744+
)
745+
),
737746
contentAlignment = Alignment.Center
738747
) {
739748
Icon(

android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import androidx.compose.ui.tooling.preview.Preview
5959
import androidx.compose.ui.unit.dp
6060
import androidx.compose.ui.unit.sp
6161
import androidx.core.content.edit
62-
import kotlinx.coroutines.delay
6362
import me.kavishdevar.librepods.R
6463
import me.kavishdevar.librepods.services.ServiceManager
6564
import me.kavishdevar.librepods.utils.AACPManager
@@ -473,30 +472,12 @@ fun StyledToggle(
473472
val attManager = ServiceManager.getService()?.attManager ?: return
474473
val isDarkTheme = isSystemInDarkTheme()
475474
val textColor = if (isDarkTheme) Color.White else Color.Black
476-
var checked by remember { mutableStateOf(false) }
475+
val checkedValue = attManager.read(attHandle).getOrNull(0)?.toInt()
476+
var checked by remember { mutableStateOf(checkedValue !=0) }
477477
var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) }
478478
val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500))
479479

480-
LaunchedEffect(Unit) {
481-
attManager.enableNotifications(attHandle)
482-
483-
var parsed = false
484-
for (attempt in 1..3) {
485-
try {
486-
val data = attManager.read(attHandle)
487-
checked = data[0].toInt() != 0
488-
Log.d("StyledToggle", "Read attempt $attempt for $label: enabled=$checked")
489-
parsed = true
490-
break
491-
} catch (e: Exception) {
492-
Log.w("StyledToggle", "Read attempt $attempt for $label failed: ${e.message}")
493-
}
494-
delay(200)
495-
}
496-
if (!parsed) {
497-
Log.d("StyledToggle", "Failed to read state for $label after 3 attempts")
498-
}
499-
}
480+
attManager.enableNotifications(attHandle)
500481

501482
if (sharedPreferenceKey != null && sharedPreferences != null) {
502483
checked = sharedPreferences.getBoolean(sharedPreferenceKey, checked)

android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt

Lines changed: 53 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import me.kavishdevar.librepods.constants.AirPodsNotifications
9191
import me.kavishdevar.librepods.services.AirPodsService
9292
import me.kavishdevar.librepods.ui.theme.LibrePodsTheme
9393
import me.kavishdevar.librepods.utils.AACPManager
94+
import me.kavishdevar.librepods.utils.RadareOffsetFinder
9495
import kotlin.io.encoding.ExperimentalEncodingApi
9596

9697
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@@ -196,8 +197,19 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
196197
}
197198
}
198199
}
200+
201+
LaunchedEffect(service) {
202+
service.let {
203+
it.sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply {
204+
putParcelableArrayListExtra("data", ArrayList(it.getBattery()))
205+
})
206+
it.sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply {
207+
putExtra("data", it.getANC())
208+
})
209+
}
210+
}
211+
199212
val darkMode = isSystemInDarkTheme()
200-
val backdrop = rememberLayerBackdrop()
201213
StyledScaffold(
202214
title = deviceName.text,
203215
actionButtons = listOf(
@@ -218,26 +230,14 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
218230
.fillMaxSize()
219231
.hazeSource(hazeState)
220232
.padding(horizontal = 16.dp)
221-
.layerBackdrop(backdrop)
222233
) {
223-
item { Spacer(modifier = Modifier.height(spacerHeight)) }
224-
item {
225-
LaunchedEffect(service) {
226-
service.let {
227-
it.sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply {
228-
putParcelableArrayListExtra("data", ArrayList(it.getBattery()))
229-
})
230-
it.sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply {
231-
putExtra("data", it.getANC())
232-
})
233-
}
234-
}
235-
234+
item(key = "spacer_top") { Spacer(modifier = Modifier.height(spacerHeight)) }
235+
item(key = "battery") {
236236
BatteryView(service = service)
237237
}
238-
item { Spacer(modifier = Modifier.height(32.dp)) }
238+
item(key = "spacer_battery") { Spacer(modifier = Modifier.height(32.dp)) }
239239

240-
item {
240+
item(key = "name") {
241241
NavigationButton(
242242
to = "rename",
243243
name = stringResource(R.string.name),
@@ -246,49 +246,55 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
246246
independent = true
247247
)
248248
}
249+
val actAsAppleDeviceHookEnabled = RadareOffsetFinder.isSdpOffsetAvailable()
250+
if (actAsAppleDeviceHookEnabled) {
251+
item(key = "spacer_hearing_aid") { Spacer(modifier = Modifier.height(32.dp)) }
252+
item(key = "hearing_aid") {
253+
NavigationButton(
254+
to = "hearing_aid",
255+
name = stringResource(R.string.hearing_aid),
256+
navController = navController
257+
)
258+
}
259+
}
249260

250-
item { Spacer(modifier = Modifier.height(32.dp)) }
251-
item { NavigationButton(to = "hearing_aid", name = stringResource(R.string.hearing_aid), navController = navController) }
252-
253-
item { Spacer(modifier = Modifier.height(16.dp)) }
254-
item { NoiseControlSettings(service = service) }
261+
item(key = "spacer_noise") { Spacer(modifier = Modifier.height(16.dp)) }
262+
item(key = "noise_control") { NoiseControlSettings(service = service) }
255263

256-
item { Spacer(modifier = Modifier.height(16.dp)) }
257-
item { PressAndHoldSettings(navController = navController) }
264+
item(key = "spacer_press_hold") { Spacer(modifier = Modifier.height(16.dp)) }
265+
item(key = "press_hold") { PressAndHoldSettings(navController = navController) }
258266

259-
item { Spacer(modifier = Modifier.height(16.dp)) }
260-
item { CallControlSettings(hazeState = hazeState) }
267+
item(key = "spacer_call") { Spacer(modifier = Modifier.height(16.dp)) }
268+
item(key = "call_control") { CallControlSettings(hazeState = hazeState) }
261269

262-
item { Spacer(modifier = Modifier.height(16.dp)) }
263-
item { NavigationButton(to = "camera_control", name = stringResource(R.string.camera_remote), description = stringResource(R.string.camera_control_description), title = stringResource(R.string.camera_control), navController = navController) }
270+
item(key = "spacer_camera") { Spacer(modifier = Modifier.height(16.dp)) }
271+
item(key = "camera_control") { NavigationButton(to = "camera_control", name = stringResource(R.string.camera_remote), description = stringResource(R.string.camera_control_description), title = stringResource(R.string.camera_control), navController = navController) }
264272

265-
item { Spacer(modifier = Modifier.height(16.dp)) }
266-
item { AudioSettings(navController = navController) }
273+
item(key = "spacer_audio") { Spacer(modifier = Modifier.height(16.dp)) }
274+
item(key = "audio") { AudioSettings(navController = navController) }
267275

268-
item { Spacer(modifier = Modifier.height(16.dp)) }
269-
item { ConnectionSettings() }
276+
item(key = "spacer_connection") { Spacer(modifier = Modifier.height(16.dp)) }
277+
item(key = "connection") { ConnectionSettings() }
270278

271-
item { Spacer(modifier = Modifier.height(16.dp)) }
272-
item { MicrophoneSettings(hazeState) }
279+
item(key = "spacer_microphone") { Spacer(modifier = Modifier.height(16.dp)) }
280+
item(key = "microphone") { MicrophoneSettings(hazeState) }
273281

274-
item { Spacer(modifier = Modifier.height(16.dp)) }
275-
item {
282+
item(key = "spacer_sleep") { Spacer(modifier = Modifier.height(16.dp)) }
283+
item(key = "sleep_detection") {
276284
StyledToggle(
277285
label = stringResource(R.string.sleep_detection),
278286
controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG
279287
)
280288
}
281289

282-
item { Spacer(modifier = Modifier.height(16.dp)) }
283-
item {
284-
NavigationButton(to = "head_tracking", name = stringResource(R.string.head_gestures), navController = navController, currentState = if (sharedPreferences.getBoolean("head_gestures", false)) stringResource(R.string.on) else stringResource(R.string.off))
285-
}
290+
item(key = "spacer_head_tracking") { Spacer(modifier = Modifier.height(16.dp)) }
291+
item(key = "head_tracking") { NavigationButton(to = "head_tracking", name = stringResource(R.string.head_gestures), navController = navController, currentState = if (sharedPreferences.getBoolean("head_gestures", false)) stringResource(R.string.on) else stringResource(R.string.off)) }
286292

287-
item { Spacer(modifier = Modifier.height(16.dp)) }
288-
item { NavigationButton(to = "accessibility", name = stringResource(R.string.accessibility), navController = navController) }
293+
item(key = "spacer_accessibility") { Spacer(modifier = Modifier.height(16.dp)) }
294+
item(key = "accessibility") { NavigationButton(to = "accessibility", name = stringResource(R.string.accessibility), navController = navController) }
289295

290-
item { Spacer(modifier = Modifier.height(16.dp)) }
291-
item {
296+
item(key = "spacer_off_listening") { Spacer(modifier = Modifier.height(16.dp)) }
297+
item(key = "off_listening") {
292298
StyledToggle(
293299
label = stringResource(R.string.off_listening_mode),
294300
controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION,
@@ -298,9 +304,9 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
298304

299305
// an about card- everything but the version number is unknown - will add later if i find out
300306

301-
item { Spacer(modifier = Modifier.height(16.dp)) }
302-
item { NavigationButton("debug", "Debug", navController) }
303-
item { Spacer(Modifier.height(24.dp)) }
307+
item(key = "spacer_debug") { Spacer(modifier = Modifier.height(16.dp)) }
308+
item(key = "debug") { NavigationButton("debug", "Debug", navController) }
309+
item(key = "spacer_bottom") { Spacer(Modifier.height(24.dp)) }
304310
}
305311
}
306312
else {

android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
8080
import kotlinx.coroutines.launch
8181
import me.kavishdevar.librepods.R
8282
import me.kavishdevar.librepods.composables.NavigationButton
83-
import me.kavishdevar.librepods.composables.StyledIconButton
8483
import me.kavishdevar.librepods.composables.StyledScaffold
8584
import me.kavishdevar.librepods.composables.StyledSlider
8685
import me.kavishdevar.librepods.composables.StyledToggle
@@ -292,7 +291,7 @@ fun AppSettingsScreen(navController: NavController) {
292291
)
293292

294293
Spacer(modifier = Modifier.height(16.dp))
295-
294+
296295
NavigationButton(
297296
to = "",
298297
title = stringResource(R.string.camera_control),

android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,10 @@ fun HeadTrackingScreen(navController: NavController) {
221221
}
222222
}
223223
}
224+
val gestureTextValue = stringResource(R.string.shake_your_head_or_nod)
224225
StyledButton(
225226
onClick = {
226-
gestureText = "Shake your head or nod!"
227+
gestureText = gestureTextValue
227228
coroutineScope.launch {
228229
val accepted = ServiceManager.getService()?.testHeadGestures() ?: false
229230
gestureText = if (accepted) "\"Yes\" gesture detected." else "\"No\" gesture detected."

0 commit comments

Comments
 (0)