Skip to content

Commit 5aeb47b

Browse files
committed
android: add microphone setting
also, un-hardcoded strings, and updated text sizes
1 parent 3cca786 commit 5aeb47b

File tree

5 files changed

+254
-38
lines changed

5 files changed

+254
-38
lines changed

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

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ fun CallControlSettings() {
9191
}?.value ?: byteArrayOf(0x00, 0x03)
9292

9393
var flipped by remember { mutableStateOf(callControlEnabledValue.contentEquals(byteArrayOf(0x00, 0x02))) }
94-
var singlePressAction by remember { mutableStateOf(if (flipped) "Double Press" else "Single Press") }
95-
var doublePressAction by remember { mutableStateOf(if (flipped) "Single Press" else "Double Press") }
94+
var singlePressAction by remember { mutableStateOf(if (flipped) "Press Twice" else "Press Once") }
95+
var doublePressAction by remember { mutableStateOf(if (flipped) "Press Once" else "Press Twice") }
9696
var showSinglePressDropdown by remember { mutableStateOf(false) }
9797
var showDoublePressDropdown by remember { mutableStateOf(false) }
9898

@@ -103,8 +103,8 @@ fun CallControlSettings() {
103103
AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG) {
104104
val newFlipped = controlCommand.value.contentEquals(byteArrayOf(0x00, 0x02))
105105
flipped = newFlipped
106-
singlePressAction = if (newFlipped) "Double Press" else "Single Press"
107-
doublePressAction = if (newFlipped) "Single Press" else "Double Press"
106+
singlePressAction = if (newFlipped) "Press Twice" else "Press Once"
107+
doublePressAction = if (newFlipped) "Press Once" else "Press Twice"
108108
Log.d("CallControlSettings", "Control command received, flipped: $newFlipped")
109109
}
110110
}
@@ -134,19 +134,19 @@ fun CallControlSettings() {
134134
modifier = Modifier
135135
.fillMaxWidth()
136136
.padding(start = 12.dp, end = 12.dp)
137-
.height(55.dp),
137+
.height(50.dp),
138138
horizontalArrangement = Arrangement.SpaceBetween,
139139
verticalAlignment = Alignment.CenterVertically
140140
) {
141141
Text(
142-
text = "Answer call",
143-
fontSize = 18.sp,
142+
text = stringResource(R.string.answer_call),
143+
fontSize = 16.sp,
144144
color = textColor,
145145
modifier = Modifier.padding(bottom = 4.dp)
146146
)
147147
Text(
148-
text = "Single Press",
149-
fontSize = 18.sp,
148+
text = stringResource(R.string.press_once),
149+
fontSize = 16.sp,
150150
color = textColor.copy(alpha = 0.6f)
151151
)
152152
}
@@ -161,13 +161,13 @@ fun CallControlSettings() {
161161
modifier = Modifier
162162
.fillMaxWidth()
163163
.padding(start = 12.dp, end = 12.dp)
164-
.height(55.dp),
164+
.height(50.dp),
165165
horizontalArrangement = Arrangement.SpaceBetween,
166166
verticalAlignment = Alignment.CenterVertically
167167
) {
168168
Text(
169-
text = "Mute/Unmute",
170-
fontSize = 18.sp,
169+
text = stringResource(R.string.mute_unmute),
170+
fontSize = 16.sp,
171171
color = textColor,
172172
modifier = Modifier.padding(bottom = 4.dp)
173173
)
@@ -177,8 +177,8 @@ fun CallControlSettings() {
177177
verticalAlignment = Alignment.CenterVertically
178178
) {
179179
Text(
180-
text = singlePressAction,
181-
fontSize = 18.sp,
180+
text = if (singlePressAction == "Press Once") stringResource(R.string.press_once) else stringResource(R.string.press_twice),
181+
fontSize = 16.sp,
182182
color = textColor.copy(alpha = 0.8f)
183183
)
184184
Icon(
@@ -193,19 +193,19 @@ fun CallControlSettings() {
193193
onDismissRequest = { showSinglePressDropdown = false }
194194
) {
195195
DropdownMenuItem(
196-
text = { Text("Single Press") },
196+
text = { Text(stringResource(R.string.press_once)) },
197197
onClick = {
198-
singlePressAction = "Single Press"
199-
doublePressAction = "Double Press"
198+
singlePressAction = "Press Once"
199+
doublePressAction = "Press Twice"
200200
showSinglePressDropdown = false
201201
service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03))
202202
}
203203
)
204204
DropdownMenuItem(
205-
text = { Text("Double Press") },
205+
text = { Text(stringResource(R.string.press_twice)) },
206206
onClick = {
207-
singlePressAction = "Double Press"
208-
doublePressAction = "Single Press"
207+
singlePressAction = "Press Twice"
208+
doublePressAction = "Press Once"
209209
showSinglePressDropdown = false
210210
service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02))
211211
}
@@ -224,13 +224,13 @@ fun CallControlSettings() {
224224
modifier = Modifier
225225
.fillMaxWidth()
226226
.padding(start = 12.dp, end = 12.dp)
227-
.height(55.dp),
227+
.height(50.dp),
228228
horizontalArrangement = Arrangement.SpaceBetween,
229229
verticalAlignment = Alignment.CenterVertically
230230
) {
231231
Text(
232-
text = "Hang Up",
233-
fontSize = 18.sp,
232+
text = stringResource(R.string.hang_up),
233+
fontSize = 16.sp,
234234
color = textColor,
235235
modifier = Modifier.padding(bottom = 4.dp)
236236
)
@@ -240,8 +240,8 @@ fun CallControlSettings() {
240240
verticalAlignment = Alignment.CenterVertically
241241
) {
242242
Text(
243-
text = doublePressAction,
244-
fontSize = 18.sp,
243+
text = if (doublePressAction == "Press Once") stringResource(R.string.press_once) else stringResource(R.string.press_twice),
244+
fontSize = 16.sp,
245245
color = textColor.copy(alpha = 0.8f)
246246
)
247247
Icon(
@@ -256,19 +256,19 @@ fun CallControlSettings() {
256256
onDismissRequest = { showDoublePressDropdown = false }
257257
) {
258258
DropdownMenuItem(
259-
text = { Text("Single Press") },
259+
text = { Text(stringResource(R.string.press_once)) },
260260
onClick = {
261-
doublePressAction = "Single Press"
262-
singlePressAction = "Double Press"
261+
doublePressAction = "Press Once"
262+
singlePressAction = "Press Twice"
263263
showDoublePressDropdown = false
264264
service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02))
265265
}
266266
)
267267
DropdownMenuItem(
268-
text = { Text("Double Press") },
268+
text = { Text(stringResource(R.string.press_twice)) },
269269
onClick = {
270-
doublePressAction = "Double Press"
271-
singlePressAction = "Single Press"
270+
doublePressAction = "Press Twice"
271+
singlePressAction = "Press Once"
272272
showDoublePressDropdown = false
273273
service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03))
274274
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* LibrePods - AirPods liberated from Apple’s ecosystem
3+
*
4+
* Copyright (C) 2025 LibrePods contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published
8+
* by the Free Software Foundation, either version 3 of the License.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
@file:OptIn(ExperimentalEncodingApi::class)
20+
21+
package me.kavishdevar.librepods.composables
22+
23+
import android.util.Log
24+
import androidx.compose.foundation.background
25+
import androidx.compose.foundation.clickable
26+
import androidx.compose.foundation.isSystemInDarkTheme
27+
import androidx.compose.foundation.layout.Arrangement
28+
import androidx.compose.foundation.layout.Box
29+
import androidx.compose.foundation.layout.Column
30+
import androidx.compose.foundation.layout.Row
31+
import androidx.compose.foundation.layout.Spacer
32+
import androidx.compose.foundation.layout.fillMaxWidth
33+
import androidx.compose.foundation.layout.height
34+
import androidx.compose.foundation.layout.padding
35+
import androidx.compose.foundation.layout.size
36+
import androidx.compose.foundation.layout.width
37+
import androidx.compose.foundation.shape.RoundedCornerShape
38+
import androidx.compose.material.icons.Icons
39+
import androidx.compose.material.icons.filled.KeyboardArrowDown
40+
import androidx.compose.material3.DropdownMenu
41+
import androidx.compose.material3.DropdownMenuItem
42+
import androidx.compose.material3.Icon
43+
import androidx.compose.material3.HorizontalDivider
44+
import androidx.compose.material3.Text
45+
import androidx.compose.runtime.Composable
46+
import androidx.compose.runtime.DisposableEffect
47+
import androidx.compose.runtime.LaunchedEffect
48+
import androidx.compose.runtime.getValue
49+
import androidx.compose.runtime.mutableStateOf
50+
import androidx.compose.runtime.remember
51+
import androidx.compose.runtime.setValue
52+
import androidx.compose.ui.Alignment
53+
import androidx.compose.ui.Modifier
54+
import androidx.compose.ui.graphics.Color
55+
import androidx.compose.ui.res.stringResource
56+
import androidx.compose.ui.text.TextStyle
57+
import androidx.compose.ui.text.font.FontWeight
58+
import androidx.compose.ui.tooling.preview.Preview
59+
import androidx.compose.ui.unit.dp
60+
import androidx.compose.ui.unit.sp
61+
import me.kavishdevar.librepods.R
62+
import me.kavishdevar.librepods.services.ServiceManager
63+
import me.kavishdevar.librepods.utils.AACPManager
64+
import kotlin.io.encoding.ExperimentalEncodingApi
65+
66+
@Composable
67+
fun MicrophoneSettings() {
68+
val isDarkTheme = isSystemInDarkTheme()
69+
val textColor = if (isDarkTheme) Color.White else Color.Black
70+
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
71+
72+
Column(
73+
modifier = Modifier
74+
.fillMaxWidth()
75+
.background(backgroundColor, RoundedCornerShape(14.dp))
76+
.padding(top = 2.dp)
77+
) {
78+
val service = ServiceManager.getService()!!
79+
val micModeValue = service.aacpManager.controlCommandStatusList.find {
80+
it.identifier == AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE
81+
}?.value?.get(0) ?: 0x00.toByte()
82+
83+
var selectedMode by remember {
84+
mutableStateOf(
85+
when (micModeValue) {
86+
0x00.toByte() -> "Automatic"
87+
0x01.toByte() -> "Always Right"
88+
0x02.toByte() -> "Always Left"
89+
else -> "Automatic"
90+
}
91+
)
92+
}
93+
var showDropdown by remember { mutableStateOf(false) }
94+
95+
val listener = object : AACPManager.ControlCommandListener {
96+
override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) {
97+
if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) ==
98+
AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE) {
99+
selectedMode = when (controlCommand.value.get(0)) {
100+
0x00.toByte() -> "Automatic"
101+
0x01.toByte() -> "Always Right"
102+
0x02.toByte() -> "Always Left"
103+
else -> "Automatic"
104+
}
105+
Log.d("MicrophoneSettings", "Microphone mode received: $selectedMode")
106+
}
107+
}
108+
}
109+
110+
LaunchedEffect(Unit) {
111+
service.aacpManager.registerControlCommandListener(
112+
AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE,
113+
listener
114+
)
115+
}
116+
117+
DisposableEffect(Unit) {
118+
onDispose {
119+
service.aacpManager.unregisterControlCommandListener(
120+
AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE,
121+
listener
122+
)
123+
}
124+
}
125+
126+
Row(
127+
modifier = Modifier
128+
.fillMaxWidth()
129+
.padding(start = 12.dp, end = 12.dp)
130+
.height(55.dp),
131+
horizontalArrangement = Arrangement.SpaceBetween,
132+
verticalAlignment = Alignment.CenterVertically
133+
) {
134+
Text(
135+
text = stringResource(R.string.microphone_mode),
136+
fontSize = 16.sp,
137+
color = textColor,
138+
modifier = Modifier.padding(bottom = 4.dp)
139+
)
140+
Box {
141+
Row(
142+
modifier = Modifier.clickable { showDropdown = true },
143+
verticalAlignment = Alignment.CenterVertically
144+
) {
145+
Text(
146+
text = selectedMode,
147+
fontSize = 16.sp,
148+
color = textColor.copy(alpha = 0.8f)
149+
)
150+
Icon(
151+
Icons.Default.KeyboardArrowDown,
152+
contentDescription = null,
153+
modifier = Modifier.size(16.dp),
154+
tint = textColor.copy(alpha = 0.6f)
155+
)
156+
}
157+
DropdownMenu(
158+
expanded = showDropdown,
159+
onDismissRequest = { showDropdown = false }
160+
) {
161+
DropdownMenuItem(
162+
text = { Text(stringResource(R.string.microphone_automatic)) },
163+
onClick = {
164+
selectedMode = "Automatic"
165+
showDropdown = false
166+
service.aacpManager.sendControlCommand(
167+
AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value,
168+
byteArrayOf(0x00)
169+
)
170+
}
171+
)
172+
DropdownMenuItem(
173+
text = { Text(stringResource(R.string.microphone_always_right)) },
174+
onClick = {
175+
selectedMode = "Always Right"
176+
showDropdown = false
177+
service.aacpManager.sendControlCommand(
178+
AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value,
179+
byteArrayOf(0x01)
180+
)
181+
}
182+
)
183+
DropdownMenuItem(
184+
text = { Text(stringResource(R.string.microphone_always_left)) },
185+
onClick = {
186+
selectedMode = "Always Left"
187+
showDropdown = false
188+
service.aacpManager.sendControlCommand(
189+
AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value,
190+
byteArrayOf(0x02)
191+
)
192+
}
193+
)
194+
}
195+
}
196+
}
197+
}
198+
}
199+
200+
@Preview
201+
@Composable
202+
fun MicrophoneSettingsPreview() {
203+
MicrophoneSettings()
204+
}

0 commit comments

Comments
 (0)