mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-10 19:52:24 +00:00
android: add long press settings to cycle between anc modes
This commit is contained in:
@@ -38,7 +38,7 @@ Here are a few screenshots!
|
|||||||
|-------------------|-------------------|-------------------|
|
|-------------------|-------------------|-------------------|
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|  |  |  |
|
|  |  |  |
|
||||||
|
|  | | |
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
Currently, there's a [bug in the Android Bluetooth stack](https://issuetracker.google.com/issues/371713238) that prevents the app from working (upvote the issue - click the '+1' icon on the top right corner of IssueTracker). This repository provides a workaround for the bug, specifically tested on Android 14 and Android 15 (stock versions).
|
Currently, there's a [bug in the Android Bluetooth stack](https://issuetracker.google.com/issues/371713238) that prevents the app from working (upvote the issue - click the '+1' icon on the top right corner of IssueTracker). This repository provides a workaround for the bug, specifically tested on Android 14 and Android 15 (stock versions).
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ object ServiceManager {
|
|||||||
fun setService(service: AirPodsService?) {
|
fun setService(service: AirPodsService?) {
|
||||||
this.service = service
|
this.service = service
|
||||||
}
|
}
|
||||||
|
@Synchronized
|
||||||
|
fun restartService(context: Context) {
|
||||||
|
service?.stopSelf()
|
||||||
|
context.startService(Intent(context, AirPodsService::class.java))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@@ -708,6 +713,146 @@ class AirPodsService: Service() {
|
|||||||
socket.outputStream?.write(bytes)
|
socket.outputStream?.write(bytes)
|
||||||
socket.outputStream?.flush()
|
socket.outputStream?.flush()
|
||||||
}
|
}
|
||||||
|
fun findChangedIndex(oldArray: BooleanArray, newArray: BooleanArray): Int {
|
||||||
|
for (i in oldArray.indices) {
|
||||||
|
if (oldArray[i] != newArray[i]) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("No element has changed")
|
||||||
|
}
|
||||||
|
fun updateLongPress(oldLongPressArray: BooleanArray, newLongPressArray: BooleanArray, offListeningMode: Boolean) {
|
||||||
|
if (oldLongPressArray.contentEquals(newLongPressArray)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val oldOffEnabled = oldLongPressArray[0]
|
||||||
|
val oldAncEnabled = oldLongPressArray[1]
|
||||||
|
val oldTransparencyEnabled = oldLongPressArray[2]
|
||||||
|
val oldAdaptiveEnabled = oldLongPressArray[3]
|
||||||
|
|
||||||
|
val newOffEnabled = newLongPressArray[0]
|
||||||
|
val newAncEnabled = newLongPressArray[1]
|
||||||
|
val newTransparencyEnabled = newLongPressArray[2]
|
||||||
|
val newAdaptiveEnabled = newLongPressArray[3]
|
||||||
|
|
||||||
|
val changedIndex = findChangedIndex(oldLongPressArray, newLongPressArray)
|
||||||
|
Log.d("AirPodsService", "changedIndex: $changedIndex")
|
||||||
|
var packet: ByteArray? = null
|
||||||
|
if (offListeningMode) {
|
||||||
|
packet = when (changedIndex) {
|
||||||
|
0 -> {
|
||||||
|
if (newOffEnabled) {
|
||||||
|
when {
|
||||||
|
oldAncEnabled && oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_EVERYTHING.value
|
||||||
|
oldAncEnabled && oldTransparencyEnabled -> LongPressPackets.ENABLE_OFF_FROM_TRANSPARENCY_AND_ANC.value
|
||||||
|
oldAncEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_OFF_FROM_ADAPTIVE_AND_ANC.value
|
||||||
|
oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_OFF_FROM_TRANSPARENCY_AND_ADAPTIVE.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
oldAncEnabled && oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_OFF_FROM_EVERYTHING.value
|
||||||
|
oldAncEnabled && oldTransparencyEnabled -> LongPressPackets.DISABLE_OFF_FROM_TRANSPARENCY_AND_ANC.value
|
||||||
|
oldAncEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_OFF_FROM_ADAPTIVE_AND_ANC.value
|
||||||
|
oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_OFF_FROM_TRANSPARENCY_AND_ADAPTIVE.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
if (newAncEnabled) {
|
||||||
|
when {
|
||||||
|
oldOffEnabled && oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_EVERYTHING.value
|
||||||
|
oldOffEnabled && oldTransparencyEnabled -> LongPressPackets.ENABLE_ANC_FROM_OFF_AND_TRANSPARENCY.value
|
||||||
|
oldOffEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_ANC_FROM_OFF_AND_ADAPTIVE.value
|
||||||
|
oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_OFF_FROM_TRANSPARENCY_AND_ADAPTIVE.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
oldOffEnabled && oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_ANC_FROM_EVERYTHING.value
|
||||||
|
oldOffEnabled && oldTransparencyEnabled -> LongPressPackets.DISABLE_ANC_FROM_OFF_AND_TRANSPARENCY.value
|
||||||
|
oldOffEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_ANC_FROM_OFF_AND_ADAPTIVE.value
|
||||||
|
oldTransparencyEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_OFF_FROM_TRANSPARENCY_AND_ADAPTIVE.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
if (newTransparencyEnabled) {
|
||||||
|
when {
|
||||||
|
oldOffEnabled && oldAncEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_EVERYTHING.value
|
||||||
|
oldOffEnabled && oldAncEnabled -> LongPressPackets.ENABLE_TRANSPARENCY_FROM_OFF_AND_ANC.value
|
||||||
|
oldOffEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_TRANSPARENCY_FROM_OFF_AND_ADAPTIVE.value
|
||||||
|
oldAncEnabled && oldAdaptiveEnabled -> LongPressPackets.ENABLE_TRANSPARENCY_FROM_ADAPTIVE_AND_ANC.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
oldOffEnabled && oldAncEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_TRANSPARENCY_FROM_EVERYTHING.value
|
||||||
|
oldOffEnabled && oldAncEnabled -> LongPressPackets.DISABLE_TRANSPARENCY_FROM_OFF_AND_ANC.value
|
||||||
|
oldOffEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_TRANSPARENCY_FROM_OFF_AND_ADAPTIVE.value
|
||||||
|
oldAncEnabled && oldAdaptiveEnabled -> LongPressPackets.DISABLE_TRANSPARENCY_FROM_ADAPTIVE_AND_ANC.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
3 -> {
|
||||||
|
if (newAdaptiveEnabled) {
|
||||||
|
when {
|
||||||
|
oldOffEnabled && oldAncEnabled && oldTransparencyEnabled -> LongPressPackets.ENABLE_EVERYTHING.value
|
||||||
|
oldOffEnabled && oldAncEnabled -> LongPressPackets.ENABLE_ADAPTIVE_FROM_OFF_AND_ANC.value
|
||||||
|
oldOffEnabled && oldTransparencyEnabled -> LongPressPackets.ENABLE_ADAPTIVE_FROM_OFF_AND_TRANSPARENCY.value
|
||||||
|
oldAncEnabled && oldTransparencyEnabled -> LongPressPackets.ENABLE_ADAPTIVE_FROM_TRANSPARENCY_AND_ANC.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when {
|
||||||
|
oldOffEnabled && oldAncEnabled && oldTransparencyEnabled -> LongPressPackets.DISABLE_ADAPTIVE_FROM_EVERYTHING.value
|
||||||
|
oldOffEnabled && oldAncEnabled -> LongPressPackets.DISABLE_ADAPTIVE_FROM_OFF_AND_ANC.value
|
||||||
|
oldOffEnabled && oldTransparencyEnabled -> LongPressPackets.DISABLE_ADAPTIVE_FROM_OFF_AND_TRANSPARENCY.value
|
||||||
|
oldAncEnabled && oldTransparencyEnabled -> LongPressPackets.DISABLE_ADAPTIVE_FROM_TRANSPARENCY_AND_ANC.value
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (changedIndex) {
|
||||||
|
1 -> {
|
||||||
|
packet = if (newLongPressArray[1]) {
|
||||||
|
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
||||||
|
} else {
|
||||||
|
LongPressPackets.DISABLE_ANC_OFF_DISABLED.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
packet = if (newLongPressArray[2]) {
|
||||||
|
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
||||||
|
} else {
|
||||||
|
LongPressPackets.DISABLE_TRANSPARENCY_OFF_DISABLED.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
packet = if (newLongPressArray[3]) {
|
||||||
|
LongPressPackets.ENABLE_EVERYTHING_OFF_DISABLED.value
|
||||||
|
} else {
|
||||||
|
LongPressPackets.DISABLE_ADAPTIVE_OFF_DISABLED.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
packet?.let {
|
||||||
|
Log.d("AirPodsService", "Sending packet: ${it.joinToString(" ") { "%02X".format(it) }}")
|
||||||
|
socket.outputStream.write(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d("AirPodsService", "Service stopped is being destroyed for some reason!")
|
Log.d("AirPodsService", "Service stopped is being destroyed for some reason!")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,256 @@
|
|||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.layout.wrapContentWidth
|
||||||
|
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.Checkbox
|
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.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
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.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.imageResource
|
||||||
|
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.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
@Composable()
|
||||||
|
fun RightDivider() {
|
||||||
|
HorizontalDivider(
|
||||||
|
color = Color(0xFF4D4D4D).copy(alpha = 0.4f),
|
||||||
|
thickness = 2.dp,
|
||||||
|
modifier = Modifier.padding(start = 72.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LongPress(navController: NavController) {
|
fun LongPress(navController: NavController, name: String) {
|
||||||
val offChecked = remember { mutableStateOf(false) }
|
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
val ncChecked = remember { mutableStateOf(false) }
|
val offChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_off", false)) }
|
||||||
val transparencyChecked = remember { mutableStateOf(false) }
|
val ncChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_nc", false)) }
|
||||||
val adaptiveChecked = remember { mutableStateOf(false) }
|
val transparencyChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_transparency", false)) }
|
||||||
Column {
|
val adaptiveChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_adaptive", false)) }
|
||||||
Row {
|
Log.d("LongPress", "offChecked: ${offChecked.value}, ncChecked: ${ncChecked.value}, transparencyChecked: ${transparencyChecked.value}, adaptiveChecked: ${adaptiveChecked.value}")
|
||||||
Text("Off")
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
Checkbox(
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
checked = offChecked.value,
|
|
||||||
onCheckedChange = { offChecked.value = it },
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(24.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
|
contentDescription = "Back",
|
||||||
|
tint = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5),
|
||||||
|
modifier = Modifier.scale(1.5f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
sharedPreferences.getString("name", "AirPods")!!,
|
||||||
|
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 (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF000000)
|
||||||
|
else Color(0xFFF2F2F7),
|
||||||
|
) { paddingValues ->
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues = paddingValues)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "NOISE CONTROL",
|
||||||
|
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 = 4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor, RoundedCornerShape(14.dp)),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
|
||||||
|
LongPressElement("Off", offChecked, "long_press_off", offListeningMode, R.drawable.noise_cancellation)
|
||||||
|
if (offListeningMode) RightDivider()
|
||||||
|
LongPressElement("Transparency", transparencyChecked, "long_press_transparency", resourceId = R.drawable.transparency)
|
||||||
|
RightDivider()
|
||||||
|
LongPressElement("Adaptive", adaptiveChecked, "long_press_adaptive", resourceId = R.drawable.adaptive)
|
||||||
|
RightDivider()
|
||||||
|
LongPressElement("Noise Cancellation", ncChecked, "long_press_nc", resourceId = R.drawable.noise_cancellation)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
"Press and hold the stem to cycle between the selected noise control modes.",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
color = textColor.copy(alpha = 0.6f),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp, top = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row {
|
}
|
||||||
Text("Noise Cancellation")
|
}
|
||||||
Checkbox(
|
|
||||||
checked = ncChecked.value,
|
@Composable
|
||||||
onCheckedChange = { ncChecked.value = it },
|
fun LongPressElement (name: String, checked: MutableState<Boolean>, id: String, enabled: Boolean = true, resourceId: Int) {
|
||||||
)
|
val sharedPreferences =
|
||||||
|
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
|
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
|
||||||
|
val darkMode = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
val textColor = if (darkMode) Color.White else Color.Black
|
||||||
|
val desc = when (name) {
|
||||||
|
"Off" -> "Turns off noise management"
|
||||||
|
"Noise Cancellation" -> "Blocks out external sounds"
|
||||||
|
"Transparency" -> "Lets in external sounds"
|
||||||
|
"Adaptive" -> "Dynamically adjust external noise"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
fun valueChanged(value: Boolean = !checked.value) {
|
||||||
|
val originalLongPressArray = booleanArrayOf(
|
||||||
|
sharedPreferences.getBoolean("long_press_off", false),
|
||||||
|
sharedPreferences.getBoolean("long_press_nc", false),
|
||||||
|
sharedPreferences.getBoolean("long_press_transparency", false),
|
||||||
|
sharedPreferences.getBoolean("long_press_adaptive", false)
|
||||||
|
)
|
||||||
|
if (!value && originalLongPressArray.count { it } <= 2) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Row {
|
checked.value = value
|
||||||
Text("Transparency")
|
with(sharedPreferences.edit()) {
|
||||||
Checkbox(
|
putBoolean(id, checked.value)
|
||||||
checked = transparencyChecked.value,
|
apply()
|
||||||
onCheckedChange = { transparencyChecked.value = it },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Row {
|
val newLongPressArray = booleanArrayOf(
|
||||||
Text("Off")
|
sharedPreferences.getBoolean("long_press_off", false),
|
||||||
|
sharedPreferences.getBoolean("long_press_nc", false),
|
||||||
|
sharedPreferences.getBoolean("long_press_transparency", false),
|
||||||
|
sharedPreferences.getBoolean("long_press_adaptive", false)
|
||||||
|
)
|
||||||
|
ServiceManager.getService()
|
||||||
|
?.updateLongPress(originalLongPressArray, newLongPressArray, offListeningMode)
|
||||||
|
}
|
||||||
|
if (!enabled) {
|
||||||
|
valueChanged(false)
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(72.dp)
|
||||||
|
.clickable(
|
||||||
|
onClick = { valueChanged() }
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 0.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
bitmap = ImageBitmap.imageResource(resourceId),
|
||||||
|
contentDescription = "Icon",
|
||||||
|
tint = Color(0xFF007AFF),
|
||||||
|
modifier = Modifier
|
||||||
|
.height(48.dp)
|
||||||
|
.wrapContentWidth()
|
||||||
|
)
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(vertical = 2.dp)
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
)
|
||||||
|
Text (
|
||||||
|
desc,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = textColor.copy(alpha = 0.6f),
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
)
|
||||||
|
}
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked = adaptiveChecked.value,
|
checked = checked.value,
|
||||||
onCheckedChange = { adaptiveChecked.value = it },
|
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
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.height(24.dp)
|
||||||
|
.scale(1.5f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ fun Main() {
|
|||||||
composable("debug") {
|
composable("debug") {
|
||||||
DebugScreen(navController = navController)
|
DebugScreen(navController = navController)
|
||||||
}
|
}
|
||||||
composable("long_press") {
|
composable("long_press/{bud}") { navBackStackEntry ->
|
||||||
LongPress(navController = navController)
|
LongPress(navController = navController, name = navBackStackEntry.arguments?.getString("bud")!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,4 +205,49 @@ class Capabilities {
|
|||||||
OFF(byteArrayOf(0x02)),
|
OFF(byteArrayOf(0x02)),
|
||||||
ON(byteArrayOf(0x01));
|
ON(byteArrayOf(0x01));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class LongPressPackets(val value: ByteArray) {
|
||||||
|
ENABLE_EVERYTHING(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0F, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
DISABLE_OFF_FROM_EVERYTHING(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0e, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_OFF_FROM_TRANSPARENCY_AND_ADAPTIVE(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0c, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_OFF_FROM_TRANSPARENCY_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x06, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_OFF_FROM_ADAPTIVE_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0a, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
ENABLE_OFF_FROM_TRANSPARENCY_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x07, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_OFF_FROM_ADAPTIVE_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0b, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_OFF_FROM_TRANSPARENCY_AND_ADAPTIVE(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0d, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
DISABLE_TRANSPARENCY_FROM_EVERYTHING(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0b, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_TRANSPARENCY_FROM_OFF_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x03, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_TRANSPARENCY_FROM_ADAPTIVE_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0a, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_TRANSPARENCY_FROM_OFF_AND_ADAPTIVE(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x09, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
ENABLE_TRANSPARENCY_FROM_OFF_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x07, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_TRANSPARENCY_FROM_ADAPTIVE_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0e, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_TRANSPARENCY_FROM_OFF_AND_ADAPTIVE(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0d, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
DISABLE_ANC_FROM_EVERYTHING(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0D, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ANC_FROM_OFF_AND_TRANSPARENCY(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x05, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ANC_FROM_ADAPTIVE_AND_TRANSPARENCY(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0c, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ANC_FROM_OFF_AND_ADAPTIVE(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x09, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
ENABLE_ANC_FROM_OFF_AND_TRANSPARENCY(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x07, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_ANC_FROM_ADAPTIVE_AND_TRANSPARENCY(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0e, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_ANC_FROM_OFF_AND_ADAPTIVE(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0b, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
DISABLE_ADAPTIVE_FROM_EVERYTHING(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x07, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ADAPTIVE_FROM_OFF_AND_TRANSPARENCY(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x05, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ADAPTIVE_FROM_TRANSPARENCY_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x06, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ADAPTIVE_FROM_OFF_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x03, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
ENABLE_ADAPTIVE_FROM_OFF_AND_TRANSPARENCY(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0d, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_ADAPTIVE_FROM_TRANSPARENCY_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0e, 0x00, 0x00, 0x00)),
|
||||||
|
ENABLE_ADAPTIVE_FROM_OFF_AND_ANC(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0b, 0x00, 0x00, 0x00)),
|
||||||
|
|
||||||
|
ENABLE_EVERYTHING_OFF_DISABLED(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0E, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_TRANSPARENCY_OFF_DISABLED(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0A, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ADAPTIVE_OFF_DISABLED(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x06, 0x00, 0x00, 0x00)),
|
||||||
|
DISABLE_ANC_OFF_DISABLED(byteArrayOf(0x04, 0x00, 0x04, 0x00, 0x09, 0x00, 0x1A, 0x0C, 0x00, 0x00, 0x00)),
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
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.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BatteryIndicator(batteryPercentage: Int, charging: Boolean = false) {
|
||||||
|
val batteryOutlineColor = Color(0xFFBFBFBF)
|
||||||
|
val batteryFillColor = if (batteryPercentage > 30) Color(0xFF30D158) else Color(0xFFFC3C3C)
|
||||||
|
val batteryTextColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
|
||||||
|
// Battery indicator dimensions
|
||||||
|
val batteryWidth = 40.dp
|
||||||
|
val batteryHeight = 15.dp
|
||||||
|
val batteryCornerRadius = 4.dp
|
||||||
|
val tipWidth = 5.dp
|
||||||
|
val tipHeight = batteryHeight * 0.375f
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp) // Padding between icon and percentage text
|
||||||
|
) {
|
||||||
|
// Battery Icon
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(batteryWidth)
|
||||||
|
.height(batteryHeight)
|
||||||
|
.border(1.dp, batteryOutlineColor, RoundedCornerShape(batteryCornerRadius))
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(2.dp)
|
||||||
|
.width(batteryWidth * (batteryPercentage / 100f))
|
||||||
|
.background(batteryFillColor, RoundedCornerShape(2.dp))
|
||||||
|
)
|
||||||
|
if (charging) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(0.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\uDBC0\uDEE6",
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.padding(0.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(tipWidth)
|
||||||
|
.height(tipHeight)
|
||||||
|
.padding(start = 1.dp)
|
||||||
|
.background(
|
||||||
|
batteryOutlineColor,
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = 0.dp,
|
||||||
|
topEnd = 12.dp,
|
||||||
|
bottomStart = 0.dp,
|
||||||
|
bottomEnd = 12.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "$batteryPercentage%",
|
||||||
|
color = batteryTextColor,
|
||||||
|
style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import me.kavishdevar.aln.AirPodsNotifications
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
import me.kavishdevar.aln.Battery
|
||||||
|
import me.kavishdevar.aln.BatteryComponent
|
||||||
|
import me.kavishdevar.aln.composables.BatteryIndicator
|
||||||
|
import me.kavishdevar.aln.BatteryStatus
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
||||||
|
val batteryStatus = remember { mutableStateOf<List<Battery>>(listOf()) }
|
||||||
|
@Suppress("DEPRECATION") val batteryReceiver = remember {
|
||||||
|
object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
batteryStatus.value =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
intent.getParcelableArrayListExtra("data", Battery::class.java)
|
||||||
|
} else {
|
||||||
|
intent.getParcelableArrayListExtra("data")
|
||||||
|
}?.toList() ?: listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(context) {
|
||||||
|
val batteryIntentFilter = IntentFilter(AirPodsNotifications.BATTERY_DATA)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
context.registerReceiver(
|
||||||
|
batteryReceiver,
|
||||||
|
batteryIntentFilter,
|
||||||
|
Context.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
batteryStatus.value = service.getBattery()
|
||||||
|
|
||||||
|
if (preview) {
|
||||||
|
batteryStatus.value = listOf<Battery>(
|
||||||
|
Battery(BatteryComponent.LEFT, 100, BatteryStatus.CHARGING),
|
||||||
|
Battery(BatteryComponent.RIGHT, 50, BatteryStatus.NOT_CHARGING),
|
||||||
|
Battery(BatteryComponent.CASE, 5, BatteryStatus.CHARGING)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.5f),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Image (
|
||||||
|
bitmap = ImageBitmap.imageResource(R.drawable.pro_2_buds),
|
||||||
|
contentDescription = "Buds",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.scale(0.80f)
|
||||||
|
)
|
||||||
|
val left = batteryStatus.value.find { it.component == BatteryComponent.LEFT }
|
||||||
|
val right = batteryStatus.value.find { it.component == BatteryComponent.RIGHT }
|
||||||
|
if ((right?.status == BatteryStatus.CHARGING && left?.status == BatteryStatus.CHARGING) || (left?.status == BatteryStatus.NOT_CHARGING && right?.status == BatteryStatus.NOT_CHARGING))
|
||||||
|
{
|
||||||
|
BatteryIndicator(right.level.let { left.level.coerceAtMost(it) }, left.status == BatteryStatus.CHARGING)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Row (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
if (left?.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
BatteryIndicator(
|
||||||
|
left?.level ?: 0,
|
||||||
|
left?.status == BatteryStatus.CHARGING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (left?.status != BatteryStatus.DISCONNECTED && right?.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
}
|
||||||
|
if (right?.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
BatteryIndicator(
|
||||||
|
right?.level ?: 0,
|
||||||
|
right?.status == BatteryStatus.CHARGING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
val case = batteryStatus.value.find { it.component == BatteryComponent.CASE }
|
||||||
|
|
||||||
|
Image(
|
||||||
|
bitmap = ImageBitmap.imageResource(R.drawable.pro_2_case),
|
||||||
|
contentDescription = "Case",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.scale(1.25f)
|
||||||
|
)
|
||||||
|
if (case?.status != BatteryStatus.DISCONNECTED) {
|
||||||
|
BatteryIndicator(case?.level ?: 0, case?.status == BatteryStatus.CHARGING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
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.material3.MaterialTheme
|
||||||
|
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.graphics.luminance
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SinglePodANCSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
var singleANCEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("single_anc", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSingleEnabled(enabled: Boolean) {
|
||||||
|
singleANCEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("single_anc", enabled).apply()
|
||||||
|
service.setNoiseCancellationWithOnePod(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
val isPressed = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onPress = {
|
||||||
|
isPressed.value = true
|
||||||
|
tryAwaitRelease()
|
||||||
|
isPressed.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
updateSingleEnabled(!singleANCEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Noise Cancellation with Single AirPod",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Allow AirPods to be put in noise cancellation mode when only one AirPods is in your ear.",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = textColor.copy(0.6f),
|
||||||
|
lineHeight = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StyledSwitch(
|
||||||
|
checked = singleANCEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
updateSingleEnabled(it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StyledSwitch(
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
|
val thumbColor = Color.White
|
||||||
|
val trackColor = if (checked) Color(0xFF34C759) 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")
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(51.dp)
|
||||||
|
.height(31.dp)
|
||||||
|
.clip(RoundedCornerShape(15.dp))
|
||||||
|
.background(trackColor) // Dynamic track background
|
||||||
|
.padding(horizontal = 3.dp),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.offset(x = thumbOffsetX) // Animate the offset for smooth transition
|
||||||
|
.size(27.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(thumbColor) // Dynamic thumb color
|
||||||
|
.clickable { onCheckedChange(!checked) } // Make the switch clickable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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.graphics.luminance
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StyledTextField(
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit
|
||||||
|
) {
|
||||||
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
|
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)
|
||||||
|
) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.onFocusChanged { focusState ->
|
||||||
|
isFocused = focusState.isFocused // Update focus state
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.graphics.luminance
|
||||||
|
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.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ToneVolumeSlider(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
val sliderValue = remember { mutableFloatStateOf(0f) }
|
||||||
|
LaunchedEffect(sliderValue) {
|
||||||
|
if (sharedPreferences.contains("tone_volume")) {
|
||||||
|
sliderValue.floatValue = sharedPreferences.getInt("tone_volume", 0).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(sliderValue.floatValue) {
|
||||||
|
sharedPreferences.edit().putInt("tone_volume", sliderValue.floatValue.toInt()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
|
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491)
|
||||||
|
val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5)
|
||||||
|
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF)
|
||||||
|
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "\uDBC0\uDEA1",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = labelTextColor
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(start = 4.dp)
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
value = sliderValue.floatValue,
|
||||||
|
onValueChange = {
|
||||||
|
sliderValue.floatValue = it
|
||||||
|
service.setToneVolume(volume = it.toInt())
|
||||||
|
},
|
||||||
|
valueRange = 0f..100f,
|
||||||
|
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 / 100)
|
||||||
|
.height(4.dp)
|
||||||
|
.background(activeTrackColor, RoundedCornerShape(4.dp))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "\uDBC0\uDEA9",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = labelTextColor
|
||||||
|
),
|
||||||
|
modifier = Modifier.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
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.material3.MaterialTheme
|
||||||
|
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.graphics.luminance
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VolumeControlSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
var volumeControlEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("volume_control", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fun updateVolumeControlEnabled(enabled: Boolean) {
|
||||||
|
volumeControlEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("volume_control", enabled).apply()
|
||||||
|
service.setVolumeControl(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
val isPressed = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
shape = RoundedCornerShape(14.dp),
|
||||||
|
color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(
|
||||||
|
onPress = {
|
||||||
|
isPressed.value = true
|
||||||
|
tryAwaitRelease()
|
||||||
|
isPressed.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.clickable(
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
updateVolumeControlEnabled(!volumeControlEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Volume Control",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Adjust the volume by swiping up or down on the sensor located on the AirPods Pro stem.",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = textColor.copy(0.6f),
|
||||||
|
lineHeight = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StyledSwitch(
|
||||||
|
checked = volumeControlEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
updateVolumeControlEnabled(it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
android/imgs/long-press.png
Normal file
BIN
android/imgs/long-press.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 212 KiB |
Reference in New Issue
Block a user