diff --git a/android/app/libs/backdrop-debug.aar b/android/app/libs/backdrop-debug.aar index d8d5bff..2a687c7 100644 Binary files a/android/app/libs/backdrop-debug.aar and b/android/app/libs/backdrop-debug.aar differ diff --git a/android/app/libs/backdrop-release.aar b/android/app/libs/backdrop-release.aar index 306297b..6aad2de 100644 Binary files a/android/app/libs/backdrop-release.aar and b/android/app/libs/backdrop-release.aar differ diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt index ddf746b..d0df514 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt @@ -51,22 +51,22 @@ fun AudioSettings(navController: NavController) { val textColor = if (isDarkTheme) Color.White else Color.Black Text( - text = stringResource(R.string.audio).uppercase(), + text = stringResource(R.string.audio), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Column( modifier = Modifier - .clip(RoundedCornerShape(14.dp)) + .clip(RoundedCornerShape(28.dp)) .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { @@ -76,11 +76,12 @@ fun AudioSettings(navController: NavController) { controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, independent = false ) + HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal= 12.dp) ) StyledToggle( @@ -90,10 +91,10 @@ fun AudioSettings(navController: NavController) { independent = false ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal= 12.dp) ) StyledToggle( @@ -103,10 +104,10 @@ fun AudioSettings(navController: NavController) { independent = false ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal= 12.dp) ) NavigationButton( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt index 8aba9a0..3beef1c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt @@ -27,7 +27,9 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.height import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -71,7 +73,6 @@ fun BatteryIndicator( Column( modifier = Modifier - .padding(12.dp) .background(backgroundColor), // just for haze to work horizontalAlignment = Alignment.CenterHorizontally ) { @@ -85,7 +86,7 @@ fun BatteryIndicator( color = batteryFillColor, gapSize = 0.dp, strokeCap = StrokeCap.Round, - strokeWidth = 2.dp, + strokeWidth = 4.dp, trackColor = if (isDarkTheme) Color(0xFF0E0E0F) else Color(0xFFE3E3E8) ) @@ -101,6 +102,8 @@ fun BatteryIndicator( ) } + Spacer(modifier = Modifier.height(4.dp)) + Text( text = "$prefix $batteryPercentage%", color = batteryTextColor, 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 a993c17..992d1e6 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 @@ -146,7 +146,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { contentDescription = stringResource(R.string.buds), modifier = Modifier .fillMaxWidth() - .padding(12.dp) + .padding(8.dp) ) if ( leftCharging == rightCharging && @@ -202,7 +202,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { contentDescription = stringResource(R.string.case_alt), modifier = Modifier .fillMaxWidth() - .padding(12.dp) + .padding(8.dp) ) if (caseLevel > 0 || case?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt index 3a06981..e42b849 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -32,12 +32,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -57,6 +53,8 @@ import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource 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.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -76,19 +74,19 @@ fun CallControlSettings(hazeState: HazeState) { val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Text( - text = stringResource(R.string.call_controls).uppercase(), + text = stringResource(R.string.call_controls), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { val service = ServiceManager.getService()!! @@ -169,8 +167,8 @@ fun CallControlSettings(hazeState: HazeState) { Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(50.dp), + .padding(horizontal = 16.dp) + .height(58.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -187,17 +185,17 @@ fun CallControlSettings(hazeState: HazeState) { ) } HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) ) Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(50.dp) + .padding(horizontal = 16.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -276,14 +274,21 @@ fun CallControlSettings(hazeState: HazeState) { ) { Text( text = singlePressAction, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } @@ -315,17 +320,17 @@ fun CallControlSettings(hazeState: HazeState) { } } HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) ) Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(50.dp) + .padding(horizontal = 16.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -404,14 +409,21 @@ fun CallControlSettings(hazeState: HazeState) { ) { Text( text = doublePressAction, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index 18139ec..f08f448 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -74,7 +74,6 @@ import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.colorControls import com.kyant.backdrop.effects.refraction import com.kyant.backdrop.highlight.Highlight -import com.kyant.backdrop.highlight.HighlightStyle import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.utils.inspectDragGestures @@ -143,7 +142,9 @@ half4 main(float2 coord) { .drawBackdrop( backdrop, { RoundedCornerShape(48f.dp) }, - highlight = { Highlight { HighlightStyle.Solid } }, + highlight = { + Highlight.SolidDefault + }, onDrawSurface = { drawRect(containerColor) }, effects = { colorControls( @@ -153,7 +154,7 @@ half4 main(float2 coord) { blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) refraction(24f.dp.toPx(), 48f.dp.toPx(), true) }, - layer = { + layerBlock = { val width = size.width val height = size.height @@ -273,16 +274,6 @@ half4 main(float2 coord) { horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { - // Box( - // Modifier - // .clip(RoundedCornerShape(50.dp)) - // .background(containerColor.copy(0.2f)) - // .clickable(onClick = onDismiss) - // .height(48.dp) - // .weight(1f) - // .padding(horizontal = 16.dp), - // contentAlignment = Alignment.Center - // ) { StyledButton( onClick = onDismiss, backdrop = backdrop, @@ -295,16 +286,6 @@ half4 main(float2 coord) { style = TextStyle(contentColor, 16.sp) ) } - // Box( - // Modifier - // .clip(RoundedCornerShape(50.dp)) - // .background(accentColor) - // .clickable(onClick = onConfirm) - // .height(48.dp) - // .weight(1f) - // .padding(horizontal = 16.dp), - // contentAlignment = Alignment.Center - // ) { StyledButton( onClick = onConfirm, backdrop = backdrop, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt index 90106e4..9bf8417 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.content.Context.MODE_PRIVATE import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column @@ -34,11 +35,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import kotlin.io.encoding.ExperimentalEncodingApi -import android.content.Context.MODE_PRIVATE -import me.kavishdevar.librepods.composables.StyledToggle -import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun ConnectionSettings() { @@ -48,7 +47,7 @@ fun ConnectionSettings() { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { StyledToggle( @@ -59,10 +58,10 @@ fun ConnectionSettings() { independent = false ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal= 12.dp) ) StyledToggle( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt index 4f88af0..2c1b4a0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -32,11 +32,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -55,6 +51,9 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -75,7 +74,7 @@ fun MicrophoneSettings(hazeState: HazeState) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { val service = ServiceManager.getService()!! @@ -141,8 +140,8 @@ fun MicrophoneSettings(hazeState: HazeState) { Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(55.dp) + .padding(horizontal = 16.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -214,8 +213,11 @@ fun MicrophoneSettings(hazeState: HazeState) { ) { Text( text = stringResource(R.string.microphone_mode), - fontSize = 16.sp, - color = textColor, + style = TextStyle( + fontSize = 16.sp, + color = textColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), modifier = Modifier.padding(bottom = 4.dp) ) Box( @@ -228,14 +230,21 @@ fun MicrophoneSettings(hazeState: HazeState) { ) { Text( text = selectedMode, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt deleted file mode 100644 index 399adc4..0000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package me.kavishdevar.librepods.composables - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -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.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.text.TextStyle -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 -import androidx.navigation.compose.rememberNavController - -@Composable -fun NameField( - name: String, - value: String, - navController: NavController -) { - var isFocused by remember { mutableStateOf(false) } - - val isDarkTheme = isSystemInDarkTheme() - - var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - - val textColor = if (isDarkTheme) Color.White else Color.Black - val cursorColor = if (isFocused) { - if (isDarkTheme) Color.White else Color.Black - } else { - Color.Transparent - } - - Box ( - modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - navController.navigate("rename") - } - ) - } - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(55.dp) - .background( - animatedBackgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 8.dp) - - ) { - Text( - text = name, - style = TextStyle( - fontSize = 16.sp, - color = textColor - ) - ) - BasicTextField( - value = value, - textStyle = TextStyle( - color = textColor.copy(alpha = 0.75f), - fontSize = 16.sp, - textAlign = TextAlign.End - ), - onValueChange = {}, - singleLine = true, - enabled = false, - cursorBrush = SolidColor(cursorColor), - modifier = Modifier - .fillMaxWidth() - .padding(start = 8.dp) - .onFocusChanged { focusState -> - isFocused = focusState.isFocused - }, - decorationBox = { innerTextField -> - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End - ) { - innerTextField() - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "Edit name", - tint = textColor.copy(alpha = 0.75f), - modifier = Modifier - .size(32.dp) - ) - } - } - ) - } - } -} - -@Preview -@Composable -fun StyledTextFieldPreview() { - NameField(name = "Name", value = "AirPods Pro", rememberNavController()) -} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt index 0baa894..8175654 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt @@ -1,17 +1,17 @@ /* * LibrePods - AirPods liberated from Apple’s ecosystem - * + * * Copyright (C) 2025 LibrePods contributors - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -23,75 +23,105 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowRight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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.pointerInput import androidx.compose.ui.platform.LocalContext +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.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController - +import me.kavishdevar.librepods.R @Composable -fun NavigationButton(to: String, name: String, navController: NavController, onClick: (() -> Unit)? = null, independent: Boolean = true) { +fun NavigationButton( + to: String, + name: String, + navController: NavController, onClick: (() -> Unit)? = null, + independent: Boolean = true, + description: String? = null, + currentState: String? = null +) { val isDarkTheme = isSystemInDarkTheme() var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - - Row( - modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(if (independent) 14.dp else 0.dp)) - .height(55.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - if (onClick != null) onClick() else navController.navigate(to) - } + Column { + Row( + modifier = Modifier + .background(animatedBackgroundColor, RoundedCornerShape(if (independent) 28.dp else 0.dp)) + .height(58.dp) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + if (onClick != null) onClick() else navController.navigate(to) + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = name, + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White else Color.Black, + ) + ) + Spacer(modifier = Modifier.weight(1f)) + if (currentState != null) { + Text( + text = currentState, + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.8f), + ) ) } - ) { - Text( - text = name, - modifier = Modifier.padding(16.dp), - color = if (isDarkTheme) Color.White else Color.Black - ) - Spacer(modifier = Modifier.weight(1f)) - IconButton( - onClick = { if (onClick != null) onClick() else navController.navigate(to) }, - colors = IconButtonDefaults.iconButtonColors( - containerColor = Color.Transparent, - contentColor = if (isDarkTheme) Color.White else Color.Black - ), - modifier = Modifier - .padding(start = 16.dp) - .fillMaxHeight() - ) { - @Suppress("DEPRECATION") - Icon( - imageVector = Icons.Default.KeyboardArrowRight, - contentDescription = name + Text( + text = "􀯻", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f) + ), + modifier = Modifier + .padding(start = if (currentState != null) 6.dp else 0.dp) + ) + } + if (description != null) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) ) } } @@ -101,4 +131,4 @@ fun NavigationButton(to: String, name: String, navController: NavController, onC @Composable fun NavigationButtonPreview() { NavigationButton("to", "Name", NavController(LocalContext.current)) -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt index 648e610..d031a8c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt @@ -181,10 +181,10 @@ fun NoiseControlSettings( } Text( - text = stringResource(R.string.noise_control).uppercase(), + text = stringResource(R.string.noise_control), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), ), modifier = Modifier.padding(8.dp, bottom = 2.dp) @@ -241,7 +241,7 @@ fun NoiseControlSettings( modifier = Modifier .fillMaxWidth() .height(60.dp) - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) ) { Row( modifier = Modifier.fillMaxWidth() @@ -334,7 +334,7 @@ fun NoiseControlSettings( modifier = Modifier .fillMaxSize() .padding(3.dp) - .background(selectedBackground, RoundedCornerShape(12.dp)) + .background(selectedBackground, RoundedCornerShape(26.dp)) ) } @@ -400,7 +400,6 @@ fun NoiseControlSettings( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 4.dp) .padding(top = 4.dp) ) { if (offListeningMode.value) { @@ -408,7 +407,6 @@ fun NoiseControlSettings( text = stringResource(R.string.off), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) } @@ -416,21 +414,18 @@ fun NoiseControlSettings( text = stringResource(R.string.transparency), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) Text( text = stringResource(R.string.adaptive), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) Text( text = stringResource(R.string.noise_cancellation), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt index 53af719..b4cb081 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt @@ -19,34 +19,21 @@ package me.kavishdevar.librepods.composables import android.content.Context -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween +import android.content.res.Configuration import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -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.draw.clip import androidx.compose.ui.graphics.Color -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 @@ -65,12 +52,6 @@ fun PressAndHoldSettings(navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val dividerColor = Color(0x40888888) - var leftBackgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - var rightBackgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - - val animationSpec = tween(durationMillis = 500) - val animatedLeftBackgroundColor by animateColorAsState(targetValue = leftBackgroundColor, animationSpec = animationSpec) - val animatedRightBackgroundColor by animateColorAsState(targetValue = rightBackgroundColor, animationSpec = animationSpec) val context = LocalContext.current val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) @@ -91,14 +72,14 @@ fun PressAndHoldSettings(navController: NavController) { } Text( - text = stringResource(R.string.press_and_hold_airpods).uppercase(), + text = stringResource(R.string.press_and_hold_airpods), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) Spacer(modifier = Modifier.height(1.dp)) @@ -106,126 +87,33 @@ fun PressAndHoldSettings(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF), RoundedCornerShape(14.dp)) + .background(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF), RoundedCornerShape(28.dp)) + .clip(RoundedCornerShape(28.dp)) ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(50.dp) - .background(animatedLeftBackgroundColor, RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - leftBackgroundColor = dividerColor - tryAwaitRelease() - leftBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - navController.navigate("long_press/Left") - } - ) - }, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .padding(start = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.left), - style = TextStyle( - fontSize = 16.sp, - color = textColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = leftActionText, - style = TextStyle( - fontSize = 16.sp, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - IconButton( - onClick = { - navController.navigate("long_press/Left") - } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "go", - tint = textColor - ) - } - } - } + NavigationButton( + to = "long_press/Left", + name = stringResource(R.string.left), + navController = navController, + independent = false, + currentState = leftActionText, + ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = dividerColor, modifier = Modifier - .padding(start = 16.dp) + .padding(horizontal = 16.dp) + ) + NavigationButton( + to = "long_press/Right", + name = stringResource(R.string.right), + navController = navController, + independent = false, + currentState = rightActionText, ) - Box( - modifier = Modifier - .fillMaxWidth() - .height(50.dp) - .background(animatedRightBackgroundColor, RoundedCornerShape(bottomEnd = 14.dp, bottomStart = 14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - rightBackgroundColor = dividerColor - tryAwaitRelease() - rightBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - navController.navigate("long_press/Right") - } - ) - }, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .padding(start = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.right), - style = TextStyle( - fontSize = 16.sp, - color = textColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = rightActionText, - style = TextStyle( - fontSize = 16.sp, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - IconButton( - onClick = { - navController.navigate("long_press/Right") - } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "go", - tint = textColor - ) - } - } - } } } -@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun PressAndHoldSettingsPreview() { PressAndHoldSettings(navController = NavController(LocalContext.current)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt index ce68372..41258bf 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt @@ -74,7 +74,8 @@ fun StyledButton( isInteractive: Boolean = true, tint: Color = Color.Unspecified, surfaceColor: Color = Color.Unspecified, - content: @Composable RowScope.() -> Unit + maxScale: Float = 0.1f, + content: @Composable RowScope.() -> Unit, ) { val animationScope = rememberCoroutineScope() val progressAnimation = remember { Animatable(0f) } @@ -113,7 +114,7 @@ half4 main(float2 coord) { effects = { blur(16f.dp.toPx()) }, - layer = null, + layerBlock = null, onDrawSurface = { if (tint.isSpecified) { drawRect(tint, blendMode = BlendMode.Hue) @@ -147,12 +148,11 @@ half4 main(float2 coord) { blur(2f.dp.toPx()) refraction(12f.dp.toPx(), 24f.dp.toPx()) }, - layer = { + layerBlock = { val width = size.width val height = size.height val progress = progressAnimation.value - val maxScale = 0.1f val scale = lerp(1f, 1f + maxScale, progress) val maxOffset = size.minDimension diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt index 46168b8..73cf744 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt @@ -230,7 +230,7 @@ fun StyledDropdown( if (index != options.lastIndex) { HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier.padding(start = 12.dp, end = 0.dp) ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt index 4e94193..2f59f72 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt @@ -24,6 +24,7 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.spring +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -59,8 +60,10 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastCoerceAtMost import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp +import com.kyant.backdrop.backdrops.LayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.refractionWithDispersion import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.Shadow @@ -79,6 +82,8 @@ fun StyledIconButton( icon: String, darkMode: Boolean, tint: Color = Color.Unspecified, + backdrop: LayerBackdrop = rememberLayerBackdrop(), + modifier: Modifier = Modifier, ) { val animationScope = rememberCoroutineScope() val progressAnimationSpec = spring(0.5f, 300f, 0.001f) @@ -110,26 +115,23 @@ half4 main(float2 coord) { null } } - + val isDarkTheme = isSystemInDarkTheme() TextButton( onClick = onClick, shape = RoundedCornerShape(56.dp), - modifier = Modifier + modifier = modifier .padding(horizontal = 12.dp) .drawBackdrop( - backdrop = rememberLayerBackdrop(), + backdrop = backdrop, shape = { RoundedCornerShape(56.dp) }, - highlight = { - val progress = progressAnimation.value - Highlight.AmbientDefault.copy(alpha = progress.coerceIn(0.45f, 1f)) - }, + highlight = { Highlight.AmbientDefault.copy(alpha = if (isDarkTheme) 1f else 0f) }, shadow = { Shadow( - radius = 4f.dp, - color = Color.Black.copy(0.08f) + radius = 48f.dp, + color = Color.Black.copy(if (isDarkTheme) 0.08f else 0.4f) ) }, - layer = { + layerBlock = { val width = size.width val height = size.height @@ -182,7 +184,7 @@ half4 main(float2 coord) { drawLayer(innerShadowLayer) drawRect( - Color.White.copy(progress.coerceIn(0.15f, 0.35f)) + (if (isDarkTheme) Color(0xFFAFAFAF) else Color.White).copy(progress.coerceIn(0.15f, 0.35f)) ) }, onDrawFront = { @@ -218,6 +220,7 @@ half4 main(float2 coord) { }, effects = { refractionWithDispersion(6f.dp.toPx(), size.height / 2f) + blur(24f, TileMode.Decal) }, ) .pointerInput(animationScope) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt index b0544cd..caa1644 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -31,12 +31,14 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -84,6 +86,7 @@ import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.Shadow import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import kotlin.math.abs import kotlin.math.roundToInt @Composable @@ -136,23 +139,26 @@ fun StyledSlider( val content = @Composable { Box( - Modifier.fillMaxWidth(if (startIcon == null && endIcon == null) 0.95f else 1f) - ) { + Modifier + .fillMaxWidth(if (startIcon == null && endIcon == null) 0.95f else 1f) + ) { Box( Modifier + .padding(vertical = 4.dp) .layerBackdrop(sliderBackdrop) - .fillMaxWidth()) { + .fillMaxWidth() + ) { Column( modifier = Modifier .fillMaxWidth(1f) .padding(vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) ) { if (startLabel != null || endLabel != null) { Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .padding(horizontal = 8.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Text( @@ -174,80 +180,119 @@ fun StyledSlider( ) ) } + Spacer(modifier = Modifier.height(12.dp)) } - - Row( + Column( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp) - .then(if (startIcon == null && endIcon == null) Modifier.padding(horizontal = 12.dp) else Modifier), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(0.dp) + .then(if (startIcon == null && endIcon == null) Modifier.padding(horizontal = 8.dp) else Modifier), ) { - if (startIcon != null) { - Text( - text = startIcon, - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = accentColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier - .padding(horizontal = 12.dp) - .onGloballyPositioned { - startIconWidthState.floatValue = it.size.width.toFloat() - } - ) - } - Box( - Modifier - .weight(1f) - .onSizeChanged { trackWidthState.floatValue = it.width.toFloat() } - .onGloballyPositioned { - trackPositionState.floatValue = - it.positionInParent().y + it.size.height / 2f - } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(0.dp) ) { - Box( - Modifier - .clip(RoundedCornerShape(28.dp)) - .background(trackColor) - .height(6f.dp) - .fillMaxWidth() - ) - - Box( - Modifier - .clip(RoundedCornerShape(28.dp)) - .background(accentColor) - .height(6f.dp) - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - val fraction = fraction - val width = - (fraction * constraints.maxWidth).fastRoundToInt() - layout(width, placeable.height) { - placeable.place(0, 0) + if (startIcon != null) { + Text( + text = startIcon, + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = accentColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp) + .onGloballyPositioned { + startIconWidthState.floatValue = it.size.width.toFloat() } - } - ) - } - if (endIcon != null) { - Text( - text = endIcon, - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = accentColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier - .padding(horizontal = 12.dp) + ) + } + Box( + Modifier + .weight(1f) + .onSizeChanged { trackWidthState.floatValue = it.width.toFloat() } .onGloballyPositioned { - endIconWidthState.floatValue = it.size.width.toFloat() + trackPositionState.floatValue = + it.positionInParent().y + it.size.height / 2f } - ) + ) { + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(trackColor) + .height(6f.dp) + .fillMaxWidth() + ) + + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(accentColor) + .height(6f.dp) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val fraction = fraction + val width = + (fraction * constraints.maxWidth).fastRoundToInt() + layout(width, placeable.height) { + placeable.place(0, 0) + } + } + ) + } + if (endIcon != null) { + Text( + text = endIcon, + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = accentColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp) + .onGloballyPositioned { + endIconWidthState.floatValue = it.size.width.toFloat() + } + ) + } + } + if (snapPoints.isNotEmpty() && startLabel != null && endLabel != null) Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + if (snapPoints.isNotEmpty()) { + val trackWidth = if (startIcon != null && endIcon != null) trackWidthState.floatValue - with(density) { 6.dp.toPx() } * 2 else trackWidthState.floatValue- with(density) { 22.dp.toPx() } + val startOffset = + if (startIcon != null) startIconWidthState.floatValue + with( + density + ) { 34.dp.toPx() } else with(density) { 14.dp.toPx() } + Box( + Modifier + .fillMaxWidth() + ) { + snapPoints.forEach { point -> + val pointFraction = + ((point - valueRange.start) / (valueRange.endInclusive - valueRange.start)) + .fastCoerceIn(0f, 1f) + Box( + Modifier + .graphicsLayer { + translationX = + startOffset + pointFraction * trackWidth - 4.dp.toPx() + } + .size(2.dp) + .background( + trackColor, + CircleShape + ) + ) + } + } + } } } } @@ -257,10 +302,10 @@ fun StyledSlider( Modifier .graphicsLayer { val startOffset = - if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else with(density) { 8.dp.toPx() } + if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else with(density) { 12.dp.toPx() } translationX = startOffset + fraction * trackWidthState.floatValue - size.width / 2f - translationY = if (startLabel != null || endLabel != null) trackPositionState.floatValue + with(density) { 22.dp.toPx() } + size.height / 2f else trackPositionState.floatValue + with(density) { 4.dp.toPx() } + translationY = if (startLabel != null || endLabel != null) trackPositionState.floatValue + with(density) { 26.dp.toPx() } + size.height / 2f else trackPositionState.floatValue + with(density) { 8.dp.toPx() } } .draggable( rememberDraggableState { delta -> @@ -305,7 +350,7 @@ fun StyledSlider( color = Color.Black.copy(0.05f) ) }, - layer = { + layerBlock = { val progress = progressAnimation.value val scale = lerp(1f, 1.5f, progress) scaleX = scale @@ -361,20 +406,20 @@ fun StyledSlider( text = label, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = labelTextColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp) + modifier = Modifier.padding(horizontal = 18.dp, vertical = 4.dp) ) } Box( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(horizontal = 8.dp, vertical = 0.dp) - .heightIn(min = 55.dp), + .heightIn(min = 58.dp), contentAlignment = Alignment.Center ) { content() @@ -390,7 +435,7 @@ fun StyledSlider( fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier - .padding(horizontal = 12.dp, vertical = 4.dp) + .padding(horizontal = 18.dp, vertical = 4.dp) ) } } @@ -402,8 +447,8 @@ fun StyledSlider( } private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { - val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value - return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value + val nearest = points.minByOrNull { abs(it - value) } ?: value + return if (abs(nearest - value) <= threshold) nearest else value } @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -426,9 +471,11 @@ fun StyledSliderPreview() { a.floatValue = it }, valueRange = 0f..2f, + snapPoints = listOf(0f, 0.5f, 1f, 1.5f, 2f), + snapThreshold = 0.1f, independent = true, startLabel = "A", - endLabel = "B" + endLabel = "B", ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt index 4786c0f..94ea690 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt @@ -48,8 +48,11 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.BlurEffect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.layer.CompositingStrategy @@ -68,7 +71,7 @@ import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.refractionWithDispersion import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.Shadow -import kotlinx.coroutines.delay +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @Composable @@ -76,16 +79,17 @@ fun StyledSwitch( checked: Boolean, onCheckedChange: (Boolean) -> Unit, enabled: Boolean = true, + indpendent: Boolean = true, ) { val isDarkTheme = isSystemInDarkTheme() val onColor = if (enabled) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) val offColor = if (enabled) if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) - val trackWidth = 70.dp - val trackHeight = 31.dp - val thumbHeight = 27.dp - val thumbWidth = 36.dp + val trackWidth = 64.dp + val trackHeight = 28.dp + val thumbHeight = 24.dp + val thumbWidth = 39.dp val backdrop = rememberLayerBackdrop() val switchBackdrop = rememberLayerBackdrop() @@ -97,18 +101,23 @@ fun StyledSwitch( val density = LocalDensity.current val animationScope = rememberCoroutineScope() val progressAnimationSpec = spring(0.5f, 300f, 0.001f) - val colorAnimationSpec = tween(300, easing = FastOutSlowInEasing) + val colorAnimationSpec = tween(200, easing = FastOutSlowInEasing) val progressAnimation = remember { Animatable(0f) } val innerShadowLayer = rememberGraphicsLayer().apply { compositingStrategy = CompositingStrategy.Offscreen } val animatedTrackColor = remember { Animatable(if (checked) onColor else offColor) } - LaunchedEffect(checked) { - val targetColor = if (checked) onColor else offColor - animatedTrackColor.animateTo(targetColor, colorAnimationSpec) - val targetFrac = if (checked) 1f else 0f - animatedFraction.animateTo(targetFrac, progressAnimationSpec) + coroutineScope { + launch { + val targetColor = if (checked) onColor else offColor + animatedTrackColor.animateTo(targetColor, colorAnimationSpec) + } + launch { + val targetFrac = if (checked) 1f else 0f + animatedFraction.animateTo(targetFrac, progressAnimationSpec) + } + } } Box( @@ -136,7 +145,7 @@ fun StyledSwitch( .then(if (enabled) Modifier.draggable( rememberDraggableState { delta -> if (trackWidthPx.floatValue > 0f) { - val newFraction = (animatedFraction.value + delta / trackWidthPx.floatValue).fastCoerceIn(0f, 1f) + val newFraction = (animatedFraction.value + delta / trackWidthPx.floatValue).fastCoerceIn(-0.3f, 1.3f) animationScope.launch { animatedFraction.snapTo(newFraction) } @@ -155,10 +164,12 @@ fun StyledSwitch( }, onDragStopped = { animationScope.launch { - progressAnimation.animateTo(0f, progressAnimationSpec) val snappedFraction = if (animatedFraction.value >= 0.5f) 1f else 0f - animatedFraction.animateTo(snappedFraction, progressAnimationSpec) onCheckedChange(snappedFraction >= 0.5f) + coroutineScope { + launch { progressAnimation.animateTo(0f, progressAnimationSpec) } + launch { animatedFraction.animateTo(snappedFraction, progressAnimationSpec) } + } } } ) else Modifier) @@ -175,12 +186,27 @@ fun StyledSwitch( color = Color.Black.copy(0.05f) ) }, - layer = { + layerBlock = { val progress = progressAnimation.value - val scale = lerp(1f, 2f, progress) + val scale = lerp(1f, 1.6f, progress) scaleX = scale scaleY = scale }, + onDrawBackdrop = { drawScope -> + drawIntoCanvas { canvas -> + canvas.save() + canvas.drawRect(0f, 0f, size.width, size.height, Paint().apply { + color = if (indpendent) { + if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + } else { + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + } + }) + scale(0.75f) { + drawScope() + } + } + }, onDrawSurface = { val progress = progressAnimation.value.fastCoerceIn(0f, 1f) @@ -224,12 +250,12 @@ fun StyledSwitch( @Composable fun StyledSwitchPreview() { val isDarkTheme = isSystemInDarkTheme() - val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7) Box( modifier = Modifier .background(backgroundColor) .width(100.dp) - .height(100.dp), + .height(400.dp), contentAlignment = Alignment.Center ) { val checked = remember { mutableStateOf(true) } @@ -238,13 +264,14 @@ fun StyledSwitchPreview() { onCheckedChange = { checked.value = it }, - enabled = true + enabled = true, + indpendent = false ) - LaunchedEffect(Unit) { - delay(1000) - checked.value = false - delay(1000) - checked.value = true - } +// LaunchedEffect(Unit) { +// delay(1000) +// checked.value = false +// delay(1000) +// checked.value = true +// } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt index 99567b9..920fe7f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt @@ -75,6 +75,7 @@ fun StyledToggle( sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, independent: Boolean = true, + enabled: Boolean = true, onCheckedChange: ((Boolean) -> Unit)? = null, ) { val isDarkTheme = isSystemInDarkTheme() @@ -82,7 +83,9 @@ fun StyledToggle( var checked by checkedState var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - + if (sharedPreferenceKey != null && sharedPreferences != null) { + checked = sharedPreferences.getBoolean(sharedPreferenceKey, checked) + } fun cb() { if (sharedPreferences != null) { if (sharedPreferenceKey == null) { @@ -101,15 +104,16 @@ fun StyledToggle( text = title, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 4.dp) + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 4.dp) ) } Box( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(28.dp)) + .padding(4.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -120,8 +124,10 @@ fun StyledToggle( if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } } ) } @@ -145,10 +151,14 @@ fun StyledToggle( ) StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = true ) } } @@ -156,7 +166,7 @@ fun StyledToggle( Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) ) { Text( @@ -177,10 +187,10 @@ fun StyledToggle( modifier = Modifier .fillMaxWidth() .background( - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(28.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent ) - .padding(horizontal = 12.dp, vertical = 12.dp) + .padding(16.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -194,8 +204,10 @@ fun StyledToggle( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -206,25 +218,35 @@ fun StyledToggle( ) { Text( text = label, - fontSize = 16.sp, - color = textColor + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) ) Spacer(modifier = Modifier.height(4.dp)) if (description != null) { Text( text = description, - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, + style = TextStyle( + fontSize = 12.sp, + color = textColor.copy(0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)), + ) ) } } StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = false ) } } @@ -237,6 +259,7 @@ fun StyledToggle( description: String? = null, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers, independent: Boolean = true, + enabled: Boolean = true, sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, onCheckedChange: ((Boolean) -> Unit)? = null, @@ -291,15 +314,16 @@ fun StyledToggle( text = title, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 4.dp) + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 4.dp) ) } Box( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(28.dp)) + .padding(4.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -310,8 +334,10 @@ fun StyledToggle( if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } } ) } @@ -335,10 +361,14 @@ fun StyledToggle( ) StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = true ) } } @@ -346,7 +376,7 @@ fun StyledToggle( Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) ) { Text( @@ -367,10 +397,10 @@ fun StyledToggle( modifier = Modifier .fillMaxWidth() .background( - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(28.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent ) - .padding(horizontal = 12.dp, vertical = 12.dp) + .padding(16.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -384,8 +414,10 @@ fun StyledToggle( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -396,25 +428,35 @@ fun StyledToggle( ) { Text( text = label, - fontSize = 16.sp, - color = textColor + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) ) Spacer(modifier = Modifier.height(4.dp)) if (description != null) { Text( text = description, - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, + style = TextStyle( + fontSize = 12.sp, + color = textColor.copy(0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)), + ) ) } } StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = false ) } } @@ -427,6 +469,7 @@ fun StyledToggle( description: String? = null, attHandle: ATTHandles, independent: Boolean = true, + enabled: Boolean = true, sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, onCheckedChange: ((Boolean) -> Unit)? = null, @@ -509,15 +552,16 @@ fun StyledToggle( text = title, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 4.dp) + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 4.dp) ) } Box( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(28.dp)) + .padding(4.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -528,8 +572,10 @@ fun StyledToggle( if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } } ) } @@ -553,10 +599,14 @@ fun StyledToggle( ) StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = true ) } } @@ -564,7 +614,7 @@ fun StyledToggle( Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) ) { Text( @@ -585,10 +635,10 @@ fun StyledToggle( modifier = Modifier .fillMaxWidth() .background( - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(28.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent ) - .padding(horizontal = 12.dp, vertical = 12.dp) + .padding(16.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -602,8 +652,10 @@ fun StyledToggle( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -629,10 +681,14 @@ fun StyledToggle( } StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = false ) } } 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 1e8a1c0..5e3be79 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 @@ -40,20 +40,16 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableLongStateOf @@ -79,6 +75,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi @@ -149,13 +147,16 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } + val backdrop = rememberLayerBackdrop() + StyledScaffold( title = stringResource(R.string.accessibility), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, ) { spacerHeight, hazeState -> @@ -163,6 +164,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { modifier = Modifier .fillMaxSize() .hazeSource(hazeState) + .layerBackdrop(backdrop) .verticalScroll(rememberScrollState()) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -370,7 +372,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { ) StyledToggle( - title = stringResource(R.string.noise_control).uppercase(), + title = stringResource(R.string.noise_control), label = stringResource(R.string.noise_cancellation_single_airpod), description = stringResource(R.string.noise_cancellation_single_airpod_description), controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, @@ -392,7 +394,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { } StyledSlider( - label = stringResource(R.string.tone_volume).uppercase(), + label = stringResource(R.string.tone_volume), description = stringResource(R.string.tone_volume_description), mutableFloatState = toneVolumeValue, onValueChange = { @@ -405,6 +407,12 @@ fun AccessibilitySettingsScreen(navController: NavController) { independent = true ) + StyledToggle( + label = stringResource(R.string.volume_control), + description = stringResource(R.string.volume_control_description), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE, + ) + DropdownMenuComponent( label = stringResource(R.string.volume_swipe_speed), description = stringResource(R.string.volume_swipe_speed_description), @@ -425,10 +433,10 @@ fun AccessibilitySettingsScreen(navController: NavController) { if (!hearingAidEnabled.value&& isSdpOffsetAvailable.value) { Text( - text = stringResource(R.string.apply_eq_to).uppercase(), + text = stringResource(R.string.apply_eq_to), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), @@ -437,12 +445,12 @@ fun AccessibilitySettingsScreen(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(vertical = 0.dp) ) { val darkModeLocal = isSystemInDarkTheme() - val phoneShape = RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) + val phoneShape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) var phoneBackgroundColor by remember { mutableStateOf( if (darkModeLocal) Color( @@ -500,11 +508,11 @@ fun AccessibilitySettingsScreen(navController: NavController) { } HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888) ) - val mediaShape = RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + val mediaShape = RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp) var mediaBackgroundColor by remember { mutableStateOf( if (darkModeLocal) Color( @@ -565,7 +573,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -686,15 +694,18 @@ private fun DropdownMenuComponent( ) .background( if (independent) (if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) else Color.Transparent, - if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) + if (independent) RoundedCornerShape(28.dp) else RoundedCornerShape(0.dp) ) - .clip(if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp)) + then( + if (independent) Modifier.padding(horizontal = 4.dp) else Modifier + ) + .clip(if (independent) RoundedCornerShape(28.dp) else RoundedCornerShape(0.dp)) ){ Row( modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(55.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -766,7 +777,7 @@ private fun DropdownMenuComponent( color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(bottom = 2.dp) + modifier = Modifier.padding(16.dp, top = 0.dp, bottom = 2.dp) ) } } @@ -780,14 +791,21 @@ private fun DropdownMenuComponent( ) { Text( text = selectedOption, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } @@ -816,7 +834,7 @@ private fun DropdownMenuComponent( Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7)) ){ Text( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt index da0605a..440e068 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -94,6 +96,7 @@ fun AdaptiveStrengthScreen(navController: NavController) { } } + val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.customize_adaptive_audio), @@ -101,19 +104,21 @@ fun AdaptiveStrengthScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight -> Column( modifier = Modifier .fillMaxSize() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) StyledSlider( - label = stringResource(R.string.customize_adaptive_audio).uppercase(), + label = stringResource(R.string.customize_adaptive_audio), mutableFloatState = sliderValue, onValueChange = { sliderValue.floatValue = it 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 c8242ea..62c700b 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 @@ -68,6 +68,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.highlight.Highlight @@ -80,7 +81,6 @@ import me.kavishdevar.librepods.composables.BatteryView import me.kavishdevar.librepods.composables.CallControlSettings import me.kavishdevar.librepods.composables.ConnectionSettings import me.kavishdevar.librepods.composables.MicrophoneSettings -import me.kavishdevar.librepods.composables.NameField import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.NoiseControlSettings import me.kavishdevar.librepods.composables.PressAndHoldSettings @@ -199,13 +199,15 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } } val darkMode = isSystemInDarkTheme() + val backdrop = rememberLayerBackdrop() StyledScaffold( title = deviceName.text, actionButtons = listOf { StyledIconButton( onClick = { navController.navigate("app_settings") }, icon = "􀍟", - darkMode = darkMode + darkMode = darkMode, + backdrop = backdrop ) }, snackbarHostState = snackbarHostState @@ -216,6 +218,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, .fillMaxSize() .hazeSource(hazeState) .padding(horizontal = 16.dp) + .layerBackdrop(backdrop) .verticalScroll(rememberScrollState()) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -249,28 +252,28 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, // Only show name field when not in BLE-only mode if (!bleOnlyMode) { - NameField( + NavigationButton( + to = "rename", name = stringResource(R.string.name), - value = deviceName.text, - navController = navController + currentState = deviceName.text, + navController = navController, + independent = true ) - } - if (!bleOnlyMode) { Spacer(modifier = Modifier.height(32.dp)) NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) Spacer(modifier = Modifier.height(16.dp)) NoiseControlSettings(service = service) + Spacer(modifier = Modifier.height(16.dp)) + PressAndHoldSettings(navController = navController) + Spacer(modifier = Modifier.height(16.dp)) CallControlSettings(hazeState = hazeState) // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events - Spacer(modifier = Modifier.height(16.dp)) - PressAndHoldSettings(navController = navController) - Spacer(modifier = Modifier.height(16.dp)) AudioSettings(navController = navController) 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 353e9c9..89cf866 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 @@ -26,7 +26,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -37,7 +36,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -47,25 +45,21 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -79,13 +73,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +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.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold -import me.kavishdevar.librepods.composables.StyledSwitch +import me.kavishdevar.librepods.composables.StyledSlider +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.RadareOffsetFinder import kotlin.io.encoding.Base64 @@ -102,13 +100,13 @@ fun AppSettingsScreen(navController: NavController) { val coroutineScope = rememberCoroutineScope() val scrollState = rememberScrollState() - var showResetDialog by remember { mutableStateOf(false) } - var showIrkDialog by remember { mutableStateOf(false) } - var showEncKeyDialog by remember { mutableStateOf(false) } - var irkValue by remember { mutableStateOf("") } - var encKeyValue by remember { mutableStateOf("") } - var irkError by remember { mutableStateOf(null) } - var encKeyError by remember { mutableStateOf(null) } + val showResetDialog = remember { mutableStateOf(false) } + val showIrkDialog = remember { mutableStateOf(false) } + val showEncKeyDialog = remember { mutableStateOf(false) } + val irkValue = remember { mutableStateOf("") } + val encKeyValue = remember { mutableStateOf("") } + val irkError = remember { mutableStateOf(null) } + val encKeyError = remember { mutableStateOf(null) } LaunchedEffect(Unit) { val savedIrk = sharedPreferences.getString(AACPManager.Companion.ProximityKeyType.IRK.name, null) @@ -117,9 +115,9 @@ fun AppSettingsScreen(navController: NavController) { if (savedIrk != null) { try { val decoded = Base64.decode(savedIrk) - irkValue = decoded.joinToString("") { "%02x".format(it) } + irkValue.value = decoded.joinToString("") { "%02x".format(it) } } catch (e: Exception) { - irkValue = "" + irkValue.value = "" e.printStackTrace() } } @@ -127,59 +125,58 @@ fun AppSettingsScreen(navController: NavController) { if (savedEncKey != null) { try { val decoded = Base64.decode(savedEncKey) - encKeyValue = decoded.joinToString("") { "%02x".format(it) } + encKeyValue.value = decoded.joinToString("") { "%02x".format(it) } } catch (e: Exception) { - encKeyValue = "" + encKeyValue.value = "" e.printStackTrace() } } } - var showPhoneBatteryInWidget by remember { + val showPhoneBatteryInWidget = remember { mutableStateOf(sharedPreferences.getBoolean("show_phone_battery_in_widget", true)) } - var conversationalAwarenessPauseMusicEnabled by remember { + val conversationalAwarenessPauseMusicEnabled = remember { mutableStateOf(sharedPreferences.getBoolean("conversational_awareness_pause_music", false)) } - var relativeConversationalAwarenessVolumeEnabled by remember { + val relativeConversationalAwarenessVolumeEnabled = remember { mutableStateOf(sharedPreferences.getBoolean("relative_conversational_awareness_volume", true)) } - var openDialogForControlling by remember { + val openDialogForControlling = remember { mutableStateOf(sharedPreferences.getString("qs_click_behavior", "dialog") == "dialog") } - var disconnectWhenNotWearing by remember { + val disconnectWhenNotWearing = remember { mutableStateOf(sharedPreferences.getBoolean("disconnect_when_not_wearing", false)) } - var takeoverWhenDisconnected by remember { + val takeoverWhenDisconnected = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_disconnected", true)) } - var takeoverWhenIdle by remember { + val takeoverWhenIdle = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_idle", true)) } - var takeoverWhenMusic by remember { + val takeoverWhenMusic = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_music", false)) } - var takeoverWhenCall by remember { + val takeoverWhenCall = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_call", true)) } - var takeoverWhenRingingCall by remember { + val takeoverWhenRingingCall = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_ringing_call", true)) } - var takeoverWhenMediaStart by remember { + val takeoverWhenMediaStart = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_media_start", true)) } - var useAlternateHeadTrackingPackets by remember { + val useAlternateHeadTrackingPackets = remember { mutableStateOf(sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false)) } - var bleOnlyMode by remember { + val bleOnlyMode = remember { mutableStateOf(sharedPreferences.getBoolean("ble_only_mode", false)) } - // Ensure the default value is properly set if not exists LaunchedEffect(Unit) { if (!sharedPreferences.contains("ble_only_mode")) { sharedPreferences.edit { putBoolean("ble_only_mode", false) } @@ -191,10 +188,12 @@ fun AppSettingsScreen(navController: NavController) { return hexPattern.matches(input) } - var isProcessingSdp by remember { mutableStateOf(false) } - var actAsAppleDevice by remember { mutableStateOf(false) } + val isProcessingSdp = remember { mutableStateOf(false) } + val actAsAppleDevice = remember { mutableStateOf(false) } - BackHandler(enabled = isProcessingSdp) {} + BackHandler(enabled = isProcessingSdp.value) {} + + val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.app_settings), @@ -202,15 +201,17 @@ fun AppSettingsScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight, hazeState -> Column( modifier = Modifier .fillMaxSize() - .verticalScroll(scrollState) + .layerBackdrop(backdrop) .hazeSource(state = hazeState) + .verticalScroll(scrollState) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -219,17 +220,33 @@ fun AppSettingsScreen(navController: NavController) { val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black - Spacer(modifier = Modifier.height(8.dp)) + StyledToggle( + title = stringResource(R.string.widget), + label = stringResource(R.string.show_phone_battery_in_widget), + description = stringResource(R.string.show_phone_battery_in_widget_description), + checkedState = showPhoneBatteryInWidget, + sharedPreferenceKey = "show_phone_battery_in_widget", + sharedPreferences = sharedPreferences, + ) + + StyledToggle( + title = stringResource(R.string.connection_mode), + label = stringResource(R.string.ble_only_mode), + description = stringResource(R.string.ble_only_mode_description), + checkedState = bleOnlyMode, + sharedPreferenceKey = "ble_only_mode", + sharedPreferences = sharedPreferences, + ) Text( - text = stringResource(R.string.widget).uppercase(), + text = stringResource(R.string.conversational_awareness), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 8.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -239,770 +256,251 @@ fun AppSettingsScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) - .padding(horizontal = 16.dp, vertical = 4.dp) + .padding(vertical = 4.dp) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - showPhoneBatteryInWidget = !showPhoneBatteryInWidget - sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.show_phone_battery_in_widget), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.show_phone_battery_in_widget_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = showPhoneBatteryInWidget, - onCheckedChange = { - showPhoneBatteryInWidget = it - sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", it)} - } - ) - } - } - - Text( - text = stringResource(R.string.connection_mode).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column ( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - bleOnlyMode = !bleOnlyMode - sharedPreferences.edit { putBoolean("ble_only_mode", bleOnlyMode)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.ble_only_mode), - fontSize = 16.sp, - color = textColor - ) - Text( - text = stringResource(R.string.ble_only_mode_description), - fontSize = 13.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = bleOnlyMode, - onCheckedChange = { - bleOnlyMode = it - sharedPreferences.edit { putBoolean("ble_only_mode", it)} - } - ) - } - } - - Text( - text = stringResource(R.string.conversational_awareness).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column ( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - val sliderValue = remember { mutableFloatStateOf(0f) } - LaunchedEffect(sliderValue) { - if (sharedPreferences.contains("conversational_awareness_volume")) { - sliderValue.floatValue = sharedPreferences.getInt("conversational_awareness_volume", 43).toFloat() - } - } - fun updateConversationalAwarenessPauseMusic(enabled: Boolean) { - conversationalAwarenessPauseMusicEnabled = enabled + conversationalAwarenessPauseMusicEnabled.value = enabled sharedPreferences.edit { putBoolean("conversational_awareness_pause_music", enabled)} } fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) { - relativeConversationalAwarenessVolumeEnabled = enabled + relativeConversationalAwarenessVolumeEnabled.value = enabled sharedPreferences.edit { putBoolean("relative_conversational_awareness_volume", enabled)} } - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - updateConversationalAwarenessPauseMusic(!conversationalAwarenessPauseMusicEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.conversational_awareness_pause_music), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.conversational_awareness_pause_music_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = conversationalAwarenessPauseMusicEnabled, - onCheckedChange = { - updateConversationalAwarenessPauseMusic(it) - }, - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - updateRelativeConversationalAwarenessVolume(!relativeConversationalAwarenessVolumeEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.relative_conversational_awareness_volume), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.relative_conversational_awareness_volume_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = relativeConversationalAwarenessVolumeEnabled, - onCheckedChange = { - updateRelativeConversationalAwarenessVolume(it) - } - ) - } - - Text( - text = stringResource(R.string.conversational_awareness_volume), - fontSize = 16.sp, - color = textColor, - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) + StyledToggle( + label = stringResource(R.string.conversational_awareness_pause_music), + description = stringResource(R.string.conversational_awareness_pause_music_description), + checkedState = conversationalAwarenessPauseMusicEnabled, + onCheckedChange = { updateConversationalAwarenessPauseMusic(it) }, + independent = false ) - val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFFD9D9D9) - val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) - val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) - - Slider( - value = sliderValue.floatValue, - onValueChange = { - sliderValue.floatValue = it - sharedPreferences.edit { putInt("conversational_awareness_volume", it.toInt())} - }, - valueRange = 10f..85f, - onValueChangeFinished = { - sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat() - }, + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), modifier = Modifier - .fillMaxWidth() - .height(36.dp) - .padding(vertical = 4.dp), - colors = SliderDefaults.colors( - thumbColor = thumbColor, - activeTrackColor = activeTrackColor, - inactiveTrackColor = trackColor, - ), - thumb = { - Box( - modifier = Modifier - .size(24.dp) - .shadow(4.dp, CircleShape) - .background(thumbColor, CircleShape) - ) - }, - track = { - Box ( - modifier = Modifier - .fillMaxWidth() - .height(12.dp), - contentAlignment = Alignment.CenterStart - ) - { - Box( - modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .background(trackColor, RoundedCornerShape(4.dp)) - ) - Box( - modifier = Modifier - .fillMaxWidth(((sliderValue.floatValue - 10) * 100) /7500) - .height(4.dp) - .background(if (conversationalAwarenessPauseMusicEnabled) trackColor else activeTrackColor, RoundedCornerShape(4.dp)) - ) - } - } + .padding(horizontal = 12.dp) ) + StyledToggle( + label = stringResource(R.string.relative_conversational_awareness_volume), + description = stringResource(R.string.relative_conversational_awareness_volume_description), + checkedState = relativeConversationalAwarenessVolumeEnabled, + onCheckedChange = { updateRelativeConversationalAwarenessVolume(it) }, + independent = false + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + val conversationalAwarenessVolume = remember { mutableFloatStateOf(sharedPreferences.getInt("conversational_awareness_volume", 43).toFloat()) } + LaunchedEffect(conversationalAwarenessVolume.floatValue) { + sharedPreferences.edit { putInt("conversational_awareness_volume", conversationalAwarenessVolume.floatValue.roundToInt()) } + } + + StyledSlider( + label = stringResource(R.string.conversational_awareness_volume), + mutableFloatState = conversationalAwarenessVolume, + valueRange = 10f..85f, + startLabel = "10%", + endLabel = "85%", + onValueChange = { newValue -> conversationalAwarenessVolume.floatValue = newValue }, + independent = true + ) + + Spacer(modifier = Modifier.height(16.dp)) + + StyledToggle( + title = stringResource(R.string.quick_settings_tile), + label = stringResource(R.string.open_dialog_for_controlling), + description = stringResource(R.string.open_dialog_for_controlling_description), + checkedState = openDialogForControlling, + onCheckedChange = { + openDialogForControlling.value = it + sharedPreferences.edit { putString("qs_click_behavior", if (it) "dialog" else "activity") } + }, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + StyledToggle( + title = stringResource(R.string.ear_detection), + label = stringResource(R.string.disconnect_when_not_wearing), + description = stringResource(R.string.disconnect_when_not_wearing_description), + checkedState = disconnectWhenNotWearing, + sharedPreferenceKey = "disconnect_when_not_wearing", + sharedPreferences = sharedPreferences, + ) + + Text( + text = stringResource(R.string.takeover_airpods_state), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp) + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Column( + modifier = Modifier + .fillMaxWidth() + .background( + backgroundColor, + RoundedCornerShape(28.dp) + ) + .padding(vertical = 4.dp) + ) { + StyledToggle( + label = stringResource(R.string.takeover_disconnected), + description = stringResource(R.string.takeover_disconnected_desc), + checkedState = takeoverWhenDisconnected, + onCheckedChange = { + takeoverWhenDisconnected.value = it + sharedPreferences.edit { putBoolean("takeover_when_disconnected", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.takeover_idle), + description = stringResource(R.string.takeover_idle_desc), + checkedState = takeoverWhenIdle, + onCheckedChange = { + takeoverWhenIdle.value = it + sharedPreferences.edit { putBoolean("takeover_when_idle", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.takeover_music), + description = stringResource(R.string.takeover_music_desc), + checkedState = takeoverWhenMusic, + onCheckedChange = { + takeoverWhenMusic.value = it + sharedPreferences.edit { putBoolean("takeover_when_music", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.takeover_call), + description = stringResource(R.string.takeover_call_desc), + checkedState = takeoverWhenCall, + onCheckedChange = { + takeoverWhenCall.value = it + sharedPreferences.edit { putBoolean("takeover_when_call", it)} + }, + independent = false + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = stringResource(R.string.takeover_phone_state), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(horizontal = 16.dp) + ) + Spacer(modifier = Modifier.height(4.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .background( + backgroundColor, + RoundedCornerShape(28.dp) + ) + .padding(vertical = 4.dp) + ){ + StyledToggle( + label = stringResource(R.string.takeover_ringing_call), + description = stringResource(R.string.takeover_ringing_call_desc), + checkedState = takeoverWhenRingingCall, + onCheckedChange = { + takeoverWhenRingingCall.value = it + sharedPreferences.edit { putBoolean("takeover_when_ringing_call", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.takeover_media_start), + description = stringResource(R.string.takeover_media_start_desc), + checkedState = takeoverWhenMediaStart, + onCheckedChange = { + takeoverWhenMediaStart.value = it + sharedPreferences.edit { putBoolean("takeover_when_media_start", it)} + }, + independent = false + ) + } + + Text( + text = stringResource(R.string.advanced_options), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp) + ) + + Spacer(modifier = Modifier.height(2.dp)) + + Column( + modifier = Modifier + .fillMaxWidth() + .background( + backgroundColor, + RoundedCornerShape(28.dp) + ) + .padding(horizontal = 16.dp, vertical = 4.dp) + ) { Row( modifier = Modifier .fillMaxWidth() - .padding(bottom = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "10%", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.7f) + .clickable ( + onClick = { showIrkDialog.value = true }, + indication = null, + interactionSource = remember { MutableInteractionSource() } ), - modifier = Modifier.padding(start = 4.dp) - ) - Text( - text = "85%", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.7f) - ), - modifier = Modifier.padding(end = 4.dp) - ) - } - } - - Text( - text = stringResource(R.string.quick_settings_tile).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - fun updateQsClickBehavior(enabled: Boolean) { - openDialogForControlling = enabled - sharedPreferences.edit { putString("qs_click_behavior", if (enabled) "dialog" else "cycle")} - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - updateQsClickBehavior(!openDialogForControlling) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.open_dialog_for_controlling), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.open_dialog_for_controlling_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = openDialogForControlling, - onCheckedChange = { - updateQsClickBehavior(it) - } - ) - } - } - - Text( - text = stringResource(R.string.ear_detection).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - fun updateDisconnectWhenNotWearing(enabled: Boolean) { - disconnectWhenNotWearing = enabled - sharedPreferences.edit { putBoolean("disconnect_when_not_wearing", enabled)} - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - updateDisconnectWhenNotWearing(!disconnectWhenNotWearing) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.disconnect_when_not_wearing), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.disconnect_when_not_wearing_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = disconnectWhenNotWearing, - onCheckedChange = { - updateDisconnectWhenNotWearing(it) - } - ) - } - } - - Text( - text = stringResource(R.string.takeover_header).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_airpods_state), - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - modifier = Modifier.padding(top = 12.dp, bottom = 4.dp) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenDisconnected = !takeoverWhenDisconnected - sharedPreferences.edit { putBoolean("takeover_when_disconnected", takeoverWhenDisconnected)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_disconnected), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_disconnected_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenDisconnected, - onCheckedChange = { - takeoverWhenDisconnected = it - sharedPreferences.edit { putBoolean("takeover_when_disconnected", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenIdle = !takeoverWhenIdle - sharedPreferences.edit { putBoolean("takeover_when_idle", takeoverWhenIdle)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_idle), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_idle_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenIdle, - onCheckedChange = { - takeoverWhenIdle = it - sharedPreferences.edit { putBoolean("takeover_when_idle", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenMusic = !takeoverWhenMusic - sharedPreferences.edit { putBoolean("takeover_when_music", takeoverWhenMusic)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_music), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_music_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenMusic, - onCheckedChange = { - takeoverWhenMusic = it - sharedPreferences.edit { putBoolean("takeover_when_music", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenCall = !takeoverWhenCall - sharedPreferences.edit { putBoolean("takeover_when_call", takeoverWhenCall)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_call), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_call_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenCall, - onCheckedChange = { - takeoverWhenCall = it - sharedPreferences.edit { putBoolean("takeover_when_call", it)} - } - ) - } - - Spacer(modifier = Modifier.height(12.dp)) - - Text( - text = stringResource(R.string.takeover_phone_state), - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenRingingCall = !takeoverWhenRingingCall - sharedPreferences.edit { putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_ringing_call), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_ringing_call_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenRingingCall, - onCheckedChange = { - takeoverWhenRingingCall = it - sharedPreferences.edit { putBoolean("takeover_when_ringing_call", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenMediaStart = !takeoverWhenMediaStart - sharedPreferences.edit { putBoolean("takeover_when_media_start", takeoverWhenMediaStart)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_media_start), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_media_start_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenMediaStart, - onCheckedChange = { - takeoverWhenMediaStart = it - sharedPreferences.edit { putBoolean("takeover_when_media_start", it)} - } - ) - } - } - - Text( - text = stringResource(R.string.advanced_options).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - showIrkDialog = true - }, verticalAlignment = Alignment.CenterVertically ) { Column( @@ -1026,12 +524,19 @@ fun AppSettingsScreen(navController: NavController) { } } + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + ) + Row( modifier = Modifier .fillMaxWidth() - .clickable { - showEncKeyDialog = true - }, + .clickable ( + onClick = { showEncKeyDialog.value = true }, + indication = null, + interactionSource = remember { MutableInteractionSource() } + ), verticalAlignment = Alignment.CenterVertically ) { Column( @@ -1054,177 +559,71 @@ fun AppSettingsScreen(navController: NavController) { ) } } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - useAlternateHeadTrackingPackets = !useAlternateHeadTrackingPackets - sharedPreferences.edit { - putBoolean( - "use_alternate_head_tracking_packets", - useAlternateHeadTrackingPackets)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.use_alternate_head_tracking_packets), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.use_alternate_head_tracking_packets_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = useAlternateHeadTrackingPackets, - onCheckedChange = { - useAlternateHeadTrackingPackets = it - sharedPreferences.edit { putBoolean("use_alternate_head_tracking_packets", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - navController.navigate("troubleshooting") - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.troubleshooting), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.troubleshooting_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - } - - LaunchedEffect(Unit) { - actAsAppleDevice = RadareOffsetFinder.isSdpOffsetAvailable() - } - val restartBluetoothText = stringResource(R.string.found_offset_restart_bluetooth) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - enabled = !isProcessingSdp, - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - if (!isProcessingSdp) { - val newValue = !actAsAppleDevice - actAsAppleDevice = newValue - isProcessingSdp = true - coroutineScope.launch { - if (newValue) { - val radareOffsetFinder = RadareOffsetFinder(context) - val success = radareOffsetFinder.findSdpOffset() - if (success) { - Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() - } - } else { - RadareOffsetFinder.clearSdpOffset() - } - isProcessingSdp = false - } - } - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.act_as_an_apple_device), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.act_as_an_apple_device_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - if (actAsAppleDevice) { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.act_as_an_apple_device_warning), - fontSize = 12.sp, - color = MaterialTheme.colorScheme.error, - lineHeight = 14.sp, - ) - } - } - StyledSwitch( - checked = actAsAppleDevice, - onCheckedChange = { - if (!isProcessingSdp) { - actAsAppleDevice = it - isProcessingSdp = true - coroutineScope.launch { - if (it) { - val radareOffsetFinder = RadareOffsetFinder(context) - val success = radareOffsetFinder.findSdpOffset() - if (success) { - Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() - } - } else { - RadareOffsetFinder.clearSdpOffset() - } - isProcessingSdp = false - } - } - }, - enabled = !isProcessingSdp - ) - } } Spacer(modifier = Modifier.height(16.dp)) + StyledToggle( + label = stringResource(R.string.use_alternate_head_tracking_packets), + description = stringResource(R.string.use_alternate_head_tracking_packets_description), + checkedState = useAlternateHeadTrackingPackets, + onCheckedChange = { + useAlternateHeadTrackingPackets.value = it + sharedPreferences.edit { putBoolean("use_alternate_head_tracking_packets", it)} + }, + independent = true + ) + + Spacer(modifier = Modifier.height(16.dp)) + + NavigationButton( + to = "troubleshooting", + name = stringResource(R.string.troubleshooting), + navController = navController, + independent = true, + description = stringResource(R.string.troubleshooting_description) + ) + + LaunchedEffect(Unit) { + actAsAppleDevice.value = RadareOffsetFinder.isSdpOffsetAvailable() + } + val restartBluetoothText = stringResource(R.string.found_offset_restart_bluetooth) + + StyledToggle( + label = stringResource(R.string.act_as_an_apple_device), + description = stringResource(R.string.act_as_an_apple_device_description), + checkedState = actAsAppleDevice, + onCheckedChange = { + actAsAppleDevice.value = it + isProcessingSdp.value = true + coroutineScope.launch { + if (it) { + val radareOffsetFinder = RadareOffsetFinder(context) + val success = radareOffsetFinder.findSdpOffset() + if (success) { + Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() + } + } else { + RadareOffsetFinder.clearSdpOffset() + } + isProcessingSdp.value = false + } + }, + independent = true, + enabled = !isProcessingSdp.value + ) + + Spacer(modifier = Modifier.height(16.dp)) + Button( - onClick = { showResetDialog = true }, + onClick = { showResetDialog.value = true }, modifier = Modifier .fillMaxWidth() .height(50.dp), colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.errorContainer ), - shape = RoundedCornerShape(14.dp) + shape = RoundedCornerShape(28.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -1251,9 +650,9 @@ fun AppSettingsScreen(navController: NavController) { Spacer(modifier = Modifier.height(32.dp)) - if (showResetDialog) { + if (showResetDialog.value) { AlertDialog( - onDismissRequest = { showResetDialog = false }, + onDismissRequest = { showResetDialog.value = false }, title = { Text( "Reset Hook Offset", @@ -1289,7 +688,7 @@ fun AppSettingsScreen(navController: NavController) { Toast.LENGTH_SHORT ).show() } - showResetDialog = false + showResetDialog.value = false }, colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.error @@ -1304,7 +703,7 @@ fun AppSettingsScreen(navController: NavController) { }, dismissButton = { TextButton( - onClick = { showResetDialog = false } + onClick = { showResetDialog.value = false } ) { Text( "Cancel", @@ -1316,9 +715,9 @@ fun AppSettingsScreen(navController: NavController) { ) } - if (showIrkDialog) { + if (showIrkDialog.value) { AlertDialog( - onDismissRequest = { showIrkDialog = false }, + onDismissRequest = { showIrkDialog.value = false }, title = { Text( stringResource(R.string.set_identity_resolving_key), @@ -1335,13 +734,13 @@ fun AppSettingsScreen(navController: NavController) { ) OutlinedTextField( - value = irkValue, + value = irkValue.value, onValueChange = { - irkValue = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } - irkError = null + irkValue.value = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } + irkError.value = null }, modifier = Modifier.fillMaxWidth(), - isError = irkError != null, + isError = irkError.value != null, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Ascii, capitalization = KeyboardCapitalization.None @@ -1351,7 +750,7 @@ fun AppSettingsScreen(navController: NavController) { unfocusedBorderColor = if (isDarkTheme) Color.Gray else Color.LightGray ), supportingText = { - if (irkError != null) { + if (irkError.value != null) { Text(stringResource(R.string.must_be_32_hex_chars), color = MaterialTheme.colorScheme.error) } }, @@ -1364,15 +763,15 @@ fun AppSettingsScreen(navController: NavController) { val errorText = stringResource(R.string.error_converting_hex) TextButton( onClick = { - if (!validateHexInput(irkValue)) { - irkError = "Must be exactly 32 hex characters" + if (!validateHexInput(irkValue.value)) { + irkError.value = "Must be exactly 32 hex characters" return@TextButton } try { val hexBytes = ByteArray(16) for (i in 0 until 16) { - val hexByte = irkValue.substring(i * 2, i * 2 + 2) + val hexByte = irkValue.value.substring(i * 2, i * 2 + 2) hexBytes[i] = hexByte.toInt(16).toByte() } @@ -1380,9 +779,9 @@ fun AppSettingsScreen(navController: NavController) { sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value)} Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() - showIrkDialog = false + showIrkDialog.value = false } catch (e: Exception) { - irkError = errorText + " " + (e.message ?: "Unknown error") + irkError.value = errorText + " " + (e.message ?: "Unknown error") } } ) { @@ -1395,7 +794,7 @@ fun AppSettingsScreen(navController: NavController) { }, dismissButton = { TextButton( - onClick = { showIrkDialog = false } + onClick = { showIrkDialog.value = false } ) { Text( "Cancel", @@ -1407,9 +806,9 @@ fun AppSettingsScreen(navController: NavController) { ) } - if (showEncKeyDialog) { + if (showEncKeyDialog.value) { AlertDialog( - onDismissRequest = { showEncKeyDialog = false }, + onDismissRequest = { showEncKeyDialog.value = false }, title = { Text( stringResource(R.string.set_encryption_key), @@ -1426,13 +825,13 @@ fun AppSettingsScreen(navController: NavController) { ) OutlinedTextField( - value = encKeyValue, + value = encKeyValue.value, onValueChange = { - encKeyValue = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } - encKeyError = null + encKeyValue.value = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } + encKeyError.value = null }, modifier = Modifier.fillMaxWidth(), - isError = encKeyError != null, + isError = encKeyError.value != null, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Ascii, capitalization = KeyboardCapitalization.None @@ -1442,7 +841,7 @@ fun AppSettingsScreen(navController: NavController) { unfocusedBorderColor = if (isDarkTheme) Color.Gray else Color.LightGray ), supportingText = { - if (encKeyError != null) { + if (encKeyError.value != null) { Text(stringResource(R.string.must_be_32_hex_chars), color = MaterialTheme.colorScheme.error) } }, @@ -1455,15 +854,15 @@ fun AppSettingsScreen(navController: NavController) { val errorText = stringResource(R.string.error_converting_hex) TextButton( onClick = { - if (!validateHexInput(encKeyValue)) { - encKeyError = "Must be exactly 32 hex characters" + if (!validateHexInput(encKeyValue.value)) { + encKeyError.value = "Must be exactly 32 hex characters" return@TextButton } try { val hexBytes = ByteArray(16) for (i in 0 until 16) { - val hexByte = encKeyValue.substring(i * 2, i * 2 + 2) + val hexByte = encKeyValue.value.substring(i * 2, i * 2 + 2) hexBytes[i] = hexByte.toInt(16).toByte() } @@ -1471,9 +870,9 @@ fun AppSettingsScreen(navController: NavController) { sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value)} Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() - showEncKeyDialog = false + showEncKeyDialog.value = false } catch (e: Exception) { - encKeyError = errorText + " " + (e.message ?: "Unknown error") + encKeyError.value = errorText + " " + (e.message ?: "Unknown error") } } ) { @@ -1486,7 +885,7 @@ fun AppSettingsScreen(navController: NavController) { }, dismissButton = { TextButton( - onClick = { showEncKeyDialog = false } + onClick = { showEncKeyDialog.value = false } ) { Text( "Cancel", 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 f9dbdab..fc3bf05 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 @@ -40,14 +40,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState 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.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Send import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -78,6 +75,8 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +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.delay @@ -325,14 +324,15 @@ fun DebugScreen(navController: NavController) { } val isDarkTheme = isSystemInDarkTheme() - + val backdrop = rememberLayerBackdrop() StyledScaffold( title = "Debug", navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, actionButtons = listOf( @@ -344,6 +344,7 @@ fun DebugScreen(navController: NavController) { }, icon = "􀈑", darkMode = isDarkTheme, + backdrop = backdrop ) } ), @@ -353,6 +354,7 @@ fun DebugScreen(navController: NavController) { .fillMaxSize() .hazeSource(hazeState) .navigationBarsPadding() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -391,11 +393,13 @@ fun DebugScreen(navController: NavController) { ) ) { Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = if (isSent) Icons.AutoMirrored.Filled.KeyboardArrowLeft else Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = null, - tint = if (isSent) Color.Green else Color.Red, - modifier = Modifier.size(24.dp) + Text( + text = if (isSent) "􀆉" else "􀆊", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isSent) Color(0xFF4CD964) else Color(0xFFFF3B30) + ), ) Spacer(modifier = Modifier.width(4.dp)) Column { 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 ad71bb4..a42241a 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,10 +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.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -87,12 +84,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +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.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledToggle @@ -116,18 +116,19 @@ fun HeadTrackingScreen(navController: NavController) { } } val isDarkTheme = isSystemInDarkTheme() - val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black val scrollState = rememberScrollState() - + val backdrop = rememberLayerBackdrop() StyledScaffold ( title = stringResource(R.string.head_tracking), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, actionButtons = listOf( @@ -144,72 +145,95 @@ fun HeadTrackingScreen(navController: NavController) { } }, icon = if (isActive) "􀊅" else "􀊃", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ), ) { spacerHeight, hazeState -> - Column ( + val backdrop = rememberLayerBackdrop() + val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) + + var gestureText by remember { mutableStateOf("") } + val coroutineScope = rememberCoroutineScope() + + var lastClickTime by remember { mutableLongStateOf(0L) } + var shouldExplode by remember { mutableStateOf(false) } + Column( modifier = Modifier - .fillMaxSize() - .padding(top = 8.dp) - .padding(horizontal = 16.dp) - .verticalScroll(scrollState) - .hazeSource(state = hazeState) + .layerBackdrop(backdrop) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(spacerHeight)) - val sharedPreferences = - LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) + Column ( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .padding(horizontal = 16.dp) + .verticalScroll(scrollState) + .hazeSource(state = hazeState) + .layerBackdrop( + backdrop = backdrop + ) + ) { + Spacer(modifier = Modifier.height(spacerHeight)) + StyledToggle( + label = "Head Gestures", + sharedPreferences = sharedPreferences, + sharedPreferenceKey = "head_gestures", + ) - var gestureText by remember { mutableStateOf("") } - val coroutineScope = rememberCoroutineScope() + Spacer(modifier = Modifier.height(2.dp)) + Text( + stringResource(R.string.head_gestures_details), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = textColor.copy(0.6f) + ), + modifier = Modifier.padding(start = 4.dp) + ) - StyledToggle( - label = "Head Gestures", - sharedPreferences = sharedPreferences, - sharedPreferenceKey = "head_gestures", - ) - - Spacer(modifier = Modifier.height(2.dp)) - Text( - stringResource(R.string.head_gestures_details), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Normal, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor.copy(0.6f) - ), - modifier = Modifier.padding(start = 4.dp) - ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + "Head Orientation", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = textColor + ), + modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) + ) + HeadVisualization() - Spacer(modifier = Modifier.height(16.dp)) - Text( - "Head Orientation", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor - ), - modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) - ) - HeadVisualization() + Spacer(modifier = Modifier.height(16.dp)) + Text( + "Velocity", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = textColor + ), + modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) + ) + AccelerationPlot() - Spacer(modifier = Modifier.height(16.dp)) - Text( - "Velocity", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor - ), - modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) - ) - AccelerationPlot() + Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.height(16.dp)) - Button ( + LaunchedEffect(gestureText) { + if (gestureText.isNotEmpty()) { + lastClickTime = System.currentTimeMillis() + delay(3000) + if (System.currentTimeMillis() - lastClickTime >= 3000) { + shouldExplode = true + } + } + } + } + StyledButton( onClick = { gestureText = "Shake your head or nod!" coroutineScope.launch { @@ -217,13 +241,9 @@ fun HeadTrackingScreen(navController: NavController) { gestureText = if (accepted) "\"Yes\" gesture detected." else "\"No\" gesture detected." } }, - modifier = Modifier - .fillMaxWidth() - .height(55.dp), - colors = ButtonDefaults.buttonColors( - containerColor = backgroundColor - ), - shape = RoundedCornerShape(8.dp) + backdrop = backdrop, + modifier = Modifier.fillMaxWidth(0.75f), + maxScale = 0.05f ) { Text( "Test Head Gestures", @@ -235,19 +255,6 @@ fun HeadTrackingScreen(navController: NavController) { ), ) } - var lastClickTime by remember { mutableLongStateOf(0L) } - var shouldExplode by remember { mutableStateOf(false) } - - LaunchedEffect(gestureText) { - if (gestureText.isNotEmpty()) { - lastClickTime = System.currentTimeMillis() - delay(3000) - if (System.currentTimeMillis() - lastClickTime >= 3000) { - shouldExplode = true - } - } - } - Box( contentAlignment = Alignment.Center, modifier = Modifier.padding(top = 12.dp, bottom = 24.dp) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 6c642a5..1a48a12 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -41,6 +41,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi @@ -77,14 +79,15 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") val aacpManager = remember { ServiceManager.getService()?.aacpManager } - + val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.adjustments), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight -> @@ -92,6 +95,7 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController modifier = Modifier .hazeSource(hazeState) .fillMaxSize() + .layerBackdrop(backdrop) .verticalScroll(verticalScrollState) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -282,7 +286,7 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController } StyledSlider( - label = stringResource(R.string.amplification).uppercase(), + label = stringResource(R.string.amplification), valueRange = -1f..1f, mutableFloatState = amplificationSliderValue, onValueChange = { @@ -301,20 +305,20 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController ) StyledSlider( - label = stringResource(R.string.balance).uppercase(), + label = stringResource(R.string.balance), valueRange = -1f..1f, mutableFloatState = balanceSliderValue, onValueChange = { balanceSliderValue.floatValue = it }, - snapPoints = listOf(0f), + snapPoints = listOf(-1f, 0f, 1f), startLabel = stringResource(R.string.left), endLabel = stringResource(R.string.right), independent = true, ) StyledSlider( - label = stringResource(R.string.tone).uppercase(), + label = stringResource(R.string.tone), valueRange = -1f..1f, mutableFloatState = toneSliderValue, onValueChange = { @@ -326,7 +330,7 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController ) StyledSlider( - label = stringResource(R.string.ambient_noise_reduction).uppercase(), + label = stringResource(R.string.ambient_noise_reduction), valueRange = 0f..1f, mutableFloatState = ambientNoiseReductionSliderValue, onValueChange = { 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 106ea48..28e4c98 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 @@ -20,15 +20,10 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint import android.util.Log -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -37,25 +32,18 @@ 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.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font @@ -74,9 +62,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.ConfirmationDialog +import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold -import me.kavishdevar.librepods.composables.StyledSwitch import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager @@ -117,7 +105,8 @@ fun HearingAidScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, actionButtons = emptyList(), @@ -129,7 +118,7 @@ fun HearingAidScreen(navController: NavController) { .hazeSource(hazeState) .fillMaxSize() .verticalScroll(verticalScrollState) - .padding(16.dp), + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -175,22 +164,22 @@ fun HearingAidScreen(navController: NavController) { } fun onAdjustPhoneChange(value: Boolean) { - adjustPhoneEnabled.value = value + // TODO } fun onAdjustMediaChange(value: Boolean) { - adjustMediaEnabled.value = value + // TODO } Text( - text = stringResource(R.string.hearing_aid).uppercase(), + text = stringResource(R.string.hearing_aid), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp) ) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) @@ -198,9 +187,9 @@ fun HearingAidScreen(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .clip( - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) ) { StyledToggle( @@ -209,31 +198,17 @@ fun HearingAidScreen(navController: NavController) { independent = false ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) + ) + NavigationButton( + to = "hearing_aid_adjustments", + name = stringResource(R.string.adjustments), + navController, + independent = false ) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { navController.navigate("hearing_aid_adjustments") } - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.adjustments), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.weight(1f)) - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = null, - tint = textColor - ) - } } Text( text = stringResource(R.string.hearing_aid_description), @@ -243,12 +218,12 @@ fun HearingAidScreen(navController: NavController) { color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(horizontal = 8.dp) + modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(16.dp)) - + StyledToggle( - title = stringResource(R.string.media_assist).uppercase(), + title = stringResource(R.string.media_assist), label = stringResource(R.string.media_assist), checkedState = mediaAssistEnabled, independent = true, @@ -260,99 +235,27 @@ fun HearingAidScreen(navController: NavController) { Column ( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) ) { - val isDarkThemeLocal = isSystemInDarkTheme() - var backgroundColorAdjustMedia by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColorAdjustMedia by animateColorAsState(targetValue = backgroundColorAdjustMedia, animationSpec = tween(durationMillis = 500)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - onAdjustMediaChange(!adjustMediaEnabled.value) - } - ) - } - .background( - animatedBackgroundColorAdjustMedia, - RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - ), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.adjust_media), - modifier = Modifier.weight(1f), - style = TextStyle( - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Normal, - color = textColor - ) - ) - StyledSwitch( - checked = adjustMediaEnabled.value, - onCheckedChange = { - onAdjustMediaChange(it) - }, - ) - } - + StyledToggle( + label = stringResource(R.string.adjust_media), + checkedState = adjustMediaEnabled, + onCheckedChange = { onAdjustMediaChange(it) }, + independent = false + ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) ) - var backgroundColorAdjustPhone by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColorAdjustPhone by animateColorAsState(targetValue = backgroundColorAdjustPhone, animationSpec = tween(durationMillis = 500)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - onAdjustPhoneChange(!adjustPhoneEnabled.value) - } - ) - } - .background( - animatedBackgroundColorAdjustPhone, - RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) - ), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.adjust_calls), - modifier = Modifier.weight(1f), - style = TextStyle( - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Normal, - color = textColor - ) - ) - StyledSwitch( - checked = adjustPhoneEnabled.value, - onCheckedChange = { - onAdjustPhoneChange(it) - }, - ) - } + StyledToggle( + label = stringResource(R.string.adjust_calls), + checkedState = adjustPhoneEnabled, + onCheckedChange = { onAdjustPhoneChange(it) }, + independent = false + ) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt index e6420bb..7e886ba 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt @@ -73,6 +73,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -151,6 +153,7 @@ fun Onboarding(navController: NavController, activityContext: Context) { isComplete = true } } + val backdrop = rememberLayerBackdrop() StyledScaffold( title = "Setting Up", actionButtons = listOf( @@ -160,7 +163,8 @@ fun Onboarding(navController: NavController, activityContext: Context) { showSkipDialog = true }, icon = "􀊋", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) @@ -168,6 +172,7 @@ fun Onboarding(navController: NavController, activityContext: Context) { Column( modifier = Modifier .fillMaxSize() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index 032cbc4..d461322 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -23,6 +23,7 @@ package me.kavishdevar.librepods.screens import android.content.Context import android.util.Log import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures @@ -37,8 +38,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -50,11 +49,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue 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.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily @@ -63,6 +62,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.StyledIconButton @@ -76,20 +77,20 @@ import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun RightDivider() { HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 72.dp) + .padding(start = 72.dp, end = 20.dp) ) } @Composable fun RightDividerNoIcon() { HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 20.dp) + .padding(start = 20.dp, end = 20.dp) ) } @@ -117,19 +118,22 @@ fun LongPress(navController: NavController, name: String) { val longPressActionPref = sharedPreferences.getString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name) Log.d("PressAndHoldSettingsScreen", "Long press action preference ($prefKey): $longPressActionPref") var longPressAction by remember { mutableStateOf(StemAction.valueOf(longPressActionPref ?: StemAction.CYCLE_NOISE_CONTROL_MODES.name)) } + val backdrop = rememberLayerBackdrop() StyledScaffold( title = name, navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight -> val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Column ( modifier = Modifier + .layerBackdrop(backdrop) .fillMaxSize() .padding(top = 8.dp) .padding(horizontal = 16.dp) @@ -138,11 +142,11 @@ fun LongPress(navController: NavController, name: String) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)), + .background(backgroundColor, RoundedCornerShape(28.dp)), horizontalAlignment = Alignment.CenterHorizontally ) { LongPressActionElement( - name = "Noise Control", + name = stringResource(R.string.noise_control), selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES, onClick = { longPressAction = StemAction.CYCLE_NOISE_CONTROL_MODES @@ -153,7 +157,7 @@ fun LongPress(navController: NavController, name: String) { ) RightDividerNoIcon() LongPressActionElement( - name = "Digital Assistant", + name = stringResource(R.string.digital_assistant), selected = longPressAction == StemAction.DIGITAL_ASSISTANT, onClick = { longPressAction = StemAction.DIGITAL_ASSISTANT @@ -165,23 +169,25 @@ fun LongPress(navController: NavController, name: String) { } if (longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES) { + Spacer(modifier = Modifier.height(32.dp)) Text( - text = "NOISE CONTROL", + text = stringResource(R.string.noise_control), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), ), fontFamily = FontFamily(Font(R.font.sf_pro)), modifier = Modifier - .padding(top = 32.dp, bottom = 4.dp) - .padding(horizontal = 8.dp) + .padding(horizontal = 18.dp) ) + Spacer(modifier = Modifier.height(8.dp)) + Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)), + .background(backgroundColor, RoundedCornerShape(28.dp)), horizontalAlignment = Alignment.CenterHorizontally ) { val offListeningModeValue = ServiceManager.getService()!!.aacpManager.controlCommandStatusList.find { @@ -189,28 +195,28 @@ fun LongPress(navController: NavController, name: String) { }?.value?.takeIf { it.isNotEmpty() }?.get(0) val offListeningMode = offListeningModeValue == 1.toByte() ListeningModeElement( - name = "Off", + name = stringResource(R.string.off), enabled = offListeningMode, resourceId = R.drawable.noise_cancellation, isFirst = true) if (offListeningMode) RightDivider() ListeningModeElement( - name = "Transparency", + name = stringResource(R.string.transparency), resourceId = R.drawable.transparency, isFirst = !offListeningMode) RightDivider() ListeningModeElement( - name = "Adaptive", + name = stringResource(R.string.adaptive), resourceId = R.drawable.adaptive) RightDivider() ListeningModeElement( - name = "Noise Cancellation", + name = stringResource(R.string.noise_cancellation), resourceId = R.drawable.noise_cancellation, isLast = true) } - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Press and hold the stem to cycle between the selected noise control modes.", + text = stringResource(R.string.press_and_hold_noise_control_description), style = TextStyle( fontSize = 12.sp, fontWeight = FontWeight.Light, @@ -218,7 +224,7 @@ fun LongPress(navController: NavController, name: String) { fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 18.dp) ) } } @@ -329,8 +335,8 @@ fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, } val shape = when { - isFirst -> RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - isLast -> RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + isFirst -> RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) + isLast -> RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp) else -> RoundedCornerShape(0.dp) } var backgroundColor by remember { mutableStateOf(if (darkMode) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } @@ -352,7 +358,7 @@ fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, }, ) } - .padding(horizontal = 16.dp, vertical = 0.dp), + .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { @@ -384,24 +390,19 @@ fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, fontFamily = FontFamily(Font(R.font.sf_pro)), ) } - Checkbox( - checked = checked.value, - onCheckedChange = { valueChanged() }, - colors = CheckboxDefaults.colors().copy( - checkedCheckmarkColor = Color(0xFF007AFF), - uncheckedCheckmarkColor = Color.Transparent, - checkedBoxColor = Color.Transparent, - uncheckedBoxColor = Color.Transparent, - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent, - disabledBorderColor = Color.Transparent, - disabledCheckedBoxColor = Color.Transparent, - disabledUncheckedBoxColor = Color.Transparent, - disabledUncheckedBorderColor = Color.Transparent + + val floatAnimateState by animateFloatAsState( + targetValue = if (checked.value) 1f else 0f, + animationSpec = tween(durationMillis = 300) + ) + Text( + text = "􀆅", + style = TextStyle( + fontSize = 20.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = Color(0xFF007AFF).copy(alpha = floatAnimateState), ), - modifier = Modifier - .height(24.dp) - .scale(1.5f), + modifier = Modifier.padding(end = 4.dp) ) } } @@ -417,15 +418,15 @@ fun LongPressActionElement( ) { val darkMode = isSystemInDarkTheme() val shape = when { - isFirst -> RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - isLast -> RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + isFirst -> RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) + isLast -> RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp) else -> RoundedCornerShape(0.dp) } var backgroundColor by remember { mutableStateOf(if (darkMode) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) Row( modifier = Modifier - .height(48.dp) + .height(55.dp) .background(animatedBackgroundColor, shape) .pointerInput(Unit) { detectTapGestures( @@ -437,7 +438,7 @@ fun LongPressActionElement( } ) } - .padding(horizontal = 16.dp, vertical = 0.dp), + .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { @@ -449,24 +450,18 @@ fun LongPressActionElement( .weight(1f) .padding(start = 4.dp) ) - Checkbox( - checked = selected, - onCheckedChange = { onClick() }, - colors = CheckboxDefaults.colors().copy( - checkedCheckmarkColor = Color(0xFF007AFF), - uncheckedCheckmarkColor = Color.Transparent, - checkedBoxColor = Color.Transparent, - uncheckedBoxColor = Color.Transparent, - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent, - disabledBorderColor = Color.Transparent, - disabledCheckedBoxColor = Color.Transparent, - disabledUncheckedBoxColor = Color.Transparent, - disabledUncheckedBorderColor = Color.Transparent + val floatAnimateState by animateFloatAsState( + targetValue = if (selected) 1f else 0f, + animationSpec = tween(durationMillis = 300) + ) + Text( + text = "􀆅", + style = TextStyle( + fontSize = 20.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = Color(0xFF007AFF).copy(alpha = floatAnimateState) ), - modifier = Modifier - .height(24.dp) - .scale(1.5f), + modifier = Modifier.padding(end = 4.dp) ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt index 9dd1f95..11f5eba 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt @@ -32,11 +32,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf @@ -52,12 +50,16 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange 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.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.StyledIconButton @@ -81,19 +83,23 @@ fun RenameScreen(navController: NavController) { name.value = name.value.copy(selection = TextRange(name.value.text.length)) } + val backdrop = rememberLayerBackdrop() + StyledScaffold( title = stringResource(R.string.name), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, ) { spacerHeight -> Column( modifier = Modifier .fillMaxSize() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -105,10 +111,10 @@ fun RenameScreen(navController: NavController) { verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .height(55.dp) + .height(58.dp) .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(horizontal = 16.dp, vertical = 8.dp) ) { @@ -120,8 +126,9 @@ fun RenameScreen(navController: NavController) { ServiceManager.getService()?.setName(it.text) }, textStyle = TextStyle( - color = textColor, fontSize = 16.sp, + color = textColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) ), singleLine = true, cursorBrush = SolidColor(cursorColor), @@ -138,14 +145,15 @@ fun RenameScreen(navController: NavController) { IconButton( onClick = { name.value = TextFieldValue("") - sharedPreferences.edit { putString("name", "") } - ServiceManager.getService()?.setName("") } ) { - Icon( - Icons.Default.Clear, - contentDescription = "Clear", - tint = if (isDarkTheme) Color.White else Color.Black + Text( + text = "􀁡", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f) + ), ) } } 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 a0a8855..ebee5a5 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 @@ -59,6 +59,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +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.delay @@ -95,19 +97,23 @@ fun TransparencySettingsScreen(navController: NavController) { val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) + val backdrop = rememberLayerBackdrop() + StyledScaffold( title = stringResource(R.string.customize_transparency_mode), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ){ spacerHeight, hazeState -> Column( modifier = Modifier .hazeSource(hazeState) + .layerBackdrop(backdrop) .fillMaxSize() .verticalScroll(verticalScrollState) .padding(horizontal = 16.dp), @@ -154,19 +160,15 @@ fun TransparencySettingsScreen(navController: NavController) { object : (ByteArray) -> Unit { override fun invoke(value: ByteArray) { val parsed = parseTransparencySettingsResponse(value) - if (parsed != null) { - enabled.value = parsed.enabled - amplificationSliderValue.floatValue = parsed.netAmplification - balanceSliderValue.floatValue = parsed.balance - toneSliderValue.floatValue = parsed.leftTone - ambientNoiseReductionSliderValue.floatValue = - parsed.leftAmbientNoiseReduction - conversationBoostEnabled.value = parsed.leftConversationBoost - eq.value = parsed.leftEQ.copyOf() - Log.d(TAG, "Updated transparency settings from notification") - } else { - Log.w(TAG, "Failed to parse transparency settings from notification") - } + enabled.value = parsed.enabled + amplificationSliderValue.floatValue = parsed.netAmplification + balanceSliderValue.floatValue = parsed.balance + toneSliderValue.floatValue = parsed.leftTone + ambientNoiseReductionSliderValue.floatValue = + parsed.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsed.leftConversationBoost + eq.value = parsed.leftEQ.copyOf() + Log.d(TAG, "Updated transparency settings from notification") } } } @@ -251,12 +253,7 @@ fun TransparencySettingsScreen(navController: NavController) { try { val data = attManager.read(ATTHandles.TRANSPARENCY) parsedSettings = parseTransparencySettingsResponse(data = data) - if (parsedSettings != null) { - Log.d(TAG, "Parsed settings on attempt $attempt") - break - } else { - Log.d(TAG, "Parsing returned null on attempt $attempt") - } + Log.d(TAG, "Parsed settings on attempt $attempt") } catch (e: Exception) { Log.w(TAG, "Read attempt $attempt failed: ${e.message}") } @@ -297,7 +294,7 @@ fun TransparencySettingsScreen(navController: NavController) { ) Spacer(modifier = Modifier.height(4.dp)) StyledSlider( - label = stringResource(R.string.amplification).uppercase(), + label = stringResource(R.string.amplification), valueRange = -1f..1f, mutableFloatState = amplificationSliderValue, onValueChange = { @@ -309,20 +306,20 @@ fun TransparencySettingsScreen(navController: NavController) { ) StyledSlider( - label = stringResource(R.string.balance).uppercase(), + label = stringResource(R.string.balance), valueRange = -1f..1f, mutableFloatState = balanceSliderValue, onValueChange = { balanceSliderValue.floatValue = it }, - snapPoints = listOf(0f), + snapPoints = listOf(-1f, 0f, 1f), startLabel = stringResource(R.string.left), endLabel = stringResource(R.string.right), independent = true, ) StyledSlider( - label = stringResource(R.string.tone).uppercase(), + label = stringResource(R.string.tone), valueRange = -1f..1f, mutableFloatState = toneSliderValue, onValueChange = { @@ -334,7 +331,7 @@ fun TransparencySettingsScreen(navController: NavController) { ) StyledSlider( - label = stringResource(R.string.ambient_noise_reduction).uppercase(), + label = stringResource(R.string.ambient_noise_reduction), valueRange = 0f..1f, mutableFloatState = ambientNoiseReductionSliderValue, onValueChange = { @@ -356,20 +353,20 @@ fun TransparencySettingsScreen(navController: NavController) { // Only show transparency mode EQ section if SDP offset is available if (isSdpOffsetAvailable.value) { Text( - text = stringResource(R.string.equalizer).uppercase(), + text = stringResource(R.string.equalizer), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceBetween 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 1124ad5..467367b 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 @@ -85,6 +85,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.FileProvider import androidx.navigation.NavController +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.Dispatchers @@ -208,6 +210,8 @@ fun TroubleshootingScreen(navController: NavController) { showBottomSheet = true } + val backdrop = rememberLayerBackdrop() + Box( modifier = Modifier.fillMaxSize() ) { @@ -217,28 +221,30 @@ fun TroubleshootingScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ){ spacerHeight, hazeState -> Column( modifier = Modifier .fillMaxSize() - .verticalScroll(scrollState) + .layerBackdrop(backdrop) .hazeSource(state = hazeState) + .verticalScroll(scrollState) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) Text( - text = stringResource(R.string.saved_logs).uppercase(), + text = stringResource(R.string.saved_logs), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 8.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp, top = 8.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -249,7 +255,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally @@ -266,7 +272,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(horizontal = 16.dp, vertical = 8.dp) ) { @@ -372,14 +378,14 @@ fun TroubleshootingScreen(navController: NavController) { Spacer(modifier = Modifier.height(16.dp)) Text( - text = "TROUBLESHOOTING STEPS".uppercase(), + text = "TROUBLESHOOTING STEPS", style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 8.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 8.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -389,7 +395,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(16.dp) ) { diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 2c49987..31a83be 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -15,12 +15,13 @@ Case Test Name - Noise Control + Listening Mode Off Transparency Adaptive Noise Cancellation Press and Hold AirPods + Press and hold the stem to cycle between the selected listening modes. Head Gestures Left Right @@ -110,7 +111,7 @@ Press Speed Adjust the speed required to press two or three times on your AirPods. Press and Hold Duration - Adjust the duration required to press and hold on your AirPods + Adjust the duration required to press and hold on your AirPods. Volume Swipe Speed To prevent unintended volume adjustments, select preferred wait time between swipes. Equalizer @@ -133,7 +134,7 @@ Ambient Noise Reduction Conversation Boost Conversation Boost focuses your AirPods Pro on the person talking in front of you, making it easier to hear in a face-to-face conversation. - AirPods can use the results of a hearing test to make adjustments that improve the clarity of voices and sounds around you. \n\n Hearing Aid is only intended for people with perceived mild to moderate hearing loss. + AirPods can use the results of a hearing test to make adjustments that improve the clarity of voices and sounds around you.\n\nHearing Aid is only intended for people with perceived mild to moderate hearing loss. Media Assist AirPods Pro can use the results of a hearing test to make adjustments that improve the clarity of music, video, and calls. Adjust Music and Video @@ -174,4 +175,5 @@ Must be exactly 32 hex characters Error converting hex: Found offset please restart the Bluetooth process + Digital Assistant