diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt index e79ca7c..4803ed3 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt @@ -21,6 +21,7 @@ package me.kavishdevar.librepods.composables import android.content.res.Configuration +import android.util.Log import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme @@ -32,6 +33,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -39,6 +41,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -74,76 +77,84 @@ fun BatteryView( val singleDisplayed = remember { mutableStateOf(false) } - Row { - Column( - modifier = Modifier.fillMaxWidth(0.5f), - horizontalAlignment = Alignment.CenterHorizontally + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Row( + modifier = Modifier.widthIn(max = 500.dp), + horizontalArrangement = Arrangement.Center ) { - Image( - bitmap = ImageBitmap.imageResource(budsRes), - contentDescription = stringResource(R.string.buds), - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) - - if ( - leftCharging == rightCharging && - (leftLevel - rightLevel) in -3..3 + Column( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally ) { - BatteryIndicator( - leftLevel.coerceAtMost(rightLevel), - leftCharging + Image( + bitmap = ImageBitmap.imageResource(budsRes), + contentDescription = stringResource(R.string.buds), + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) ) - singleDisplayed.value = true - } else { - singleDisplayed.value = false - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center + if ( + leftCharging == rightCharging && + (leftLevel - rightLevel) in -3..3 ) { - if (leftLevel > 0 || left?.status != BatteryStatus.DISCONNECTED) { - BatteryIndicator( - leftLevel, - leftCharging, - "\uDBC6\uDCE5" - ) - } + BatteryIndicator( + leftLevel.coerceAtMost(rightLevel), + leftCharging + ) + singleDisplayed.value = true + } else { + singleDisplayed.value = false - if (leftLevel > 0 && rightLevel > 0) { - Spacer(modifier = Modifier.width(16.dp)) - } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + if (leftLevel > 0 || left?.status != BatteryStatus.DISCONNECTED) { + BatteryIndicator( + leftLevel, + leftCharging, + "\uDBC6\uDCE5" + ) + } - if (rightLevel > 0 || right?.status != BatteryStatus.DISCONNECTED) { - BatteryIndicator( - rightLevel, - rightCharging, - "\uDBC6\uDCE8" - ) + if (leftLevel > 0 && rightLevel > 0) { + Spacer(modifier = Modifier.width(16.dp)) + } + + if (rightLevel > 0 || right?.status != BatteryStatus.DISCONNECTED) { + BatteryIndicator( + rightLevel, + rightCharging, + "\uDBC6\uDCE8" + ) + } } } } - } - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - bitmap = ImageBitmap.imageResource(caseRes), - contentDescription = stringResource(R.string.case_alt), - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) - - if (caseLevel > 0 || case?.status != BatteryStatus.DISCONNECTED) { - BatteryIndicator( - caseLevel, - caseCharging, - prefix = if (!singleDisplayed.value) "\uDBC3\uDE6C" else "" + Column( + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + bitmap = ImageBitmap.imageResource(caseRes), + contentDescription = stringResource(R.string.case_alt), + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) ) + + if (caseLevel > 0 || case?.status != BatteryStatus.DISCONNECTED) { + BatteryIndicator( + caseLevel, + caseCharging, + prefix = if (!singleDisplayed.value) "\uDBC3\uDE6C" else "" + ) + } } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt index bca74ab..f359853 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt @@ -49,7 +49,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex -import androidx.navigation.NavController import com.kyant.backdrop.backdrops.LayerBackdrop import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop @@ -57,7 +56,6 @@ import dev.chrisbanes.haze.HazeProgressive import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeTint import dev.chrisbanes.haze.hazeEffect -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.rememberHazeState import me.kavishdevar.librepods.R @@ -66,7 +64,7 @@ fun StyledScaffold( title: String, actionButtons: List<@Composable (backdrop: LayerBackdrop) -> Unit> = emptyList(), snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, - content: @Composable (spacerValue: Dp, hazeState: HazeState) -> Unit + content: @Composable (spacerValue: Dp, hazeState: HazeState, bottomPadding: Dp) -> Unit ) { val isDarkTheme = isSystemInDarkTheme() val hazeState = rememberHazeState(blurEnabled = true) @@ -86,7 +84,7 @@ fun StyledScaffold( Box( modifier = Modifier .fillMaxSize() - .padding(start = startPadding, end = endPadding, bottom = bottomPadding) + .padding(start = startPadding, end = endPadding) ) { val backdrop = rememberLayerBackdrop() Box( @@ -126,7 +124,7 @@ fun StyledScaffold( } } - content(topPadding + 64.dp, hazeState) + content(topPadding + 64.dp, hazeState, bottomPadding + 12.dp) } } } @@ -143,7 +141,7 @@ fun StyledScaffold( title = title, actionButtons = actionButtons, snackbarHostState = snackbarHostState, - ) { _, _ -> + ) { _, _, _-> content() } } @@ -159,7 +157,7 @@ fun StyledScaffold( title = title, actionButtons = actionButtons, snackbarHostState = snackbarHostState, - ) { spacerValue, _ -> + ) { spacerValue, _, _ -> content(spacerValue) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/data/ControlCommandRepository.kt b/android/app/src/main/java/me/kavishdevar/librepods/data/ControlCommandRepository.kt index 3a0727f..163c204 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/data/ControlCommandRepository.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/data/ControlCommandRepository.kt @@ -19,6 +19,7 @@ package me.kavishdevar.librepods.data import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.AACPManager.Companion.ControlCommandIdentifiers class ControlCommandRepository( private val aacpManager: AACPManager @@ -60,4 +61,10 @@ class ControlCommandRepository( ) { aacpManager.unregisterControlCommandListener(identifier, listener) } + + fun getMap(): Map { + return aacpManager.controlCommandStatusList.associate { + it.identifier to it.value + } + } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 2bdccdf..176ed77 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -101,7 +101,7 @@ fun AccessibilitySettingsScreen(viewModel: AirPodsViewModel, navController: NavC StyledScaffold( title = stringResource(R.string.accessibility) - ) { spacerHeight, hazeState -> + ) { topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .fillMaxSize() @@ -111,7 +111,7 @@ fun AccessibilitySettingsScreen(viewModel: AirPodsViewModel, navController: NavC .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) // val phoneMediaEQ = remember { mutableStateOf(FloatArray(8) { 0.5f }) } // val phoneEQEnabled = remember { mutableStateOf(false) } @@ -508,6 +508,7 @@ fun AccessibilitySettingsScreen(viewModel: AirPodsViewModel, navController: NavC // } // } // } + Spacer(modifier = Modifier.height(bottomPadding)) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 742575f..c498313 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -24,6 +24,8 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences +import android.util.Log +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -42,12 +44,16 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -56,7 +62,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue 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 androidx.navigation.NavController @@ -66,6 +71,7 @@ import com.kyant.backdrop.highlight.Highlight import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.delay import me.kavishdevar.librepods.BuildConfig import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.AboutCard @@ -82,7 +88,6 @@ import me.kavishdevar.librepods.composables.StyledButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledToggle -import me.kavishdevar.librepods.ui.theme.LibrePodsTheme import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AirPodsPro3 @@ -131,15 +136,24 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl StyledScaffold( title = deviceName.text, actionButtons = listOf( - { scaffoldBackdrop -> - StyledIconButton( - onClick = { navController.navigate("app_settings") }, - icon = "􀍟", - backdrop = scaffoldBackdrop - ) - }), snackbarHostState = snackbarHostState - ) { spacerHeight, hazeState -> + { scaffoldBackdrop -> + StyledIconButton( + onClick = { navController.navigate("app_settings") }, + icon = "􀍟", + backdrop = scaffoldBackdrop + ) + }), snackbarHostState = snackbarHostState + ) { topPadding, hazeState, bottomPadding -> hazeStateS.value = hazeState + var blockTouches by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + viewModel.demoActivated.collect { + blockTouches = true + delay(1000) + blockTouches = false + } + } if (state.isLocallyConnected) { val capabilities = state.capabilities @@ -148,8 +162,18 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl .fillMaxSize() .hazeSource(hazeState) .padding(horizontal = 16.dp) + .then( + if (blockTouches) Modifier.pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent(PointerEventPass.Initial) + event.changes.forEach { it.consume() } + } + } + } else Modifier + ) ) { - item(key = "spacer_top") { Spacer(modifier = Modifier.height(spacerHeight)) } + item(key = "spacer_top") { Spacer(modifier = Modifier.height(topPadding)) } item(key = "battery") { BatteryView( batteryList = state.battery, @@ -341,8 +365,7 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl viewModel.setAutomaticEarDetectionEnabled(it) }, automaticConnectionEnabled = state.automaticConnectionEnabled, - onAutomaticConnectionChanged = { viewModel.setAutomaticConnectionEnabled(it) } - ) + onAutomaticConnectionChanged = { viewModel.setAutomaticConnectionEnabled(it) }) } item(key = "spacer_microphone") { Spacer(modifier = Modifier.height(16.dp)) } @@ -420,7 +443,7 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl // item(key = "spacer_debug") { Spacer(modifier = Modifier.height(16.dp)) } // item(key = "debug") { NavigationButton("debug", "Debug", navController) } - item(key = "spacer_bottom") { Spacer(Modifier.height(24.dp)) } + item(key = "spacer_bottom") { Spacer(Modifier.height(bottomPadding)) } } } else { val backdrop = rememberLayerBackdrop() @@ -440,26 +463,54 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - Text( - text = stringResource(R.string.airpods_not_connected), style = TextStyle( - fontSize = 24.sp, - fontWeight = FontWeight.Medium, - color = if (isSystemInDarkTheme()) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() - ) - Spacer(Modifier.height(24.dp)) - Text( - text = stringResource(R.string.airpods_not_connected_description), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Light, - color = if (isSystemInDarkTheme()) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + val tapCount = remember { mutableIntStateOf(0) } + val lastTapTime = remember { mutableLongStateOf(0L) } + Column( + modifier = Modifier + .fillMaxWidth() + .pointerInput(Unit) { + detectTapGestures( + onTap = { + val now = System.currentTimeMillis() + + if (now - lastTapTime.longValue > 400) { + tapCount.intValue = 0 + } + + tapCount.intValue++ + lastTapTime.longValue = now + + if (tapCount.intValue >= 5) { + tapCount.intValue = 0 + viewModel.activateDemoMode() + } + } + ) + } ) + { + Text( + text = stringResource(R.string.airpods_not_connected), style = TextStyle( + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = if (isSystemInDarkTheme()) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth() + ) + Spacer(Modifier.height(24.dp)) + Text( + text = stringResource(R.string.airpods_not_connected_description), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Light, + color = if (isSystemInDarkTheme()) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } + Spacer(Modifier.height(32.dp)) // StyledButton( // onClick = { navController.navigate("troubleshooting") }, @@ -496,17 +547,3 @@ fun AirPodsSettingsScreen(viewModel: AirPodsViewModel, navController: NavControl } } } - -@Preview -@Composable -fun AirPodsSettingsScreenPreview() { - Column( - modifier = Modifier.height(2000.dp) - ) { - LibrePodsTheme( - darkTheme = true - ) { -// AirPodsSettingsScreen(dev = null, service = AirPodsService(), navController = rememberNavController(), isConnected = true, isRemotelyConnected = false) - } - } -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index bb73a2a..a2cc5b3 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -81,7 +81,7 @@ fun AppSettingsScreen( StyledScaffold( title = stringResource(R.string.app_settings) - ) { spacerHeight, hazeState -> + ) { topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .fillMaxSize() @@ -90,7 +90,7 @@ fun AppSettingsScreen( .verticalScroll(scrollState) .padding(horizontal = 16.dp) ) { - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) val isDarkTheme = isSystemInDarkTheme() val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) @@ -189,19 +189,21 @@ fun AppSettingsScreen( enabled = uiState.isPremium ) - Spacer(modifier = Modifier.height(16.dp)) + if (!BuildConfig.PLAY_BUILD) { + Spacer(modifier = Modifier.height(16.dp)) - NavigationButton( - to = "", - title = stringResource(R.string.camera_control), - name = stringResource(R.string.set_custom_camera_package), - navController = navController, - onClick = { - if (uiState.isPremium) viewModel.setShowCameraDialog(true) - }, - independent = true, - description = stringResource(R.string.camera_control_app_description) - ) + NavigationButton( + to = "", + title = stringResource(R.string.camera_control), + name = stringResource(R.string.set_custom_camera_package), + navController = navController, + onClick = { + if (uiState.isPremium) viewModel.setShowCameraDialog(true) + }, + independent = true, + description = stringResource(R.string.camera_control_app_description) + ) + } Spacer(modifier = Modifier.height(16.dp)) if (BuildConfig.FLAVOR == "xposed") { @@ -437,6 +439,7 @@ fun AppSettingsScreen( } }) } + Spacer(modifier = Modifier.height(bottomPadding)) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt index a802847..3ca7190 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt @@ -339,7 +339,7 @@ fun DebugScreen(navController: NavController) { ) } ), - ) { spacerHeight, hazeState -> + ) { topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .fillMaxSize() @@ -348,7 +348,7 @@ fun DebugScreen(navController: NavController) { .layerBackdrop(backdrop) .padding(horizontal = 16.dp) ) { - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) LazyColumn( state = listState, modifier = Modifier diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt index f485495..5576b79 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -139,7 +140,7 @@ fun HeadTrackingScreen(viewModel: AirPodsViewModel) { ) } ), - ) { spacerHeight, hazeState -> + ) { topPadding, hazeState, _ -> var gestureText by remember { mutableStateOf("") } val coroutineScope = rememberCoroutineScope() @@ -160,7 +161,7 @@ fun HeadTrackingScreen(viewModel: AirPodsViewModel) { .padding(horizontal = 16.dp) .verticalScroll(scrollState) ) { - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) val context = LocalContext.current @@ -404,7 +405,8 @@ private fun HeadVisualization() { .aspectRatio(2f), colors = CardDefaults.cardColors( containerColor = backgroundColor - ) + ), + shape = RoundedCornerShape(28.dp) ) { Box( modifier = Modifier.fillMaxSize(), @@ -655,7 +657,8 @@ private fun AccelerationPlot() { .height(300.dp), colors = CardDefaults.cardColors( containerColor = if (darkTheme) Color(0xFF1C1C1E) else Color.White - ) + ), + shape = RoundedCornerShape(28.dp) ) { Box( modifier = Modifier diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 78c9918..5c292a1 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -102,7 +102,7 @@ fun HearingAidScreen(viewModel: AirPodsViewModel, navController: NavController) StyledScaffold( title = stringResource(R.string.hearing_aid), snackbarHostState = snackbarHostState, - ) { spacerHeight, hazeState -> + ) { topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .layerBackdrop(backdrop) @@ -113,7 +113,7 @@ fun HearingAidScreen(viewModel: AirPodsViewModel, navController: NavController) verticalArrangement = Arrangement.spacedBy(8.dp) ) { hazeStateS.value = hazeState - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) // val mediaAssistEnabled = remember { mutableStateOf(false) } // val adjustMediaEnabled = remember { mutableStateOf(false) } @@ -234,6 +234,7 @@ fun HearingAidScreen(viewModel: AirPodsViewModel, navController: NavController) // independent = false // ) // } + Spacer(modifier = Modifier.height(bottomPadding)) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt index 06f2614..fedb250 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -104,7 +104,7 @@ fun TransparencySettingsScreen(viewModel: AirPodsViewModel) { StyledScaffold( title = stringResource(R.string.customize_transparency_mode) - ){ spacerHeight, hazeState -> + ){ topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .hazeSource(hazeState) @@ -114,7 +114,7 @@ fun TransparencySettingsScreen(viewModel: AirPodsViewModel) { .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val enabled = remember { mutableStateOf(false) } @@ -441,6 +441,8 @@ fun TransparencySettingsScreen(viewModel: AirPodsViewModel) { Spacer(modifier = Modifier.height(16.dp)) } + + Spacer(modifier = Modifier.height(bottomPadding)) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt index b797ef9..9bec4bb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt @@ -216,7 +216,7 @@ fun TroubleshootingScreen(navController: NavController) { ) { StyledScaffold( title = stringResource(R.string.troubleshooting) - ){ spacerHeight, hazeState -> + ){ topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .fillMaxSize() @@ -225,7 +225,7 @@ fun TroubleshootingScreen(navController: NavController) { .verticalScroll(scrollState) .padding(horizontal = 16.dp) ) { - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) Text( text = stringResource(R.string.saved_logs), diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/UpdateHearingTestScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/UpdateHearingTestScreen.kt index 00bcbdb..e8b6898 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/UpdateHearingTestScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/UpdateHearingTestScreen.kt @@ -18,7 +18,6 @@ package me.kavishdevar.librepods.screens -import android.annotation.SuppressLint import android.util.Log import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -33,7 +32,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -58,7 +56,6 @@ import androidx.compose.ui.unit.sp import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay import me.kavishdevar.librepods.R @@ -69,7 +66,6 @@ import me.kavishdevar.librepods.utils.HearingAidSettings import me.kavishdevar.librepods.utils.parseHearingAidSettingsResponse import me.kavishdevar.librepods.utils.sendHearingAidSettings import java.io.IOException -import kotlin.io.encoding.ExperimentalEncodingApi private var debounceJob: MutableState = mutableStateOf(null) private const val TAG = "HearingAidAdjustments" @@ -92,7 +88,7 @@ fun UpdateHearingTestScreen() { val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.hearing_test) - ) { spacerHeight, hazeState -> + ) { topPadding, hazeState, bottomPadding -> Column( modifier = Modifier .hazeSource(hazeState) @@ -104,7 +100,7 @@ fun UpdateHearingTestScreen() { ) { val textColor = if (isSystemInDarkTheme()) Color.White else Color.Black - Spacer(modifier = Modifier.height(spacerHeight)) + Spacer(modifier = Modifier.height(topPadding)) Text( text = stringResource(R.string.hearing_test_value_instruction), @@ -356,6 +352,7 @@ fun UpdateHearingTestScreen() { ) } } + Spacer(modifier = Modifier.height(bottomPadding)) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/viewmodel/AirPodsViewModel.kt b/android/app/src/main/java/me/kavishdevar/librepods/viewmodel/AirPodsViewModel.kt index 611bf5b..3b5d34a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/viewmodel/AirPodsViewModel.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/viewmodel/AirPodsViewModel.kt @@ -28,6 +28,7 @@ import android.util.Log import androidx.core.content.edit import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -35,6 +36,8 @@ import kotlinx.coroutines.launch import me.kavishdevar.librepods.billing.BillingManager import me.kavishdevar.librepods.constants.AirPodsNotifications import me.kavishdevar.librepods.constants.Battery +import me.kavishdevar.librepods.constants.BatteryComponent +import me.kavishdevar.librepods.constants.BatteryStatus import me.kavishdevar.librepods.constants.StemAction import me.kavishdevar.librepods.data.ControlCommandRepository import me.kavishdevar.librepods.services.AirPodsService @@ -91,6 +94,11 @@ class AirPodsViewModel( private val _uiState = MutableStateFlow(AirPodsUiState(deviceName = sharedPreferences.getString("name", "AirPods Pro") ?: "AirPods Pro")) val uiState: StateFlow = _uiState + private var isDemoMode = false + val demoActivated = MutableSharedFlow() + + private var billingFirstCollectDone = false + private val listeners = mutableMapOf< ControlCommandIdentifiers, AACPManager.ControlCommandListener @@ -120,6 +128,8 @@ class AirPodsViewModel( loadSharedPreferences() setupControlObservers() observeBilling() + loadControlList() + if (isDemoMode) activateDemoMode() } override fun onCleared() { @@ -138,14 +148,19 @@ class AirPodsViewModel( } private fun observeBilling() { - viewModelScope.launch { + if (!isDemoMode) viewModelScope.launch { BillingManager.provider.isPremium.collect { premium -> - + if (!billingFirstCollectDone) { + billingFirstCollectDone = true + return@collect + } if (!premium) { + Log.d("AirPodsViewModel", "we are not premium") setControlCommandBoolean(ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG, false) setHeadGesturesEnabled(false) + } else { + Log.d("AirPodsViewModel", "we are premium") } - _uiState.update { it.copy(isPremium = premium) } } } @@ -154,7 +169,7 @@ class AirPodsViewModel( private fun observeBroadcasts() { broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - when (intent?.action) { + if (!isDemoMode) when (intent?.action) { AirPodsNotifications.AIRPODS_CONNECTED -> { _uiState.update { it.copy(isLocallyConnected = true) @@ -208,7 +223,7 @@ class AirPodsViewModel( identifier: ControlCommandIdentifiers, value: ByteArray ) { - controlRepo.setValue(identifier, value) + if (!isDemoMode) controlRepo.setValue(identifier, value) _uiState.update { it.copy( controlStates = it.controlStates + (identifier to value) @@ -290,6 +305,7 @@ class AirPodsViewModel( } fun refreshInitialData() { + if (isDemoMode) return service.let { service -> _uiState.update { it.copy( @@ -336,6 +352,14 @@ class AirPodsViewModel( } } + private fun loadControlList() { + _uiState.update { + it.copy( + controlStates = controlRepo.getMap() + ) + } + } + private fun loadInstance() { val instance = service.airpodsInstance ?: AirPodsInstance( name = "AirPods", @@ -414,4 +438,43 @@ class AirPodsViewModel( fun purchase(context: Context) { BillingManager.provider.purchase(context as Activity) } + + fun activateDemoMode() { + isDemoMode = true + viewModelScope.launch { + demoActivated.emit(Unit) + } + val fakeInstance = AirPodsInstance( + name = "AirPods Pro (Demo)", + model = AirPodsModels.getModelByModelNumber("A3049")!!, + actualModelNumber = "A3049", + aacpManager = service.aacpManager, + serialNumber = "DEMO123", + leftSerialNumber = "L-DEMO", + rightSerialNumber = "R-DEMO", + version1 = "1.0", + version2 = "1.0", + version3 = "1.0", + attManager = null + ) + + _uiState.update { + it.copy( + isLocallyConnected = true, + instance = fakeInstance, + capabilities = fakeInstance.model.capabilities, + + battery = listOf( + Battery(BatteryComponent.LEFT, 85, BatteryStatus.CHARGING), + Battery(BatteryComponent.RIGHT, 25, BatteryStatus.NOT_CHARGING), + Battery(BatteryComponent.CASE, 85, BatteryStatus.CHARGING), + ), + + modelName = fakeInstance.model.displayName, + actualModel = fakeInstance.actualModelNumber, + serialNumbers = listOf("DEMO", "DEMO", "DEMO"), + version3 = "Demo Firmware" + ) + } + } }