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