mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-23 22:54:02 +00:00
add an app settings page for customizing functions and un-hardcode strings
This commit is contained in:
@@ -55,8 +55,10 @@ import androidx.navigation.compose.rememberNavController
|
|||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
import me.kavishdevar.aln.screens.AirPodsSettingsScreen
|
import me.kavishdevar.aln.screens.AirPodsSettingsScreen
|
||||||
|
import me.kavishdevar.aln.screens.AppSettingsScreen
|
||||||
import me.kavishdevar.aln.screens.DebugScreen
|
import me.kavishdevar.aln.screens.DebugScreen
|
||||||
import me.kavishdevar.aln.screens.LongPress
|
import me.kavishdevar.aln.screens.LongPress
|
||||||
|
import me.kavishdevar.aln.screens.RenameScreen
|
||||||
import me.kavishdevar.aln.services.AirPodsService
|
import me.kavishdevar.aln.services.AirPodsService
|
||||||
import me.kavishdevar.aln.ui.theme.ALNTheme
|
import me.kavishdevar.aln.ui.theme.ALNTheme
|
||||||
import me.kavishdevar.aln.utils.AirPodsNotifications
|
import me.kavishdevar.aln.utils.AirPodsNotifications
|
||||||
@@ -158,7 +160,7 @@ fun Main() {
|
|||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = "settings",
|
startDestination = "app_settings",
|
||||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(300)) },
|
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(300)) },
|
||||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }, animationSpec = tween(300)) },
|
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }, animationSpec = tween(300)) },
|
||||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(300)) },
|
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(300)) },
|
||||||
@@ -183,6 +185,12 @@ fun Main() {
|
|||||||
name = navBackStackEntry.arguments?.getString("bud")!!
|
name = navBackStackEntry.arguments?.getString("bud")!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable("rename") { navBackStackEntry ->
|
||||||
|
RenameScreen(navController)
|
||||||
|
}
|
||||||
|
composable("app_settings") {
|
||||||
|
AppSettingsScreen(navController)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceConnection = remember {
|
serviceConnection = remember {
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
import me.kavishdevar.aln.services.AirPodsService
|
import me.kavishdevar.aln.services.AirPodsService
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -44,7 +46,7 @@ fun AccessibilitySettings(service: AirPodsService, sharedPreferences: SharedPref
|
|||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "ACCESSIBILITY",
|
text = stringResource(R.string.accessibility).uppercase(),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Light,
|
fontWeight = FontWeight.Light,
|
||||||
@@ -68,7 +70,7 @@ fun AccessibilitySettings(service: AirPodsService, sharedPreferences: SharedPref
|
|||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Tone Volume",
|
text = stringResource(R.string.tone_volume),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
|
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
@@ -95,4 +97,4 @@ fun AccessibilitySettings(service: AirPodsService, sharedPreferences: SharedPref
|
|||||||
@Composable
|
@Composable
|
||||||
fun AccessibilitySettingsPreview() {
|
fun AccessibilitySettingsPreview() {
|
||||||
AccessibilitySettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
AccessibilitySettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
import me.kavishdevar.aln.services.AirPodsService
|
import me.kavishdevar.aln.services.AirPodsService
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -44,7 +46,7 @@ fun AudioSettings(service: AirPodsService, sharedPreferences: SharedPreferences)
|
|||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "AUDIO",
|
text = stringResource(R.string.audio).uppercase(),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Light,
|
fontWeight = FontWeight.Light,
|
||||||
@@ -72,7 +74,7 @@ fun AudioSettings(service: AirPodsService, sharedPreferences: SharedPreferences)
|
|||||||
.padding(horizontal = 8.dp, vertical = 10.dp)
|
.padding(horizontal = 8.dp, vertical = 10.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Adaptive Audio",
|
text = stringResource(R.string.adaptive_audio),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
|
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
@@ -82,7 +84,7 @@ fun AudioSettings(service: AirPodsService, sharedPreferences: SharedPreferences)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise.",
|
text = stringResource(R.string.adaptive_audio_description),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 8.dp, top = 2.dp)
|
.padding(bottom = 8.dp, top = 2.dp)
|
||||||
.padding(end = 2.dp, start = 2.dp)
|
.padding(end = 2.dp, start = 2.dp)
|
||||||
@@ -102,4 +104,4 @@ fun AudioSettings(service: AirPodsService, sharedPreferences: SharedPreferences)
|
|||||||
@Composable
|
@Composable
|
||||||
fun AudioSettingsPreview() {
|
fun AudioSettingsPreview() {
|
||||||
AudioSettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
AudioSettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import androidx.compose.ui.draw.scale
|
|||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.imageResource
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import me.kavishdevar.aln.R
|
import me.kavishdevar.aln.R
|
||||||
@@ -110,7 +111,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
) {
|
) {
|
||||||
Image (
|
Image (
|
||||||
bitmap = ImageBitmap.imageResource(R.drawable.pro_2_buds),
|
bitmap = ImageBitmap.imageResource(R.drawable.pro_2_buds),
|
||||||
contentDescription = "Buds",
|
contentDescription = stringResource(R.string.buds),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.scale(0.80f)
|
.scale(0.80f)
|
||||||
@@ -163,7 +164,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
|
|
||||||
Image(
|
Image(
|
||||||
bitmap = ImageBitmap.imageResource(R.drawable.pro_2_case),
|
bitmap = ImageBitmap.imageResource(R.drawable.pro_2_case),
|
||||||
contentDescription = "Case",
|
contentDescription = stringResource(R.string.case_alt),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.scale(1.25f)
|
.scale(1.25f)
|
||||||
@@ -181,4 +182,4 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun BatteryViewPreview() {
|
fun BatteryViewPreview() {
|
||||||
BatteryView(AirPodsService(), preview = true)
|
BatteryView(AirPodsService(), preview = true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,20 @@
|
|||||||
package me.kavishdevar.aln.composables
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -39,15 +45,18 @@ import androidx.compose.ui.focus.onFocusChanged
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StyledTextField(
|
fun NameField(
|
||||||
name: String,
|
name: String,
|
||||||
value: String,
|
value: String,
|
||||||
onValueChange: (String) -> Unit
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -61,53 +70,72 @@ fun StyledTextField(
|
|||||||
Color.Transparent
|
Color.Transparent
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Box (
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.clickable(
|
||||||
.height(55.dp)
|
onClick = {
|
||||||
.background(
|
navController.navigate("rename")
|
||||||
backgroundColor,
|
|
||||||
RoundedCornerShape(14.dp)
|
|
||||||
)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = name,
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = textColor
|
|
||||||
)
|
|
||||||
)
|
|
||||||
BasicTextField(
|
|
||||||
value = value,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
textStyle = TextStyle(
|
|
||||||
color = textColor,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
),
|
|
||||||
singleLine = true,
|
|
||||||
cursorBrush = SolidColor(cursorColor), // Dynamic cursor color based on focus
|
|
||||||
decorationBox = { innerTextField ->
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.End
|
|
||||||
) {
|
|
||||||
innerTextField()
|
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 8.dp)
|
.height(55.dp)
|
||||||
.onFocusChanged { focusState ->
|
.background(
|
||||||
isFocused = focusState.isFocused // Update focus state
|
backgroundColor,
|
||||||
|
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
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun StyledTextFieldPreview() {
|
fun StyledTextFieldPreview() {
|
||||||
StyledTextField(name = "Name", value = "AirPods Pro", onValueChange = {})
|
NameField(name = "Name", value = "AirPods Pro", rememberNavController())
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.VerticalDivider
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -49,6 +48,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.imageResource
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -81,6 +81,7 @@ fun NoiseControlSettings(service: AirPodsService) {
|
|||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDarkTheme = isSystemInDarkTheme()
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFE3E3E8)
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFE3E3E8)
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
@@ -151,12 +152,12 @@ fun NoiseControlSettings(service: AirPodsService) {
|
|||||||
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter)
|
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(// all caps
|
||||||
text = "NOISE CONTROL",
|
text = stringResource(R.string.noise_control).uppercase(),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Light,
|
fontWeight = FontWeight.Light,
|
||||||
color = textColor.copy(alpha = 0.6f)
|
color = textColor.copy(alpha = 0.6f),
|
||||||
),
|
),
|
||||||
modifier = Modifier.padding(8.dp, bottom = 2.dp)
|
modifier = Modifier.padding(8.dp, bottom = 2.dp)
|
||||||
)
|
)
|
||||||
@@ -238,7 +239,7 @@ fun NoiseControlSettings(service: AirPodsService) {
|
|||||||
) {
|
) {
|
||||||
if (offListeningMode.value) {
|
if (offListeningMode.value) {
|
||||||
Text(
|
Text(
|
||||||
text = "Off",
|
text = stringResource(R.string.off),
|
||||||
style = TextStyle(fontSize = 12.sp, color = textColor),
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -246,21 +247,21 @@ fun NoiseControlSettings(service: AirPodsService) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = "Transparency",
|
text = stringResource(R.string.transparency),
|
||||||
style = TextStyle(fontSize = 12.sp, color = textColor),
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Adaptive",
|
text = stringResource(R.string.adaptive),
|
||||||
style = TextStyle(fontSize = 12.sp, color = textColor),
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Noise Cancellation",
|
text = stringResource(R.string.noise_cancellation),
|
||||||
style = TextStyle(fontSize = 12.sp, color = textColor),
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
@@ -56,7 +57,7 @@ fun PressAndHoldSettings(navController: NavController) {
|
|||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "PRESS AND HOLD AIRPODS",
|
text = stringResource(R.string.press_and_hold_airpods).uppercase(),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Light,
|
fontWeight = FontWeight.Light,
|
||||||
@@ -95,7 +96,7 @@ fun PressAndHoldSettings(navController: NavController) {
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Left",
|
text = stringResource(R.string.left),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
color = textColor,
|
color = textColor,
|
||||||
@@ -105,7 +106,7 @@ fun PressAndHoldSettings(navController: NavController) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
// TODO: Implement voice assistant on long press; for now, it's noise control
|
// TODO: Implement voice assistant on long press; for now, it's noise control
|
||||||
text = "Noise Control",
|
text = stringResource(R.string.noise_control),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
color = textColor.copy(alpha = 0.6f),
|
color = textColor.copy(alpha = 0.6f),
|
||||||
@@ -152,7 +153,7 @@ fun PressAndHoldSettings(navController: NavController) {
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Right",
|
text = stringResource(R.string.right),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
color = textColor,
|
color = textColor,
|
||||||
@@ -162,7 +163,7 @@ fun PressAndHoldSettings(navController: NavController) {
|
|||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
// TODO: Implement voice assistant on long press; for now, it's noise control
|
// TODO: Implement voice assistant on long press; for now, it's noise control
|
||||||
text = "Noise Control",
|
text = stringResource(R.string.noise_control),
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
color = textColor.copy(alpha = 0.6f),
|
color = textColor.copy(alpha = 0.6f),
|
||||||
@@ -189,4 +190,4 @@ fun PressAndHoldSettings(navController: NavController) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun PressAndHoldSettingsPreview() {
|
fun PressAndHoldSettingsPreview() {
|
||||||
PressAndHoldSettings(navController = NavController(LocalContext.current))
|
PressAndHoldSettings(navController = NavController(LocalContext.current))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,14 +42,23 @@ import androidx.compose.ui.unit.dp
|
|||||||
@Composable
|
@Composable
|
||||||
fun StyledSwitch(
|
fun StyledSwitch(
|
||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
enabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val isDarkTheme = isSystemInDarkTheme()
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
|
||||||
val thumbColor = Color.White
|
val thumbColor = Color.White
|
||||||
val trackColor = if (checked) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6)
|
val trackColor = if (enabled) (
|
||||||
|
if (isDarkTheme) {
|
||||||
|
if (checked) Color(0xFF34C759) else Color(0xFF5B5B5E)
|
||||||
|
} else {
|
||||||
|
if (checked) Color(0xFF34C759) else Color(0xFFD1D1D6)
|
||||||
|
}
|
||||||
|
) else {
|
||||||
|
if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Animate the horizontal offset of the thumb
|
|
||||||
val thumbOffsetX by animateDpAsState(targetValue = if (checked) 20.dp else 0.dp, label = "Test")
|
val thumbOffsetX by animateDpAsState(targetValue = if (checked) 20.dp else 0.dp, label = "Test")
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
@@ -63,11 +72,11 @@ fun StyledSwitch(
|
|||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset(x = thumbOffsetX) // Animate the offset for smooth transition
|
.offset(x = thumbOffsetX)
|
||||||
.size(27.dp)
|
.size(27.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(thumbColor) // Dynamic thumb color
|
.background(thumbColor)
|
||||||
.clickable { onCheckedChange(!checked) } // Make the switch clickable
|
.clickable { if (enabled) onCheckedChange(!checked) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.content.Context.MODE_PRIVATE
|
import android.content.Context.MODE_PRIVATE
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -33,7 +34,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -43,6 +44,7 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
@@ -55,6 +57,7 @@ import androidx.compose.ui.draw.drawBehind
|
|||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
@@ -76,12 +79,11 @@ import me.kavishdevar.aln.composables.AccessibilitySettings
|
|||||||
import me.kavishdevar.aln.composables.AudioSettings
|
import me.kavishdevar.aln.composables.AudioSettings
|
||||||
import me.kavishdevar.aln.composables.BatteryView
|
import me.kavishdevar.aln.composables.BatteryView
|
||||||
import me.kavishdevar.aln.composables.IndependentToggle
|
import me.kavishdevar.aln.composables.IndependentToggle
|
||||||
|
import me.kavishdevar.aln.composables.NameField
|
||||||
import me.kavishdevar.aln.composables.NavigationButton
|
import me.kavishdevar.aln.composables.NavigationButton
|
||||||
import me.kavishdevar.aln.composables.NoiseControlSettings
|
import me.kavishdevar.aln.composables.NoiseControlSettings
|
||||||
import me.kavishdevar.aln.composables.PressAndHoldSettings
|
import me.kavishdevar.aln.composables.PressAndHoldSettings
|
||||||
import me.kavishdevar.aln.composables.StyledTextField
|
|
||||||
import me.kavishdevar.aln.services.AirPodsService
|
import me.kavishdevar.aln.services.AirPodsService
|
||||||
import me.kavishdevar.aln.services.ServiceManager
|
|
||||||
import me.kavishdevar.aln.ui.theme.ALNTheme
|
import me.kavishdevar.aln.ui.theme.ALNTheme
|
||||||
import me.kavishdevar.aln.utils.AirPodsNotifications
|
import me.kavishdevar.aln.utils.AirPodsNotifications
|
||||||
|
|
||||||
@@ -99,6 +101,23 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nameChangeListener = remember {
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
|
if (key == "name") {
|
||||||
|
deviceName = TextFieldValue(sharedPreferences.getString("name", "AirPods Pro").toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener(nameChangeListener)
|
||||||
|
onDispose {
|
||||||
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(nameChangeListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val verticalScrollState = rememberScrollState()
|
val verticalScrollState = rememberScrollState()
|
||||||
val hazeState = remember { HazeState() }
|
val hazeState = remember { HazeState() }
|
||||||
|
|
||||||
@@ -150,10 +169,9 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
|||||||
containerColor = Color.Transparent
|
containerColor = Color.Transparent
|
||||||
),
|
),
|
||||||
actions = {
|
actions = {
|
||||||
val context = LocalContext.current
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
ServiceManager.restartService(context)
|
navController.navigate("app_settings")
|
||||||
},
|
},
|
||||||
colors = IconButtonDefaults.iconButtonColors(
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
@@ -161,7 +179,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Refresh,
|
imageVector = Icons.Default.Settings,
|
||||||
contentDescription = "Settings",
|
contentDescription = "Settings",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -199,14 +217,10 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
StyledTextField(
|
NameField(
|
||||||
name = "Name",
|
name = stringResource(R.string.name),
|
||||||
value = deviceName.text,
|
value = deviceName.text,
|
||||||
onValueChange = {
|
navController = navController
|
||||||
deviceName = TextFieldValue(it)
|
|
||||||
sharedPreferences.edit().putString("name", it).apply()
|
|
||||||
service.setName(it)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Kavish Devar
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package me.kavishdevar.aln.screens
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||||
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
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.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
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
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
import me.kavishdevar.aln.composables.StyledSwitch
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AppSettingsScreen(navController: NavController) {
|
||||||
|
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val name = remember { mutableStateOf(sharedPreferences.getString("name", "") ?: "") }
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.app_settings),
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
|
modifier = Modifier.scale(1.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = name.value,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
|
||||||
|
else Color(0xFFF2F2F7),
|
||||||
|
) { paddingValues ->
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(275.sp.value.dp)
|
||||||
|
.background(
|
||||||
|
backgroundColor,
|
||||||
|
RoundedCornerShape(14.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
val sliderValue = remember { mutableFloatStateOf(0f) }
|
||||||
|
LaunchedEffect(sliderValue) {
|
||||||
|
if (sharedPreferences.contains("conversational_awareness_volume")) {
|
||||||
|
sliderValue.floatValue = sharedPreferences.getInt("conversational_awareness_volume", 0).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(sliderValue.floatValue) {
|
||||||
|
sharedPreferences.edit().putInt("conversational_awareness_volume", sliderValue.floatValue.toInt()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFFD9D9D9)
|
||||||
|
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
|
||||||
|
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.conversational_awareness_customization),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 20.sp,
|
||||||
|
color = textColor
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 12.dp, bottom = 4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var conversationalAwarenessPauseMusicEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("conversational_awareness_pause_music", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateConversationalAwarenessPauseMusic(enabled: Boolean) {
|
||||||
|
conversationalAwarenessPauseMusicEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("conversational_awareness_pause_music", enabled).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativeConversationalAwarenessVolumeEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("relative_conversational_awareness_volume", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) {
|
||||||
|
relativeConversationalAwarenessVolumeEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("relative_conversational_awareness_volume", enabled).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(85.sp.value.dp)
|
||||||
|
.background(
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
color = Color.Transparent
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
updateConversationalAwarenessPauseMusic(!conversationalAwarenessPauseMusicEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.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()
|
||||||
|
.height(85.sp.value.dp)
|
||||||
|
.background(
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
color = Color.Transparent
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
updateRelativeConversationalAwarenessVolume(!relativeConversationalAwarenessVolumeEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5)
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value = sliderValue.floatValue,
|
||||||
|
onValueChange = {
|
||||||
|
sliderValue.floatValue = it
|
||||||
|
},
|
||||||
|
valueRange = 10f..85f,
|
||||||
|
onValueChangeFinished = {
|
||||||
|
sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat()
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(36.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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "10%",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = labelTextColor
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(start = 4.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "85%",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = labelTextColor
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AppSettingsScreenPreview() {
|
||||||
|
AppSettingsScreen(navController = NavController(LocalContext.current))
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* AirPods like Normal (ALN) - Bringing Apple-only features to Linux and Android for seamless AirPods functionality!
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Kavish Devar
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package me.kavishdevar.aln.screens
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||||
|
import androidx.compose.material.icons.filled.Clear
|
||||||
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
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.scale
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
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
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
import me.kavishdevar.aln.services.ServiceManager
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun RenameScreen(navController: NavController) {
|
||||||
|
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val name = remember { mutableStateOf(sharedPreferences.getString("name", "") ?: "") }
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.name),
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
|
modifier = Modifier.scale(1.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = name.value,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
|
||||||
|
else Color(0xFFF2F2F7),
|
||||||
|
) { paddingValues ->
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues = paddingValues)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
) {
|
||||||
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
val cursorColor = if (isFocused) { // Show cursor only when focused
|
||||||
|
if (isDarkTheme) Color.White else Color.Black
|
||||||
|
} else {
|
||||||
|
Color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(55.dp)
|
||||||
|
.background(
|
||||||
|
backgroundColor,
|
||||||
|
RoundedCornerShape(14.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = name.value,
|
||||||
|
onValueChange = {
|
||||||
|
name.value = it
|
||||||
|
sharedPreferences.edit().putString("name", it).apply()
|
||||||
|
ServiceManager.getService()?.setName(it)
|
||||||
|
},
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
cursorBrush = SolidColor(cursorColor),
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
name.value = ""
|
||||||
|
sharedPreferences.edit().putString("name", "").apply()
|
||||||
|
ServiceManager.getService()?.setName("")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Clear,
|
||||||
|
contentDescription = "Clear",
|
||||||
|
tint = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RenameScreenPreview() {
|
||||||
|
RenameScreen(navController = NavController(LocalContext.current))
|
||||||
|
}
|
||||||
@@ -36,7 +36,9 @@ import android.content.IntentFilter
|
|||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.Looper
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
@@ -473,6 +475,15 @@ class AirPodsService: Service() {
|
|||||||
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
delay(200)
|
delay(200)
|
||||||
|
// just in case this doesn't work, send all three after 5 seconds again
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
it.outputStream.write(Enums.HANDSHAKE.value)
|
||||||
|
it.outputStream.flush()
|
||||||
|
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
||||||
|
it.outputStream.flush()
|
||||||
|
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
||||||
|
it.outputStream.flush()
|
||||||
|
}, 5000)
|
||||||
sendBroadcast(
|
sendBroadcast(
|
||||||
Intent(AirPodsNotifications.Companion.AIRPODS_CONNECTED)
|
Intent(AirPodsNotifications.Companion.AIRPODS_CONNECTED)
|
||||||
.putExtra("device", device)
|
.putExtra("device", device)
|
||||||
@@ -482,7 +493,7 @@ class AirPodsService: Service() {
|
|||||||
socket.let {
|
socket.let {
|
||||||
val audioManager =
|
val audioManager =
|
||||||
this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
MediaController.initialize(audioManager)
|
MediaController.initialize(audioManager, this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE))
|
||||||
val buffer = ByteArray(1024)
|
val buffer = ByteArray(1024)
|
||||||
val bytesRead = it.inputStream.read(buffer)
|
val bytesRead = it.inputStream.read(buffer)
|
||||||
var data: ByteArray = byteArrayOf()
|
var data: ByteArray = byteArrayOf()
|
||||||
@@ -889,6 +900,7 @@ class AirPodsService: Service() {
|
|||||||
socket.outputStream?.write(bytes)
|
socket.outputStream?.write(bytes)
|
||||||
socket.outputStream?.flush()
|
socket.outputStream?.flush()
|
||||||
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
val hex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||||
|
updateNotificationContent(true, name, batteryNotification.getBattery())
|
||||||
Log.d("AirPodsService", "setName: $name, sent packet: $hex")
|
Log.d("AirPodsService", "setName: $name, sent packet: $hex")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package me.kavishdevar.aln.utils
|
package me.kavishdevar.aln.utils
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.AudioPlaybackConfiguration
|
import android.media.AudioPlaybackConfiguration
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@@ -30,10 +31,35 @@ object MediaController {
|
|||||||
private lateinit var audioManager: AudioManager
|
private lateinit var audioManager: AudioManager
|
||||||
var iPausedTheMedia = false
|
var iPausedTheMedia = false
|
||||||
var userPlayedTheMedia = false
|
var userPlayedTheMedia = false
|
||||||
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
fun initialize(audioManager: AudioManager) {
|
private var relativeVolume: Boolean = false
|
||||||
|
private var conversationalAwarenessVolume: Int = 1/12
|
||||||
|
private var conversationalAwarenessPauseMusic: Boolean = false
|
||||||
|
|
||||||
|
fun initialize(audioManager: AudioManager, sharedPreferences: SharedPreferences) {
|
||||||
this.audioManager = audioManager
|
this.audioManager = audioManager
|
||||||
|
this.sharedPreferences = sharedPreferences
|
||||||
|
|
||||||
|
relativeVolume = sharedPreferences.getBoolean("relative_conversational_awareness_volume", false)
|
||||||
|
conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 100/12)
|
||||||
|
conversationalAwarenessPauseMusic = sharedPreferences.getBoolean("conversational_awareness_pause_music", false)
|
||||||
|
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener { _, key ->
|
||||||
|
when (key) {
|
||||||
|
"relative_conversational_awareness_volume" -> {
|
||||||
|
relativeVolume = sharedPreferences.getBoolean("relative_conversational_awareness_volume", false)
|
||||||
|
}
|
||||||
|
"conversational_awareness_volume" -> {
|
||||||
|
conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 100/12)
|
||||||
|
}
|
||||||
|
"conversational_awareness_pause_music" -> {
|
||||||
|
conversationalAwarenessPauseMusic = sharedPreferences.getBoolean("conversational_awareness_pause_music", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
audioManager.registerAudioPlaybackCallback(cb, null)
|
audioManager.registerAudioPlaybackCallback(cb, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,15 +72,15 @@ object MediaController {
|
|||||||
handler.postDelayed({
|
handler.postDelayed({
|
||||||
iPausedTheMedia = !audioManager.isMusicActive
|
iPausedTheMedia = !audioManager.isMusicActive
|
||||||
userPlayedTheMedia = audioManager.isMusicActive
|
userPlayedTheMedia = audioManager.isMusicActive
|
||||||
}, 7) // i have no idea why, but android sends a pause event a hundred times after the user does something.
|
}, 7) // i have no idea why android sends an event a hundred times after the user does something.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun sendPause() {
|
fun sendPause(force: Boolean = false) {
|
||||||
Log.d("MediaController", "Sending pause with iPausedTheMedia: $iPausedTheMedia, userPlayedTheMedia: $userPlayedTheMedia")
|
Log.d("MediaController", "Sending pause with iPausedTheMedia: $iPausedTheMedia, userPlayedTheMedia: $userPlayedTheMedia")
|
||||||
if (audioManager.isMusicActive && !userPlayedTheMedia) {
|
if ((audioManager.isMusicActive && !userPlayedTheMedia) || force) {
|
||||||
iPausedTheMedia = true
|
iPausedTheMedia = true
|
||||||
userPlayedTheMedia = false
|
userPlayedTheMedia = false
|
||||||
audioManager.dispatchMediaKeyEvent(
|
audioManager.dispatchMediaKeyEvent(
|
||||||
@@ -99,8 +125,11 @@ object MediaController {
|
|||||||
if (initialVolume == null) {
|
if (initialVolume == null) {
|
||||||
initialVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
initialVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
|
||||||
Log.d("MediaController", "Initial Volume Set: $initialVolume")
|
Log.d("MediaController", "Initial Volume Set: $initialVolume")
|
||||||
val targetVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * 1 / 12
|
val targetVolume = if (relativeVolume) initialVolume!! * conversationalAwarenessVolume * 1/100 else if ( initialVolume!! > audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * conversationalAwarenessVolume * 1/100) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * conversationalAwarenessVolume * 1/100 else initialVolume!!
|
||||||
smoothVolumeTransition(initialVolume!!, targetVolume)
|
smoothVolumeTransition(initialVolume!!, targetVolume.toInt())
|
||||||
|
if (conversationalAwarenessPauseMusic) {
|
||||||
|
sendPause(force = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.d("MediaController", "Initial Volume: $initialVolume")
|
Log.d("MediaController", "Initial Volume: $initialVolume")
|
||||||
}
|
}
|
||||||
@@ -110,13 +139,16 @@ object MediaController {
|
|||||||
Log.d("MediaController", "Stopping speaking, initialVolume: $initialVolume")
|
Log.d("MediaController", "Stopping speaking, initialVolume: $initialVolume")
|
||||||
if (initialVolume != null) {
|
if (initialVolume != null) {
|
||||||
smoothVolumeTransition(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC), initialVolume!!)
|
smoothVolumeTransition(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC), initialVolume!!)
|
||||||
|
if (conversationalAwarenessPauseMusic) {
|
||||||
|
sendPlay()
|
||||||
|
}
|
||||||
initialVolume = null
|
initialVolume = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun smoothVolumeTransition(fromVolume: Int, toVolume: Int) {
|
private fun smoothVolumeTransition(fromVolume: Int, toVolume: Int) {
|
||||||
val step = if (fromVolume < toVolume) 1 else -1
|
val step = if (fromVolume < toVolume) 1 else -1
|
||||||
val delay = 50L // 50 milliseconds delay between each step
|
val delay = 50L
|
||||||
var currentVolume = fromVolume
|
var currentVolume = fromVolume
|
||||||
|
|
||||||
handler.post(object : Runnable {
|
handler.post(object : Runnable {
|
||||||
|
|||||||
@@ -1,5 +1,42 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">ALN</string>
|
<string name="app_name" translatable="false">ALN</string>
|
||||||
<string name="title_activity_custom_device">GATT Testing</string>
|
<string name="title_activity_custom_device" translatable="false">GATT Testing</string>
|
||||||
<string name="app_widget_description">See your AirPods battery status right from your home screen!</string>
|
<string name="app_widget_description">See your AirPods battery status right from your home screen!</string>
|
||||||
</resources>
|
<string name="accessibility">Accessibility</string>
|
||||||
|
<string name="tone_volume">Tone Volume</string>
|
||||||
|
<string name="audio">Audio</string>
|
||||||
|
<string name="adaptive_audio">Adaptive Audio</string>
|
||||||
|
<string name="adaptive_audio_description">Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise.</string>
|
||||||
|
<string name="buds">Buds</string>
|
||||||
|
<string name="case_alt">Case</string>
|
||||||
|
<string name="test">Test</string>
|
||||||
|
<string name="name">Name</string>
|
||||||
|
<string name="noise_control">Noise Control</string>
|
||||||
|
<string name="off">Off</string>
|
||||||
|
<string name="transparency">Transparency</string>
|
||||||
|
<string name="adaptive">Adaptive</string>
|
||||||
|
<string name="noise_cancellation">Noise Cancellation</string>
|
||||||
|
<string name="press_and_hold_airpods">Press and Hold AirPods</string>
|
||||||
|
<string name="left">Left</string>
|
||||||
|
<string name="right">Right</string>
|
||||||
|
<string name="adjusts_volume">Adjusts the volume of media in response to your environment</string>
|
||||||
|
<string name="conversational_awareness">Conversational Awareness</string>
|
||||||
|
<string name="conversational_awareness_description">Lowers media volume and reduces background noise when you start speaking to other people.</string>
|
||||||
|
<string name="personalized_volume">Personalized Volume</string>
|
||||||
|
<string name="personalized_volume_description">Adjusts the volume of media in response to your environment.</string>
|
||||||
|
<string name="less_noise">Less Noise</string>
|
||||||
|
<string name="more_noise">More Noise</string>
|
||||||
|
<string name="noise_cancellation_single_airpod">Noise Cancellation with Single AirPod</string>
|
||||||
|
<string name="noise_cancellation_single_airpod_description">Allow AirPods to be put in noise cancellation mode when only one AirPods is in your ear.</string>
|
||||||
|
<string name="volume_control">Volume Control</string>
|
||||||
|
<string name="volume_control_description">Adjust the volume by swiping up or down on the sensor located on the AirPods Pro stem.</string>
|
||||||
|
<string name="airpods_not_connected">AirPods not connected</string>
|
||||||
|
<string name="airpods_not_connected_description">Please connect your AirPods to access settings. If you\'re stuck here, then try reopening the app again after closing it from the recents.\n(DO NOT KILL THE APP!)</string>
|
||||||
|
<string name="back">Back</string>
|
||||||
|
<string name="app_settings">App Settings</string>
|
||||||
|
<string name="conversational_awareness_customization">Conversational Awareness</string>
|
||||||
|
<string name="relative_conversational_awareness_volume">Relative volume</string>
|
||||||
|
<string name="relative_conversational_awareness_volume_description">Reduces to a percentage of the current volume instead of the maximum volume.</string>
|
||||||
|
<string name="conversational_awareness_pause_music">Pause Music</string>
|
||||||
|
<string name="conversational_awareness_pause_music_description">When you start speaking, music will be paused.</string>
|
||||||
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user