From 86551be86b2976526aad1cb39fc5a57432fb4173 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 8 Sep 2025 00:23:45 +0530 Subject: [PATCH 01/72] android: add accessibility stuff adds option for customizing transparency mode, amplification, tone, etc. --- android/app/src/main/AndroidManifest.xml | 4 +- android/app/src/main/cpp/l2c_fcr_hook.cpp | 68 +++ android/app/src/main/cpp/l2c_fcr_hook.h | 22 + .../librepods/CustomDeviceActivity.kt | 450 ++++++++++++----- .../composables/AccessibilitySlider.kt | 138 +++++ .../screens/AccessibilitySettingsScreen.kt | 472 ++++++++++++++++++ .../screens/EqualizerSettingsScreen.kt | 304 +++++++++++ .../librepods/utils/AACPManager.kt | 51 +- .../librepods/utils/RadareOffsetFinder.kt | 50 +- 9 files changed, 1430 insertions(+), 129 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index af4adc5e..a6a5ae4f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -90,13 +90,13 @@ - + #include #include "l2c_fcr_hook.h" +#include +#include #define LOG_TAG "AirPodsHook" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) @@ -126,6 +128,9 @@ static void (*original_l2cu_process_our_cfg_req)(tL2C_CCB* p_ccb, tL2CAP_CFG_INF static void (*original_l2c_csm_config)(tL2C_CCB* p_ccb, uint8_t event, void* p_data) = nullptr; static void (*original_l2cu_send_peer_info_req)(tL2C_LCB* p_lcb, uint16_t info_type) = nullptr; +// Add original pointer for BTA_DmSetLocalDiRecord +static tBTA_STATUS (*original_BTA_DmSetLocalDiRecord)(tSDP_DI_RECORD* p_device_info, uint32_t* p_handle) = nullptr; + uint8_t fake_l2c_fcr_chk_chan_modes(void* p_ccb) { LOGI("l2c_fcr_chk_chan_modes hooked, returning true."); return 1; @@ -156,6 +161,53 @@ void fake_l2cu_send_peer_info_req(tL2C_LCB* p_lcb, uint16_t info_type) { return; } +// New loader for SDP hook offset (persist.librepods.sdp_offset) +uintptr_t loadSdpOffset() { + const char* property_name = "persist.librepods.sdp_offset"; + char value[PROP_VALUE_MAX] = {0}; + + int len = __system_property_get(property_name, value); + if (len > 0) { + LOGI("Read sdp offset from property: %s", value); + uintptr_t offset; + char* endptr = nullptr; + + const char* parse_start = value; + if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) { + parse_start = value + 2; + } + + errno = 0; + offset = strtoul(parse_start, &endptr, 16); + + if (errno == 0 && endptr != parse_start && *endptr == '\0' && offset > 0) { + LOGI("Parsed sdp offset: 0x%x", offset); + return offset; + } + + LOGE("Failed to parse sdp offset from property value: %s", value); + } + + LOGI("No sdp offset property present - skipping SDP hook"); + return 0; +} + +// Fake BTA_DmSetLocalDiRecord: set vendor/vendor_id_source then call original +tBTA_STATUS fake_BTA_DmSetLocalDiRecord(tSDP_DI_RECORD* p_device_info, uint32_t* p_handle) { + LOGI("BTA_DmSetLocalDiRecord hooked - forcing vendor fields"); + if (p_device_info) { + p_device_info->vendor = 0x004C; + p_device_info->vendor_id_source = 0x0001; + } + LOGI("Set vendor=0x%04x, vendor_id_source=0x%04x", p_device_info->vendor, p_device_info->vendor_id_source); + if (original_BTA_DmSetLocalDiRecord) { + return original_BTA_DmSetLocalDiRecord(p_device_info, p_handle); + } + + LOGE("Original BTA_DmSetLocalDiRecord not available"); + return BTA_FAILURE; +} + uintptr_t loadHookOffset([[maybe_unused]] const char* package_name) { const char* property_name = "persist.librepods.hook_offset"; char value[PROP_VALUE_MAX] = {0}; @@ -320,6 +372,7 @@ bool findAndHookFunction(const char *library_name) { uintptr_t l2cu_process_our_cfg_req_offset = loadL2cuProcessCfgReqOffset(); uintptr_t l2c_csm_config_offset = loadL2cCsmConfigOffset(); uintptr_t l2cu_send_peer_info_req_offset = loadL2cuSendPeerInfoReqOffset(); + uintptr_t sdp_offset = loadSdpOffset(); bool success = false; @@ -392,6 +445,21 @@ bool findAndHookFunction(const char *library_name) { LOGI("Skipping l2cu_send_peer_info_req hook as offset is not available"); } + if (sdp_offset > 0) { + void* target = reinterpret_cast(base_addr + sdp_offset); + LOGI("Hooking BTA_DmSetLocalDiRecord at offset: 0x%x, base: %p, target: %p", + sdp_offset, (void*)base_addr, target); + + int result = hook_func(target, (void*)fake_BTA_DmSetLocalDiRecord, (void**)&original_BTA_DmSetLocalDiRecord); + if (result != 0) { + LOGE("Failed to hook BTA_DmSetLocalDiRecord, error: %d", result); + } else { + LOGI("Successfully hooked BTA_DmSetLocalDiRecord (SDP)"); + } + } else { + LOGI("Skipping BTA_DmSetLocalDiRecord hook as sdp offset is not available"); + } + return success; } diff --git a/android/app/src/main/cpp/l2c_fcr_hook.h b/android/app/src/main/cpp/l2c_fcr_hook.h index cff43d47..2ab32563 100644 --- a/android/app/src/main/cpp/l2c_fcr_hook.h +++ b/android/app/src/main/cpp/l2c_fcr_hook.h @@ -26,3 +26,25 @@ uintptr_t loadL2cuProcessCfgReqOffset(); uintptr_t loadL2cCsmConfigOffset(); uintptr_t loadL2cuSendPeerInfoReqOffset(); bool findAndHookFunction(const char *library_path); + +#define SDP_MAX_ATTR_LEN 400 + +typedef struct t_sdp_di_record { + uint16_t vendor; + uint16_t vendor_id_source; + uint16_t product; + uint16_t version; + bool primary_record; + char client_executable_url[SDP_MAX_ATTR_LEN]; + char service_description[SDP_MAX_ATTR_LEN]; + char documentation_url[SDP_MAX_ATTR_LEN]; +} tSDP_DI_RECORD; + +typedef enum : uint8_t { + BTA_SUCCESS = 0, /* Successful operation. */ + BTA_FAILURE = 1, /* Generic failure. */ + BTA_PENDING = 2, /* API cannot be completed right now */ + BTA_BUSY = 3, + BTA_NO_RESOURCES = 4, + BTA_WRONG_MODE = 5, +} tBTA_STATUS; \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt index 98398aa5..46b6415a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt @@ -21,13 +21,10 @@ package me.kavishdevar.librepods import android.Manifest import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice -import android.bluetooth.BluetoothDevice.TRANSPORT_LE -import android.bluetooth.BluetoothGatt -import android.bluetooth.BluetoothGattCallback -import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothManager -import android.os.Build +import android.bluetooth.BluetoothSocket import android.os.Bundle +import android.os.ParcelUuid import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -36,153 +33,364 @@ import androidx.annotation.RequiresPermission import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen +import me.kavishdevar.librepods.screens.EqualizerSettingsScreen import me.kavishdevar.librepods.ui.theme.LibrePodsTheme import org.lsposed.hiddenapibypass.HiddenApiBypass -import java.util.UUID +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder class CustomDevice : ComponentActivity() { - @SuppressLint("MissingPermission", "CoroutineCreationDuringComposition") + private val TAG = "AirPodsAccessibilitySettings" + private var socket: BluetoothSocket? = null + private val deviceAddress = "28:2D:7F:C2:05:5B" + private val psm = 31 + private val uuid: ParcelUuid = ParcelUuid.fromString("00000000-0000-0000-0000-00000000000") + + // Data states + private val isConnected = mutableStateOf(false) + private val leftAmplification = mutableStateOf(1.0f) + private val leftTone = mutableStateOf(1.0f) + private val leftAmbientNoiseReduction = mutableStateOf(0.5f) + private val leftConversationBoost = mutableStateOf(false) + private val leftEQ = mutableStateOf(FloatArray(8) { 50.0f }) + + private val rightAmplification = mutableStateOf(1.0f) + private val rightTone = mutableStateOf(1.0f) + private val rightAmbientNoiseReduction = mutableStateOf(0.5f) + private val rightConversationBoost = mutableStateOf(false) + private val rightEQ = mutableStateOf(FloatArray(8) { 50.0f }) + + private val singleMode = mutableStateOf(false) + private val amplification = mutableStateOf(1.0f) + private val balance = mutableStateOf(0.5f) + + private val retryCount = mutableStateOf(0) + private val showRetryButton = mutableStateOf(false) + private val maxRetries = 3 + + private var debounceJob: Job? = null + + // Phone and Media EQ state + private val phoneMediaEQ = mutableStateOf(FloatArray(8) { 50.0f }) + + @SuppressLint("MissingPermission") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { LibrePodsTheme { - val connect = remember { mutableStateOf(false) } - Scaffold( - modifier = Modifier.fillMaxSize(), - topBar = { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text("Custom Device", style = MaterialTheme.typography.titleLarge) - } + val navController = rememberNavController() + + NavHost(navController = navController, startDestination = "main") { + composable("main") { + AccessibilitySettingsScreen( + navController = navController, + isConnected = isConnected.value, + leftAmplification = leftAmplification, + leftTone = leftTone, + leftAmbientNoiseReduction = leftAmbientNoiseReduction, + leftConversationBoost = leftConversationBoost, + rightAmplification = rightAmplification, + rightTone = rightTone, + rightAmbientNoiseReduction = rightAmbientNoiseReduction, + rightConversationBoost = rightConversationBoost, + singleMode = singleMode, + amplification = amplification, + balance = balance, + showRetryButton = showRetryButton.value, + onRetry = { CoroutineScope(Dispatchers.IO).launch { connectL2CAP() } }, + onSettingsChanged = { sendAccessibilitySettings() } + ) } - ) { innerPadding -> - HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") - val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager -// val device: BluetoothDevice = manager.adapter.getRemoteDevice("EC:D6:F4:3D:89:B8") - val device: BluetoothDevice = manager.adapter.getRemoteDevice("E7:48:92:3B:7D:A5") -// val socket = device.createInsecureL2capChannel(31) - -// val batteryLevel = remember { mutableStateOf("") } -// socket.outputStream.write(byteArrayOf(0x12,0x3B,0x00,0x02, 0x00)) -// socket.outputStream.write(byteArrayOf(0x12, 0x3A, 0x00, 0x01, 0x00, 0x08,0x01)) - - val gatt = device.connectGatt(this, true, object: BluetoothGattCallback() { - override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { - if (status == BluetoothGatt.GATT_SUCCESS) { - // Step 2: Iterate through the services and characteristics - gatt.services.forEach { service -> - Log.d("GATT", "Service UUID: ${service.uuid}") - service.characteristics.forEach { characteristic -> - characteristic.descriptors.forEach { descriptor -> - Log.d("GATT", " Descriptor UUID: ${descriptor.uuid}: ${gatt.readDescriptor(descriptor)}") - } - } - } - - } - } + composable("eq") { + EqualizerSettingsScreen( + navController = navController, + leftEQ = leftEQ, + rightEQ = rightEQ, + singleMode = singleMode, + onEQChanged = { sendAccessibilitySettings() }, + phoneMediaEQ = phoneMediaEQ + ) + } + } + } + } - override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { - if (newState == BluetoothGatt.STATE_CONNECTED) { - Log.d("GATT", "Connected to GATT server") - gatt.discoverServices() // Discover services after connection - } - } + // Connect automatically + CoroutineScope(Dispatchers.IO).launch { connectL2CAP() } + } - override fun onCharacteristicWrite( - gatt: BluetoothGatt, - characteristic: BluetoothGattCharacteristic, - status: Int - ) { - if (status == BluetoothGatt.GATT_SUCCESS) { - Log.d("BLE", "Write successful for UUID: ${characteristic.uuid}") - } else { - Log.e("BLE", "Write failed for UUID: ${characteristic.uuid}, status: $status") - } - } - }, TRANSPORT_LE, 1) + override fun onDestroy() { + super.onDestroy() + socket?.close() + } - if (connect.value) { - try { - gatt.connect() - } - catch (e: Exception) { - e.printStackTrace() - } - connect.value = false - } + private suspend fun connectL2CAP() { + retryCount.value = 0 + // Close any existing socket + socket?.close() + socket = null + while (retryCount.value < maxRetries) { + try { + Log.d(TAG, "Starting L2CAP connection setup, attempt ${retryCount.value + 1}") + HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") + val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager + val device: BluetoothDevice = manager.adapter.getRemoteDevice(deviceAddress) + socket = createBluetoothSocket(device, psm) - Column ( - modifier = Modifier.padding(innerPadding), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) - { - Button( - onClick = { connect.value = true } - ) - { - Text("Connect") - } + withTimeout(5000L) { + socket?.connect() + } - Button(onClick = { - val characteristicUuid = "94110001-6D9B-4225-A4F1-6A4A7F01B0DE" - val value = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x00 ,0x00 ,0x01) - sendWriteRequest(gatt, characteristicUuid, value) - - }) { - Text("batteryLevel.value") - } - } + withContext(Dispatchers.Main) { + isConnected.value = true + showRetryButton.value = false + Log.d(TAG, "L2CAP connection established successfully") + } + + // Read current settings + readCurrentSettings() + + // Start listening for responses + listenForData() + + return + } catch (e: Exception) { + Log.e(TAG, "Failed to connect, attempt ${retryCount.value + 1}: ${e.message}") + retryCount.value++ + if (retryCount.value < maxRetries) { + delay(2000) // Wait 2 seconds before retry } } } + + // After max retries + withContext(Dispatchers.Main) { + isConnected.value = false + showRetryButton.value = true + Log.e(TAG, "Failed to connect after $maxRetries attempts") + } } -} - -@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) -fun sendWriteRequest( - gatt: BluetoothGatt, - characteristicUuid: String, - value: ByteArray -) { - // Retrieve the service containing the characteristic - val service = gatt.services.find { service -> - service.characteristics.any { it.uuid.toString() == characteristicUuid } + + private fun createBluetoothSocket(device: BluetoothDevice, psm: Int): BluetoothSocket { + val type = 3 // L2CAP + val constructorSpecs = listOf( + arrayOf(device, type, true, true, 31, uuid), + arrayOf(device, type, 1, true, true, 31, uuid), + arrayOf(type, 1, true, true, device, 31, uuid), + arrayOf(type, true, true, device, 31, uuid) + ) + + val constructors = BluetoothSocket::class.java.declaredConstructors + Log.d(TAG, "BluetoothSocket has ${constructors.size} constructors") + + var lastException: Exception? = null + var attemptedConstructors = 0 + + for ((index, params) in constructorSpecs.withIndex()) { + try { + Log.d(TAG, "Trying constructor signature #${index + 1}") + attemptedConstructors++ + return HiddenApiBypass.newInstance(BluetoothSocket::class.java, *params) as BluetoothSocket + } catch (e: Exception) { + Log.e(TAG, "Constructor signature #${index + 1} failed: ${e.message}") + lastException = e + } + } + + val errorMessage = "Failed to create BluetoothSocket after trying $attemptedConstructors constructor signatures" + Log.e(TAG, errorMessage) + throw lastException ?: IllegalStateException(errorMessage) } - if (service == null) { - Log.e("GATT", "Service containing characteristic UUID $characteristicUuid not found.") - return + private fun readCurrentSettings() { + CoroutineScope(Dispatchers.IO).launch { + try { + Log.d(TAG, "Sending read settings command: 0A1800") + val readCommand = byteArrayOf(0x0A, 0x18, 0x00) + socket?.outputStream?.write(readCommand) + socket?.outputStream?.flush() + Log.d(TAG, "Read settings command sent") + } catch (e: IOException) { + Log.e(TAG, "Failed to send read command: ${e.message}") + } + } } - // Retrieve the characteristic - val characteristic = service.getCharacteristic(UUID.fromString(characteristicUuid)) - if (characteristic == null) { - Log.e("GATT", "Characteristic with UUID $characteristicUuid not found.") - return + private fun listenForData() { + CoroutineScope(Dispatchers.IO).launch { + try { + val buffer = ByteArray(1024) + Log.d(TAG, "Started listening for incoming data") + while (socket?.isConnected == true) { + val bytesRead = socket?.inputStream?.read(buffer) + if (bytesRead != null && bytesRead > 0) { + val data = buffer.copyOfRange(0, bytesRead) + Log.d(TAG, "Received data: ${data.joinToString(" ") { "%02X".format(it) }}") + parseSettingsResponse(data) + } else if (bytesRead == -1) { + Log.d(TAG, "Connection closed by remote device") + withContext(Dispatchers.Main) { + isConnected.value = false + } + // Attempt to reconnect + connectL2CAP() + break + } + } + } catch (e: IOException) { + Log.e(TAG, "Connection lost: ${e.message}") + withContext(Dispatchers.Main) { + isConnected.value = false + } + // Close socket + socket?.close() + socket = null + // Attempt to reconnect + connectL2CAP() + } + } } + private fun parseSettingsResponse(data: ByteArray) { + if (data.size < 2 || data[0] != 0x0B.toByte()) { + Log.d(TAG, "Not a settings response") + return + } + + val settingsData = data.copyOfRange(1, data.size) + if (settingsData.size < 100) { // 25 floats * 4 bytes + Log.e(TAG, "Settings data too short: ${settingsData.size} bytes") + return + } + + val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) + + // Global enabled + val enabled = buffer.float + Log.d(TAG, "Parsed enabled: $enabled") + + // Left bud + val newLeftEQ = leftEQ.value.copyOf() + for (i in 0..7) { + newLeftEQ[i] = buffer.float + Log.d(TAG, "Parsed left EQ${i+1}: ${newLeftEQ[i]}") + } + leftEQ.value = newLeftEQ + if (singleMode.value) rightEQ.value = newLeftEQ + + leftAmplification.value = buffer.float + Log.d(TAG, "Parsed left amplification: ${leftAmplification.value}") + leftTone.value = buffer.float + Log.d(TAG, "Parsed left tone: ${leftTone.value}") + if (singleMode.value) rightTone.value = leftTone.value + val leftConvFloat = buffer.float + leftConversationBoost.value = leftConvFloat > 0.5f + Log.d(TAG, "Parsed left conversation boost: $leftConvFloat (${leftConversationBoost.value})") + if (singleMode.value) rightConversationBoost.value = leftConversationBoost.value + leftAmbientNoiseReduction.value = buffer.float + Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.value}") + if (singleMode.value) rightAmbientNoiseReduction.value = leftAmbientNoiseReduction.value - // Send the write request - val success = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - gatt.writeCharacteristic(characteristic, value, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) - } else { - gatt.writeCharacteristic(characteristic) + // Right bud + val newRightEQ = rightEQ.value.copyOf() + for (i in 0..7) { + newRightEQ[i] = buffer.float + Log.d(TAG, "Parsed right EQ${i+1}: ${newRightEQ[i]}") + } + rightEQ.value = newRightEQ + + rightAmplification.value = buffer.float + Log.d(TAG, "Parsed right amplification: ${rightAmplification.value}") + rightTone.value = buffer.float + Log.d(TAG, "Parsed right tone: ${rightTone.value}") + val rightConvFloat = buffer.float + rightConversationBoost.value = rightConvFloat > 0.5f + Log.d(TAG, "Parsed right conversation boost: $rightConvFloat (${rightConversationBoost.value})") + rightAmbientNoiseReduction.value = buffer.float + Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.value}") + + Log.d(TAG, "Settings parsed successfully") + + // Update single mode values if in single mode + if (singleMode.value) { + val avg = (leftAmplification.value + rightAmplification.value) / 2 + amplification.value = avg.coerceIn(0f, 1f) + val diff = rightAmplification.value - leftAmplification.value + balance.value = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) + } + } + + private fun sendAccessibilitySettings() { + if (!isConnected.value || socket == null) { + Log.w(TAG, "Not connected, cannot send settings") + return + } + + debounceJob?.cancel() + debounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + val buffer = ByteBuffer.allocate(103).order(ByteOrder.LITTLE_ENDIAN) // 3 header + 100 data bytes + + buffer.put(0x12) + buffer.put(0x18) + buffer.put(0x00) + buffer.putFloat(1.0f) // enabled + + // Left bud + for (eq in leftEQ.value) { + buffer.putFloat(eq) + } + buffer.putFloat(leftAmplification.value) + buffer.putFloat(leftTone.value) + buffer.putFloat(if (leftConversationBoost.value) 1.0f else 0.0f) + buffer.putFloat(leftAmbientNoiseReduction.value) + + // Right bud + for (eq in rightEQ.value) { + buffer.putFloat(eq) + } + buffer.putFloat(rightAmplification.value) + buffer.putFloat(rightTone.value) + buffer.putFloat(if (rightConversationBoost.value) 1.0f else 0.0f) + buffer.putFloat(rightAmbientNoiseReduction.value) + + val packet = buffer.array() + Log.d(TAG, "Packet length: ${packet.size}") + socket?.outputStream?.write(packet) + socket?.outputStream?.flush() + Log.d(TAG, "Accessibility settings sent: ${packet.joinToString(" ") { "%02X".format(it) }}") + } catch (e: IOException) { + Log.e(TAG, "Failed to send accessibility settings: ${e.message}") + withContext(Dispatchers.Main) { + isConnected.value = false + } + // Close socket + socket?.close() + socket = null + } + } } - Log.d("GATT", "Write request sent $success to UUID: $characteristicUuid") } \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt new file mode 100644 index 00000000..d111c0a3 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt @@ -0,0 +1,138 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.composables + +import androidx.compose.foundation.background +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.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.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.text.TextStyle +import androidx.compose.ui.text.font.Font +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 me.kavishdevar.librepods.R +import kotlin.math.roundToInt + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccessibilitySlider( + label: String, + value: Float, + onValueChange: (Float) -> Unit, + valueRange: ClosedFloatingPointRange +) { + val isDarkTheme = isSystemInDarkTheme() + + 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 + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = label, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = labelTextColor, + fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + ) + ) + + Slider( + value = value, + onValueChange = onValueChange, + valueRange = valueRange, + onValueChangeFinished = { + // Round to 2 decimal places + onValueChange((value * 100).roundToInt() / 100f) + }, + modifier = Modifier + .fillMaxWidth() + .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((value - valueRange.start) / (valueRange.endInclusive - valueRange.start)) + .height(4.dp) + .background(activeTrackColor, RoundedCornerShape(4.dp)) + ) + } + } + ) + } +} + +@Preview +@Composable +fun AccessibilitySliderPreview() { + AccessibilitySlider( + label = "Test Slider", + value = 1.0f, + onValueChange = {}, + valueRange = 0f..2f + ) +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt new file mode 100644 index 00000000..824098c4 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -0,0 +1,472 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +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.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.composables.NavigationButton +import me.kavishdevar.librepods.composables.StyledSwitch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccessibilitySettingsScreen( + navController: NavController, + isConnected: Boolean, + leftAmplification: MutableState, + leftTone: MutableState, + leftAmbientNoiseReduction: MutableState, + leftConversationBoost: MutableState, + rightAmplification: MutableState, + rightTone: MutableState, + rightAmbientNoiseReduction: MutableState, + rightConversationBoost: MutableState, + singleMode: MutableState, + amplification: MutableState, + balance: MutableState, + showRetryButton: Boolean, + onRetry: () -> Unit, + onSettingsChanged: () -> Unit +) { + val isDarkTheme = isSystemInDarkTheme() + val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + val cardBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + val textColor = if (isDarkTheme) Color.White else Color.Black + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + "Accessibility Settings", + style = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + ) + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent + ) + ) + }, + containerColor = backgroundColor + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // Retry Button if needed + if (!isConnected && showRetryButton) { + Button( + onClick = onRetry, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF007AFF) + ) + ) { + Text("Retry Connection") + } + } + + // Single Mode Switch + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + "Single Mode", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + ) + ) + StyledSwitch( + checked = singleMode.value, + onCheckedChange = { + singleMode.value = it + if (it) { + // When switching to single mode, set amplification and balance + val avg = (leftAmplification.value + rightAmplification.value) / 2 + amplification.value = avg.coerceIn(0f, 1f) + val diff = rightAmplification.value - leftAmplification.value + balance.value = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) + // Update left and right + val amp = amplification.value + val bal = balance.value + leftAmplification.value = amp * (1 + bal) + rightAmplification.value = amp * (2 - bal) + } + } + ) + } + } + + if (isConnected) { + if (singleMode.value) { + // Balance Slider for Amplification + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + AccessibilitySlider( + label = "Amplification", + value = amplification.value, + onValueChange = { + amplification.value = it + val amp = it + val bal = balance.value + leftAmplification.value = amp * (1 + bal) + rightAmplification.value = amp * (2 - bal) + onSettingsChanged() + }, + valueRange = 0f..1f + ) + + AccessibilitySlider( + label = "Balance", + value = balance.value, + onValueChange = { + balance.value = it + val amp = amplification.value + val bal = it + leftAmplification.value = amp * (1 + bal) + rightAmplification.value = amp * (2 - bal) + onSettingsChanged() + }, + valueRange = 0f..1f + ) + } + } + + // Single Bud Settings Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Bud Settings", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + AccessibilitySlider( + label = "Tone", + value = leftTone.value, + onValueChange = { + leftTone.value = it + rightTone.value = it + onSettingsChanged() + }, + valueRange = 0f..2f + ) + + AccessibilitySlider( + label = "Ambient Noise Reduction", + value = leftAmbientNoiseReduction.value, + onValueChange = { + leftAmbientNoiseReduction.value = it + rightAmbientNoiseReduction.value = it + onSettingsChanged() + }, + valueRange = 0f..1f + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + "Conversation Boost", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + StyledSwitch( + checked = leftConversationBoost.value, + onCheckedChange = { + leftConversationBoost.value = it + rightConversationBoost.value = it + onSettingsChanged() + } + ) + } + + NavigationButton( + to = "eq", + name = "Equalizer Settings", + navController = navController + ) + } + } + } else { + // Left Bud Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Left Bud", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + AccessibilitySlider( + label = "Amplification", + value = leftAmplification.value, + onValueChange = { + leftAmplification.value = it + onSettingsChanged() + }, + valueRange = 0f..2f + ) + + AccessibilitySlider( + label = "Tone", + value = leftTone.value, + onValueChange = { + leftTone.value = it + onSettingsChanged() + }, + valueRange = 0f..2f + ) + + AccessibilitySlider( + label = "Ambient Noise Reduction", + value = leftAmbientNoiseReduction.value, + onValueChange = { + leftAmbientNoiseReduction.value = it + onSettingsChanged() + }, + valueRange = 0f..1f + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + "Conversation Boost", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + StyledSwitch( + checked = leftConversationBoost.value, + onCheckedChange = { + leftConversationBoost.value = it + onSettingsChanged() + } + ) + } + + NavigationButton( + to = "eq", + name = "Equalizer Settings", + navController = navController + ) + } + } + + // Right Bud Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Right Bud", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + AccessibilitySlider( + label = "Amplification", + value = rightAmplification.value, + onValueChange = { + rightAmplification.value = it + onSettingsChanged() + }, + valueRange = 0f..2f + ) + + AccessibilitySlider( + label = "Tone", + value = rightTone.value, + onValueChange = { + rightTone.value = it + onSettingsChanged() + }, + valueRange = 0f..2f + ) + + AccessibilitySlider( + label = "Ambient Noise Reduction", + value = rightAmbientNoiseReduction.value, + onValueChange = { + rightAmbientNoiseReduction.value = it + onSettingsChanged() + }, + valueRange = 0f..1f + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + "Conversation Boost", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + StyledSwitch( + checked = rightConversationBoost.value, + onCheckedChange = { + rightConversationBoost.value = it + onSettingsChanged() + } + ) + } + + NavigationButton( + to = "eq", + name = "Equalizer Settings", + navController = navController + ) + } + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt new file mode 100644 index 00000000..8197c2d3 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt @@ -0,0 +1,304 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +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.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember +import androidx.compose.runtime.mutableStateOf +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.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.services.ServiceManager + +import kotlin.io.encoding.ExperimentalEncodingApi + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EqualizerSettingsScreen( + navController: NavController, + leftEQ: MutableState, + rightEQ: MutableState, + singleMode: MutableState, + onEQChanged: () -> Unit, + phoneMediaEQ: MutableState +) { + val isDarkTheme = isSystemInDarkTheme() + val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + val cardBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + val textColor = if (isDarkTheme) Color.White else Color.Black + + val aacpManager = ServiceManager.getService()!!.aacpManager + + val debounceJob = remember { mutableStateOf(null) } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + "Equalizer Settings", + style = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.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( + "Accessibility", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5), + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ), + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent + ) + ) + }, + containerColor = backgroundColor + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (singleMode.value) { + // Single Bud EQ Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Equalizer", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + for (i in 0..7) { + AccessibilitySlider( + label = "EQ${i + 1}", + value = leftEQ.value[i], + onValueChange = { + leftEQ.value = leftEQ.value.copyOf().apply { this[i] = it } + rightEQ.value = rightEQ.value.copyOf().apply { this[i] = it } // Sync to right + onEQChanged() + }, + valueRange = 0f..100f + ) + } + } + } + } else { + // Left Bud EQ Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Left Bud Equalizer", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + for (i in 0..7) { + AccessibilitySlider( + label = "EQ${i + 1}", + value = leftEQ.value[i], + onValueChange = { + leftEQ.value = leftEQ.value.copyOf().apply { this[i] = it } + onEQChanged() + }, + valueRange = 0f..100f + ) + } + } + } + + // Right Bud EQ Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Right Bud Equalizer", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + for (i in 0..7) { + AccessibilitySlider( + label = "EQ${i + 1}", + value = rightEQ.value[i], + onValueChange = { + rightEQ.value = rightEQ.value.copyOf().apply { this[i] = it } + onEQChanged() + }, + valueRange = 0f..100f + ) + } + } + } + } + + // Phone and Media EQ Card + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), + shape = RoundedCornerShape(14.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + "Phone and Media Equalizer", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = textColor, + fontFamily = androidx.compose.ui.text.font.FontFamily( + Font(R.font.sf_pro) + ) + ) + ) + + for (i in 0..7) { + AccessibilitySlider( + label = "EQ${i + 1}", + value = phoneMediaEQ.value[i], + onValueChange = { + phoneMediaEQ.value = phoneMediaEQ.value.copyOf().apply { this[i] = it } + debounceJob.value?.cancel() + debounceJob.value = CoroutineScope(Dispatchers.IO).launch { + delay(100) + aacpManager.sendPhoneMediaEQ(phoneMediaEQ.value) + } + }, + valueRange = 0f..100f + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 7f651f6b..34b60528 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -25,6 +25,8 @@ import me.kavishdevar.librepods.utils.AACPManager.Companion.ControlCommandIdenti import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressBudType.entries import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType.entries import kotlin.io.encoding.ExperimentalEncodingApi +import java.nio.ByteBuffer +import java.nio.ByteOrder /** * Manager class for Apple Accessory Communication Protocol (AACP) @@ -36,18 +38,19 @@ class AACPManager { private const val TAG = "AACPManager" object Opcodes { - const val SET_FEATURE_FLAGS: Byte = 0x4d - const val REQUEST_NOTIFICATIONS: Byte = 0x0f + const val SET_FEATURE_FLAGS: Byte = 0x4D + const val REQUEST_NOTIFICATIONS: Byte = 0x0F const val BATTERY_INFO: Byte = 0x04 const val CONTROL_COMMAND: Byte = 0x09 const val EAR_DETECTION: Byte = 0x06 - const val CONVERSATION_AWARENESS: Byte = 0x4b - const val DEVICE_METADATA: Byte = 0x1d + const val CONVERSATION_AWARENESS: Byte = 0x4B + const val DEVICE_METADATA: Byte = 0x1D const val RENAME: Byte = 0x1E const val HEADTRACKING: Byte = 0x17 const val PROXIMITY_KEYS_REQ: Byte = 0x30 const val PROXIMITY_KEYS_RSP: Byte = 0x31 const val STEM_PRESS: Byte = 0x19 + const val EQ_SETTINGS: Byte = 0x35 } private val HEADER_BYTES = byteArrayOf(0x04, 0x00, 0x04, 0x00) @@ -551,4 +554,42 @@ class AACPManager { return false } } -} + + fun sendEQPacket(eqFloats: FloatArray, phone: Boolean, media: Boolean): Boolean { + val buffer = ByteBuffer.allocate(140).order(ByteOrder.LITTLE_ENDIAN) + buffer.put(0x04) + buffer.put(0x00) + buffer.put(0x04) + buffer.put(0x00) + buffer.put(0x53) + buffer.put(0x00) + buffer.put(0x84.toByte()) + buffer.put(0x00) + buffer.put(0x02) + buffer.put(0x02) + buffer.put(if (phone) 0x01 else 0x00) + buffer.put(if (media) 0x01 else 0x00) + for (i in 0..7) { + buffer.putFloat(eqFloats[i]) + } + while (buffer.hasRemaining()) { + buffer.put(0x00) + } + val packet = buffer.array() + return sendPacket(packet) + } + + fun sendPhoneMediaEQ(eq: FloatArray, phone: Byte = 0x02.toByte(), media: Byte = 0x02.toByte()) { + if (eq.size != 8) throw IllegalArgumentException("EQ must be 8 floats") + val header = byteArrayOf(0x04.toByte(), 0x00.toByte(), 0x04.toByte(), 0x00.toByte(), 0x53.toByte(), 0x00.toByte(), 0x84.toByte(), 0x00.toByte(), 0x02.toByte(), 0x02.toByte(), phone, media) + val buffer = ByteBuffer.allocate(128).order(ByteOrder.LITTLE_ENDIAN) + for (block in 0..3) { + for (i in 0..7) { + buffer.putFloat(eq[i]) + } + } + val payload = buffer.array() + val packet = header + payload + sendPacket(packet) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt index e6a28e82..e9a8c0f9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt @@ -45,6 +45,7 @@ class RadareOffsetFinder(context: Context) { private const val CFG_REQ_OFFSET_PROP = "persist.librepods.cfg_req_offset" private const val CSM_CONFIG_OFFSET_PROP = "persist.librepods.csm_config_offset" private const val PEER_INFO_REQ_OFFSET_PROP = "persist.librepods.peer_info_req_offset" + private const val SDP_OFFSET_PROP = "persist.librepods.sdp_offset" private const val EXTRACT_DIR = "/" private const val RADARE2_BIN_PATH = "$EXTRACT_DIR/data/local/tmp/aln_unzip/org.radare.radare2installer/radare2/bin" @@ -77,7 +78,8 @@ class RadareOffsetFinder(context: Context) { "setprop $HOOK_OFFSET_PROP '' && " + "setprop $CFG_REQ_OFFSET_PROP '' && " + "setprop $CSM_CONFIG_OFFSET_PROP '' && " + - "setprop $PEER_INFO_REQ_OFFSET_PROP ''" + "setprop $PEER_INFO_REQ_OFFSET_PROP ''" + + "setprop $SDP_OFFSET_PROP ''" )) val exitCode = process.waitFor() @@ -422,6 +424,7 @@ class RadareOffsetFinder(context: Context) { // findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup) // findAndSaveL2cCsmConfigOffset(libraryPath, envSetup) // findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup) + findAndSaveSdpOffset(libraryPath, envSetup) } catch (e: Exception) { Log.e(TAG, "Failed to find function offset", e) @@ -572,6 +575,51 @@ class RadareOffsetFinder(context: Context) { } } + private suspend fun findAndSaveSdpOffset(libraryPath: String, envSetup: String) = withContext(Dispatchers.IO) { + try { + val command = "$envSetup && $RADARE2_BIN_PATH/rabin2 -q -E $libraryPath | grep DmSetLocalDiRecord" + Log.d(TAG, "Running command: $command") + + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", command)) + val reader = BufferedReader(InputStreamReader(process.inputStream)) + val errorReader = BufferedReader(InputStreamReader(process.errorStream)) + + var line: String? + var offset = 0L + + while (reader.readLine().also { line = it } != null) { + Log.d(TAG, "rabin2 output: $line") + if (line?.contains("DmSetLocalDiRecord") == true) { + val parts = line.split(" ") + if (parts.isNotEmpty() && parts[0].startsWith("0x")) { + offset = parts[0].substring(2).toLong(16) + Log.d(TAG, "Found DmSetLocalDiRecord offset at ${parts[0]}") + break + } + } + } + + while (errorReader.readLine().also { line = it } != null) { + Log.d(TAG, "rabin2 error: $line") + } + + val exitCode = process.waitFor() + if (exitCode != 0) { + Log.e(TAG, "rabin2 command failed with exit code $exitCode") + } + + if (offset > 0L) { + val hexString = "0x${offset.toString(16)}" + Runtime.getRuntime().exec(arrayOf( + "su", "-c", "setprop $SDP_OFFSET_PROP $hexString" + )).waitFor() + Log.d(TAG, "Saved DmSetLocalDiRecord offset: $hexString") + } + } catch (e: Exception) { + Log.e(TAG, "Failed to find or save DmSetLocalDiRecord offset", e) + } + } + private suspend fun saveOffset(offset: Long): Boolean = withContext(Dispatchers.IO) { try { val hexString = "0x${offset.toString(16)}" From 4ee9b2732fe048005c432fe464b602c48a2063d6 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 8 Sep 2025 00:24:15 +0530 Subject: [PATCH 02/72] docs: update transparency mode format --- AAP Definitions.md | 51 +++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/AAP Definitions.md b/AAP Definitions.md index ef83ff29..42dc6025 100644 --- a/AAP Definitions.md +++ b/AAP Definitions.md @@ -279,50 +279,27 @@ duplicated thrice for some reason ## Customize Transparency mode ``` -52 18 00 -For left bud -[Enabled] +12 18 00 [enabled] + [EQ1][EQ2][EQ3][EQ4][EQ5][EQ6][EQ7][EQ8] [Amplification] [Tone] [Conversation Boost] [Ambient Noise Reduction] -00 0080 3F - + ``` - - - - - -All values are formatted as Little Endian from float values. -| Data | Type | Value range | -|---------------------|---------------|-------------| -| Enabled | Little Endian | 0 or 1 | -| EQ | Little Endian | 0 to 100 | -| Amplification | Little Endian | -1 to 1 | -| Tone | Little Endian | -1 to 1 | -| Conversation Boost | Little Endian | 0 or 1 | + +All values are formatted as IEEE 754 floats in little endian order. +| Data | Type | Range | +|-------------------------|---------------|-------| +| Enabled | IEEE754 Float | 0/1 | +| EQ | IEEE754 Float | 0-100 | +| Amplification | IEEE754 Float | 0-2 | +| Tone | IEEE754 Float | 0-2 | +| Conversation Boost | IEEE754 Float | 0/1 | +| Ambient Noise Reduction | IEEE754 Float | 0-1 | +| Ambient Noise Reduction | IEEE754 Float | 0-1 | > [!IMPORTANT] > Also send the [Headphone Accomodation](#headphone-accomodation) after this. From d1bf5407c9b4aa0d1b5f122803d28ea342e2360e Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 9 Sep 2025 16:33:07 +0530 Subject: [PATCH 03/72] android: don't start service every time MainActivity is launched --- .../app/src/main/java/me/kavishdevar/librepods/MainActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 92be4eae..1d358135 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -291,7 +291,6 @@ fun Main() { if (permissionState.allPermissionsGranted && (canDrawOverlays || overlaySkipped.value)) { val context = LocalContext.current - context.startService(Intent(context, AirPodsService::class.java)) val navController = rememberNavController() @@ -378,7 +377,7 @@ fun Main() { } } - serviceConnection = remember { + serviceConnection = remember { object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val binder = service as AirPodsService.LocalBinder From df9f443173fcc6719cb7e40619385f233dfbaeb0 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Wed, 10 Sep 2025 10:03:52 +0530 Subject: [PATCH 04/72] android: add basic multidevice capabilities use at your own risk, may or may not work --- android/app/src/main/AndroidManifest.xml | 3 +- .../screens/AirPodsSettingsScreen.kt | 6 +- .../librepods/services/AirPodsService.kt | 2943 +++++++++-------- .../librepods/utils/AACPManager.kt | 644 +++- .../librepods/utils/IslandWindow.kt | 47 +- .../librepods/utils/MediaController.kt | 88 +- android/app/src/main/res/drawable/ic_undo.xml | 11 + .../main/res/drawable/ic_undo_button_bg.xml | 5 + .../app/src/main/res/layout/island_window.xml | 23 +- android/app/src/main/res/values/strings.xml | 4 +- 10 files changed, 2308 insertions(+), 1466 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_undo.xml create mode 100644 android/app/src/main/res/drawable/ic_undo_button_bg.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a6a5ae4f..e7f26a17 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -30,9 +30,10 @@ - + diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 97bdaebd..c6e68ec8 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -387,9 +387,6 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, default = false, controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION ) - - Spacer(modifier = Modifier.height(16.dp)) - AccessibilitySettings() } Spacer(modifier = Modifier.height(16.dp)) @@ -403,6 +400,9 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, // Only show debug when not in BLE-only mode if (!bleOnlyMode) { + Spacer(modifier = Modifier.height(16.dp)) + AccessibilitySettings() + Spacer(modifier = Modifier.height(16.dp)) NavigationButton("debug", "Debug", navController) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index f8cb4087..f6ab0571 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -143,8 +143,10 @@ object ServiceManager { @ExperimentalEncodingApi class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeListener { var macAddress = "" + var localMac = "" lateinit var aacpManager: AACPManager var cameraActive = false + private var disconnectedBecauseReversed = false data class ServiceConfig( var deviceName: String = "AirPods", var earDetectionEnabled: Boolean = true, @@ -311,10 +313,11 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList updateBattery() Log.d("AirPodsBLEService", "Battery changed") } - } + override fun onCreate() { super.onCreate() + sharedPreferencesLogs = getSharedPreferences("packet_logs", MODE_PRIVATE) inMemoryLogs.addAll(sharedPreferencesLogs.getStringSet(packetLogKey, emptySet()) ?: emptySet()) @@ -327,1586 +330,1690 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList initializeAACPManagerCallback() sharedPreferences.registerOnSharedPreferenceChangeListener(this) - } - - fun cameraOpened() { - Log.d("AirPodsService", "Camera opened, gonna handle stem presses and take action if enabled") - val isCameraShutterUsed = listOf( - config.leftSinglePressAction, - config.rightSinglePressAction, - config.leftDoublePressAction, - config.rightDoublePressAction, - config.leftTriplePressAction, - config.rightTriplePressAction, - config.leftLongPressAction, - config.rightLongPressAction - ).any { it == StemAction.CAMERA_SHUTTER } - if (isCameraShutterUsed) { - Log.d("AirPodsService", "Camera opened, setting up stem actions") - cameraActive = true - setupStemActions(isCameraActive = true) - } - } + val process = Runtime.getRuntime().exec(arrayOf("su", "-c", "settings", "get", "secure", "bluetooth_address")) + val output = process.inputStream.bufferedReader().use { it.readLine() } + localMac = output.trim() - fun cameraClosed() { - cameraActive = false - setupStemActions() - } + ServiceManager.setService(this) + startForegroundNotification() + initGestureDetector() - fun isCustomAction( - action: StemAction?, - default: StemAction?, - isCameraActive: Boolean = false - ): Boolean { - Log.d("AirPodsService", "Checking if action $action is custom against default $default, camera active: $isCameraActive") - return action != default && (action != StemAction.CAMERA_SHUTTER || isCameraActive) - } + bleManager = BLEManager(this) + bleManager.setAirPodsStatusListener(bleStatusListener) - fun setupStemActions(isCameraActive: Boolean = false) { - val singlePressDefault = StemAction.defaultActions[StemPressType.SINGLE_PRESS] - val doublePressDefault = StemAction.defaultActions[StemPressType.DOUBLE_PRESS] - val triplePressDefault = StemAction.defaultActions[StemPressType.TRIPLE_PRESS] - val longPressDefault = StemAction.defaultActions[StemPressType.LONG_PRESS] + sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) - val singlePressCustomized = isCustomAction(config.leftSinglePressAction, singlePressDefault, isCameraActive) || - isCustomAction(config.rightSinglePressAction, singlePressDefault, isCameraActive) - val doublePressCustomized = isCustomAction(config.leftDoublePressAction, doublePressDefault, isCameraActive) || - isCustomAction(config.rightDoublePressAction, doublePressDefault, isCameraActive) - val triplePressCustomized = isCustomAction(config.leftTriplePressAction, triplePressDefault, isCameraActive) || - isCustomAction(config.rightTriplePressAction, triplePressDefault, isCameraActive) - val longPressCustomized = isCustomAction(config.leftLongPressAction, longPressDefault, isCameraActive) || - isCustomAction(config.rightLongPressAction, longPressDefault, isCameraActive) - Log.d("AirPodsService", "Setting up stem actions: " + - "Single Press Customized: $singlePressCustomized, " + - "Double Press Customized: $doublePressCustomized, " + - "Triple Press Customized: $triplePressCustomized, " + - "Long Press Customized: $longPressCustomized") - aacpManager.sendStemConfigPacket( - singlePressCustomized, - doublePressCustomized, - triplePressCustomized, - longPressCustomized, - ) - } + with(sharedPreferences) { + val editor = edit() - @ExperimentalEncodingApi - private fun initializeAACPManagerCallback() { - aacpManager.setPacketCallback(object : AACPManager.PacketCallback { - @SuppressLint("MissingPermission") - override fun onBatteryInfoReceived(batteryInfo: ByteArray) { - batteryNotification.setBattery(batteryInfo) - sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { - putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery())) - }) - updateBattery() - updateNotificationContent( - true, - this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE) - .getString("name", device?.name), - batteryNotification.getBattery() - ) - CrossDevice.sendRemotePacket(batteryInfo) - CrossDevice.batteryBytes = batteryInfo + if (!contains("conversational_awareness_pause_music")) editor.putBoolean("conversational_awareness_pause_music", false) + if (!contains("personalized_volume")) editor.putBoolean("personalized_volume", false) + if (!contains("automatic_ear_detection")) editor.putBoolean("automatic_ear_detection", true) + if (!contains("long_press_nc")) editor.putBoolean("long_press_nc", true) + if (!contains("off_listening_mode")) editor.putBoolean("off_listening_mode", false) + if (!contains("show_phone_battery_in_widget")) editor.putBoolean("show_phone_battery_in_widget", true) + if (!contains("single_anc")) editor.putBoolean("single_anc", true) + if (!contains("long_press_transparency")) editor.putBoolean("long_press_transparency", true) + if (!contains("conversational_awareness")) editor.putBoolean("conversational_awareness", true) + if (!contains("relative_conversational_awareness_volume")) editor.putBoolean("relative_conversational_awareness_volume", true) + if (!contains("long_press_adaptive")) editor.putBoolean("long_press_adaptive", true) + if (!contains("loud_sound_reduction")) editor.putBoolean("loud_sound_reduction", true) + if (!contains("long_press_off")) editor.putBoolean("long_press_off", false) + if (!contains("volume_control")) editor.putBoolean("volume_control", true) + if (!contains("head_gestures")) editor.putBoolean("head_gestures", true) + if (!contains("disconnect_when_not_wearing")) editor.putBoolean("disconnect_when_not_wearing", false) - for (battery in batteryNotification.getBattery()) { - Log.d( - "AirPodsParser", - "${battery.getComponentName()}: ${battery.getStatusName()} at ${battery.level}% " - ) - } + // AirPods state-based takeover + if (!contains("takeover_when_disconnected")) editor.putBoolean("takeover_when_disconnected", true) + if (!contains("takeover_when_idle")) editor.putBoolean("takeover_when_idle", true) + if (!contains("takeover_when_music")) editor.putBoolean("takeover_when_music", false) + if (!contains("takeover_when_call")) editor.putBoolean("takeover_when_call", true) - if (batteryNotification.getBattery()[0].status == BatteryStatus.CHARGING && batteryNotification.getBattery()[1].status == BatteryStatus.CHARGING) { - disconnectAudio(this@AirPodsService, device) - } else { - connectAudio(this@AirPodsService, device) - } - } + // Phone state-based takeover + if (!contains("takeover_when_ringing_call")) editor.putBoolean("takeover_when_ringing_call", true) + if (!contains("takeover_when_media_start")) editor.putBoolean("takeover_when_media_start", true) - override fun onEarDetectionReceived(earDetection: ByteArray) { - sendBroadcast(Intent(AirPodsNotifications.EAR_DETECTION_DATA).apply { - val list = earDetectionNotification.status - val bytes = ByteArray(2) - bytes[0] = list[0] - bytes[1] = list[1] - putExtra("data", bytes) - }) - Log.d( - "AirPodsParser", - "Ear Detection: ${earDetectionNotification.status[0]} ${earDetectionNotification.status[1]}" - ) - processEarDetectionChange(earDetection) - } + if (!contains("adaptive_strength")) editor.putInt("adaptive_strength", 51) + if (!contains("tone_volume")) editor.putInt("tone_volume", 75) + if (!contains("conversational_awareness_volume")) editor.putInt("conversational_awareness_volume", 43) - override fun onConversationAwarenessReceived(conversationAwareness: ByteArray) { - conversationAwarenessNotification.setData(conversationAwareness) - sendBroadcast(Intent(AirPodsNotifications.CA_DATA).apply { - putExtra("data", conversationAwarenessNotification.status) - }) + if (!contains("textColor")) editor.putLong("textColor", -1L) - if (conversationAwarenessNotification.status == 1.toByte() || conversationAwarenessNotification.status == 2.toByte()) { - MediaController.startSpeaking() - } else if (conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) { - MediaController.stopSpeaking() - } + if (!contains("qs_click_behavior")) editor.putString("qs_click_behavior", "cycle") + if (!contains("name")) editor.putString("name", "AirPods") + if (!contains("ble_only_mode")) editor.putBoolean("ble_only_mode", false) - Log.d( - "AirPodsParser", - "Conversation Awareness: ${conversationAwarenessNotification.status}" - ) - } + if (!contains("left_single_press_action")) editor.putString("left_single_press_action", + StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name) + if (!contains("right_single_press_action")) editor.putString("right_single_press_action", + StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name) + if (!contains("left_double_press_action")) editor.putString("left_double_press_action", + StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name) + if (!contains("right_double_press_action")) editor.putString("right_double_press_action", + StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name) + if (!contains("left_triple_press_action")) editor.putString("left_triple_press_action", + StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name) + if (!contains("right_triple_press_action")) editor.putString("right_triple_press_action", + StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name) + if (!contains("left_long_press_action")) editor.putString("left_long_press_action", + StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name) + if (!contains("right_long_press_action")) editor.putString("right_long_press_action", + StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name) - override fun onControlCommandReceived(controlCommand: ByteArray) { - val command = AACPManager.ControlCommand.fromByteArray(controlCommand) - if (command.identifier == AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value) { - ancNotification.setStatus(byteArrayOf(command.value.takeIf { it.isNotEmpty() }?.get(0) ?: 0x00.toByte())) - sendANCBroadcast() - updateNoiseControlWidget() - } - } + editor.apply() + } - override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) { + initializeConfig() - } + ancModeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == "me.kavishdevar.librepods.SET_ANC_MODE") { + if (intent.hasExtra("mode")) { + val mode = intent.getIntExtra("mode", -1) + if (mode in 1..4) { + aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value, + mode + ) + } + } else { + val currentMode = ancNotification.status + val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } + val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() - @SuppressLint("NewApi") - override fun onHeadTrackingReceived(headTracking: ByteArray) { - if (isHeadTrackingActive) { - HeadTracking.processPacket(headTracking) - processHeadTrackingData(headTracking) - } - } + val nextMode = if (allowOffMode) { + when (currentMode) { + 1 -> 2 + 2 -> 3 + 3 -> 4 + 4 -> 1 + else -> 1 + } + } else { + when (currentMode) { + 1 -> 2 + 2 -> 3 + 3 -> 4 + 4 -> 2 + else -> 2 + } + } - override fun onProximityKeysReceived(proximityKeys: ByteArray) { - val keys = aacpManager.parseProximityKeysResponse(proximityKeys) - Log.d("AirPodsParser", "Proximity keys: $keys") - sharedPreferences.edit { - for (key in keys) { - Log.d("AirPodsParser", "Proximity key: ${key.key.name} = ${key.value}") - putString(key.key.name, Base64.encode(key.value)) + aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value, + nextMode + ) + Log.d("AirPodsService", "Cycling ANC mode from $currentMode to $nextMode (offListeningMode: $allowOffMode)") } } } + } - override fun onStemPressReceived(stemPress: ByteArray) { - val (stemPressType, bud) = aacpManager.parseStemPressResponse(stemPress) - - Log.d("AirPodsParser", "Stem press received: $stemPressType on $bud") - - val action = getActionFor(bud, stemPressType) - Log.d("AirPodsParser", "$bud $stemPressType action: $action") - - action?.let { executeStemAction(it) } - } - - override fun onUnknownPacketReceived(packet: ByteArray) { - Log.d("AACPManager", "Unknown packet received: ${packet.joinToString(" ") { "%02X".format(it) }}") - } - }) - } - - private fun getActionFor(bud: AACPManager.Companion.StemPressBudType, type: StemPressType): StemAction? { - return when (type) { - StemPressType.SINGLE_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftSinglePressAction else config.rightSinglePressAction - StemPressType.DOUBLE_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftDoublePressAction else config.rightDoublePressAction - StemPressType.TRIPLE_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftTriplePressAction else config.rightTriplePressAction - StemPressType.LONG_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftLongPressAction else config.rightLongPressAction - } - } - - private fun executeStemAction(action: StemAction) { - when (action) { - StemAction.defaultActions[StemPressType.SINGLE_PRESS] -> { - Log.d("AirPodsParser", "Default single press action: Play/Pause, not taking action.") - } - StemAction.PLAY_PAUSE -> MediaController.sendPlayPause() - StemAction.PREVIOUS_TRACK -> MediaController.sendPreviousTrack() - StemAction.NEXT_TRACK -> MediaController.sendNextTrack() - StemAction.CAMERA_SHUTTER -> Runtime.getRuntime().exec(arrayOf("su", "-c", "input keyevent 27")) - StemAction.DIGITAL_ASSISTANT -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - val intent = Intent(Intent.ACTION_VOICE_COMMAND).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - startActivity(intent) - } else { - Log.w("AirPodsParser", "Digital Assistant action is not supported on this Android version.") - } - } - StemAction.CYCLE_NOISE_CONTROL_MODES -> { - Log.d("AirPodsParser", "Cycling noise control modes") - sendBroadcast(Intent("me.kavishdevar.librepods.SET_ANC_MODE")) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(ancModeReceiver, ancModeFilter, RECEIVER_EXPORTED) + } else { + registerReceiver(ancModeReceiver, ancModeFilter) } - } - - private fun processEarDetectionChange(earDetection: ByteArray) { - var inEar = false - var inEarData = listOf(earDetectionNotification.status[0] == 0x00.toByte(), earDetectionNotification.status[1] == 0x00.toByte()) - var justEnabledA2dp = false - earDetectionNotification.setStatus(earDetection) - if (config.earDetectionEnabled) { - val data = earDetection.copyOfRange(earDetection.size - 2, earDetection.size) - inEar = data[0] == 0x00.toByte() && data[1] == 0x00.toByte() - - val newInEarData = listOf( - data[0] == 0x00.toByte(), - data[1] == 0x00.toByte() + val audioManager = + this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager + MediaController.initialize( + audioManager, + this@AirPodsService.getSharedPreferences( + "settings", + MODE_PRIVATE ) + ) + Log.d("AirPodsService", "Initializing CrossDevice") + CoroutineScope(Dispatchers.IO).launch { + CrossDevice.init(this@AirPodsService) + Log.d("AirPodsService", "CrossDevice initialized") + } - if (inEarData.sorted() == listOf(false, false) && newInEarData.sorted() != listOf(false, false) && islandWindow?.isVisible != true) { - showIsland( - this@AirPodsService, - (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0)) - } - - if (newInEarData == listOf(false, false) && islandWindow?.isVisible == true) { - islandWindow?.close() - } + sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) + macAddress = sharedPreferences.getString("mac_address", "") ?: "" - if (newInEarData.contains(true) && inEarData == listOf(false, false)) { - connectAudio(this@AirPodsService, device) - justEnabledA2dp = true - registerA2dpConnectionReceiver() - if (MediaController.getMusicActive()) { - MediaController.userPlayedTheMedia = true - } - } else if (newInEarData == listOf(false, false)) { - MediaController.sendPause(force = true) - if (config.disconnectWhenNotWearing) { - disconnectAudio(this@AirPodsService, device) + telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager + phoneStateListener = object : PhoneStateListener() { + @SuppressLint("SwitchIntDef", "NewApi") + override fun onCallStateChanged(state: Int, phoneNumber: String?) { + super.onCallStateChanged(state, phoneNumber) + when (state) { + TelephonyManager.CALL_STATE_RINGING -> { + val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true + if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope(Dispatchers.IO).launch { + takeOver("call") + } + if (config.headGestures) { + callNumber = phoneNumber + handleIncomingCall() + } + } + TelephonyManager.CALL_STATE_OFFHOOK -> { + val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true + if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope( + Dispatchers.IO).launch { + takeOver("call") + } + isInCall = true + } + TelephonyManager.CALL_STATE_IDLE -> { + isInCall = false + callNumber = null + gestureDetector?.stopDetection() + } } } + } + telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE) - if (inEarData.contains(false) && newInEarData == listOf(true, true)) { - Log.d("AirPodsParser", "User put in both AirPods from just one.") - MediaController.userPlayedTheMedia = false + if (config.showPhoneBatteryInWidget) { + widgetMobileBatteryEnabled = true + val batteryChangedIntentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) + batteryChangedIntentFilter.addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver( + BatteryChangedIntentReceiver, + batteryChangedIntentFilter, + RECEIVER_EXPORTED + ) + } else { + registerReceiver(BatteryChangedIntentReceiver, batteryChangedIntentFilter) } + } + val serviceIntentFilter = IntentFilter().apply { + addAction("android.bluetooth.device.action.ACL_CONNECTED") + addAction("android.bluetooth.device.action.ACL_DISCONNECTED") + addAction("android.bluetooth.device.action.BOND_STATE_CHANGED") + addAction("android.bluetooth.device.action.NAME_CHANGED") + addAction("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED") + addAction("android.bluetooth.adapter.action.STATE_CHANGED") + addAction("android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED") + addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT") + addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") + addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED") + } - if (newInEarData.contains(false) && inEarData == listOf(true, true)) { - Log.d("AirPodsParser", "User took one of two out.") - MediaController.userPlayedTheMedia = false - } + connectionReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) { + device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra("device", BluetoothDevice::class.java)!! + } else { + intent.getParcelableExtra("device") as BluetoothDevice? + } - Log.d("AirPodsParser", "inEarData: ${inEarData.sorted()}, newInEarData: ${newInEarData.sorted()}") + if (config.deviceName == "AirPods" && device?.name != null) { + config.deviceName = device?.name ?: "AirPods" + sharedPreferences.edit { putString("name", config.deviceName) } + } - if (newInEarData.sorted() != inEarData.sorted()) { - inEarData = newInEarData - if (inEar == true) { - if (!justEnabledA2dp) { - justEnabledA2dp = false - MediaController.sendPlay() - MediaController.iPausedTheMedia = false + Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString()) + if (!CrossDevice.isAvailable && !config.bleOnlyMode) { + Log.d("AirPodsService", "${config.deviceName} connected") + CoroutineScope(Dispatchers.IO).launch { + connectToSocket(device!!) + } + Log.d("AirPodsService", "Setting metadata") + setMetadatas(device!!) + isConnectedLocally = true + macAddress = device!!.address + sharedPreferences.edit { + putString("mac_address", macAddress) + } + } else if (config.bleOnlyMode) { + Log.d("AirPodsService", "BLE-only mode: skipping L2CAP connection") + macAddress = device!!.address + sharedPreferences.edit { + putString("mac_address", macAddress) + } } - } else { - MediaController.sendPause() + } else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) { + device = null + isConnectedLocally = false + popupShown = false + updateNotificationContent(false) } } } - } - - private fun registerA2dpConnectionReceiver() { - val a2dpConnectionStateReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") { - val state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) - val previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED) - val device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) - - Log.d("MediaController", "A2DP state changed: $previousState -> $state for device: ${device?.address}") - - if (state == BluetoothProfile.STATE_CONNECTED && - previousState != BluetoothProfile.STATE_CONNECTED && - device?.address == this@AirPodsService.device?.address) { - - Log.d("MediaController", "A2DP connected, sending play command") - MediaController.sendPlay() - MediaController.iPausedTheMedia = false - - context.unregisterReceiver(this) + val showIslandReceiver = object: BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == "me.kavishdevar.librepods.cross_device_island") { + showIsland(this@AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!)) + } else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { + try { + context?.unregisterReceiver(this) + } catch (e: Exception) { + e.printStackTrace() } } } } - val a2dpIntentFilter = IntentFilter("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") + val showIslandIntentFilter = IntentFilter().apply { + addAction("me.kavishdevar.librepods.cross_device_island") + addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(a2dpConnectionStateReceiver, a2dpIntentFilter, RECEIVER_EXPORTED) + registerReceiver(showIslandReceiver, showIslandIntentFilter, RECEIVER_EXPORTED) } else { - registerReceiver(a2dpConnectionStateReceiver, a2dpIntentFilter) + registerReceiver(showIslandReceiver, showIslandIntentFilter) } - } - - private fun initializeConfig() { - config = ServiceConfig( - deviceName = sharedPreferences.getString("name", "AirPods") ?: "AirPods", - earDetectionEnabled = sharedPreferences.getBoolean("automatic_ear_detection", true), - conversationalAwarenessPauseMusic = sharedPreferences.getBoolean("conversational_awareness_pause_music", false), - showPhoneBatteryInWidget = sharedPreferences.getBoolean("show_phone_battery_in_widget", true), - relativeConversationalAwarenessVolume = sharedPreferences.getBoolean("relative_conversational_awareness_volume", true), - headGestures = sharedPreferences.getBoolean("head_gestures", true), - disconnectWhenNotWearing = sharedPreferences.getBoolean("disconnect_when_not_wearing", false), - conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 43), - textColor = sharedPreferences.getLong("textColor", -1L), - qsClickBehavior = sharedPreferences.getString("qs_click_behavior", "cycle") ?: "cycle", - bleOnlyMode = sharedPreferences.getBoolean("ble_only_mode", false), - // AirPods state-based takeover - takeoverWhenDisconnected = sharedPreferences.getBoolean("takeover_when_disconnected", true), - takeoverWhenIdle = sharedPreferences.getBoolean("takeover_when_idle", true), - takeoverWhenMusic = sharedPreferences.getBoolean("takeover_when_music", false), - takeoverWhenCall = sharedPreferences.getBoolean("takeover_when_call", true), + val deviceIntentFilter = IntentFilter().apply { + addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) + addAction(AirPodsNotifications.AIRPODS_DISCONNECTED) + } - // Phone state-based takeover - takeoverWhenRingingCall = sharedPreferences.getBoolean("takeover_when_ringing_call", true), - takeoverWhenMediaStart = sharedPreferences.getBoolean("takeover_when_media_start", true), - - // Stem actions - leftSinglePressAction = StemAction.fromString(sharedPreferences.getString("left_single_press_action", "PLAY_PAUSE") ?: "PLAY_PAUSE")!!, - rightSinglePressAction = StemAction.fromString(sharedPreferences.getString("right_single_press_action", "PLAY_PAUSE") ?: "PLAY_PAUSE")!!, - - leftDoublePressAction = StemAction.fromString(sharedPreferences.getString("left_double_press_action", "PREVIOUS_TRACK") ?: "NEXT_TRACK")!!, - rightDoublePressAction = StemAction.fromString(sharedPreferences.getString("right_double_press_action", "NEXT_TRACK") ?: "NEXT_TRACK")!!, - - leftTriplePressAction = StemAction.fromString(sharedPreferences.getString("left_triple_press_action", "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK")!!, - rightTriplePressAction = StemAction.fromString(sharedPreferences.getString("right_triple_press_action", "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK")!!, - - leftLongPressAction = StemAction.fromString(sharedPreferences.getString("left_long_press_action", "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES")!!, - rightLongPressAction = StemAction.fromString(sharedPreferences.getString("right_long_press_action", "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT")!! - ) - } - - override fun onSharedPreferenceChanged(preferences: SharedPreferences?, key: String?) { - if (preferences == null || key == null) return - - when(key) { - "name" -> config.deviceName = preferences.getString(key, "AirPods") ?: "AirPods" - "automatic_ear_detection" -> config.earDetectionEnabled = preferences.getBoolean(key, true) - "conversational_awareness_pause_music" -> config.conversationalAwarenessPauseMusic = preferences.getBoolean(key, false) - "show_phone_battery_in_widget" -> { - config.showPhoneBatteryInWidget = preferences.getBoolean(key, true) - widgetMobileBatteryEnabled = config.showPhoneBatteryInWidget - updateBattery() - } - "relative_conversational_awareness_volume" -> config.relativeConversationalAwarenessVolume = preferences.getBoolean(key, true) - "head_gestures" -> config.headGestures = preferences.getBoolean(key, true) - "disconnect_when_not_wearing" -> config.disconnectWhenNotWearing = preferences.getBoolean(key, false) - "conversational_awareness_volume" -> config.conversationalAwarenessVolume = preferences.getInt(key, 43) - "textColor" -> config.textColor = preferences.getLong(key, -1L) - "qs_click_behavior" -> config.qsClickBehavior = preferences.getString(key, "cycle") ?: "cycle" - "ble_only_mode" -> config.bleOnlyMode = preferences.getBoolean(key, false) - - // AirPods state-based takeover - "takeover_when_disconnected" -> config.takeoverWhenDisconnected = preferences.getBoolean(key, true) - "takeover_when_idle" -> config.takeoverWhenIdle = preferences.getBoolean(key, true) - "takeover_when_music" -> config.takeoverWhenMusic = preferences.getBoolean(key, false) - "takeover_when_call" -> config.takeoverWhenCall = preferences.getBoolean(key, true) - - // Phone state-based takeover - "takeover_when_ringing_call" -> config.takeoverWhenRingingCall = preferences.getBoolean(key, true) - "takeover_when_media_start" -> config.takeoverWhenMediaStart = preferences.getBoolean(key, true) - - "left_single_press_action" -> { - config.leftSinglePressAction = StemAction.fromString( - preferences.getString(key, "PLAY_PAUSE") ?: "PLAY_PAUSE" - )!! - setupStemActions() - } - "right_single_press_action" -> { - config.rightSinglePressAction = StemAction.fromString( - preferences.getString(key, "PLAY_PAUSE") ?: "PLAY_PAUSE" - )!! - setupStemActions() - } - "left_double_press_action" -> { - config.leftDoublePressAction = StemAction.fromString( - preferences.getString(key, "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK" - )!! - setupStemActions() - } - "right_double_press_action" -> { - config.rightDoublePressAction = StemAction.fromString( - preferences.getString(key, "NEXT_TRACK") ?: "NEXT_TRACK" - )!! - setupStemActions() - } - "left_triple_press_action" -> { - config.leftTriplePressAction = StemAction.fromString( - preferences.getString(key, "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK" - )!! - setupStemActions() - } - "right_triple_press_action" -> { - config.rightTriplePressAction = StemAction.fromString( - preferences.getString(key, "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK" - )!! - setupStemActions() - } - "left_long_press_action" -> { - config.leftLongPressAction = StemAction.fromString( - preferences.getString(key, "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES" - )!! - setupStemActions() - } - "right_long_press_action" -> { - config.rightLongPressAction = StemAction.fromString( - preferences.getString(key, "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT" - )!! - setupStemActions() - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED) + registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED) + } else { + registerReceiver(connectionReceiver, deviceIntentFilter) + registerReceiver(bluetoothReceiver, serviceIntentFilter) } - if (key == "mac_address") { - macAddress = preferences.getString(key, "") ?: "" - } - } + val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter - private fun logPacket(packet: ByteArray, source: String) { - val packetHex = packet.joinToString(" ") { "%02X".format(it) } - val logEntry = "$source: $packetHex" + bluetoothAdapter.bondedDevices.forEach { device -> + device.fetchUuidsWithSdp() + if (device.uuids != null) { + if (device.uuids.contains(ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + bluetoothAdapter.getProfileProxy( + this, + object : BluetoothProfile.ServiceListener { + override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { + if (profile == BluetoothProfile.A2DP) { + val connectedDevices = proxy.connectedDevices + if (connectedDevices.isNotEmpty()) { + if (!CrossDevice.isAvailable && !config.bleOnlyMode) { + CoroutineScope(Dispatchers.IO).launch { + connectToSocket(device) + } + setMetadatas(device) + macAddress = device.address + sharedPreferences.edit { + putString("mac_address", macAddress) + } + } else if (config.bleOnlyMode) { + Log.d("AirPodsService", "BLE-only mode: skipping L2CAP connection") + macAddress = device.address + sharedPreferences.edit { + putString("mac_address", macAddress) + } + } + this@AirPodsService.sendBroadcast( + Intent(AirPodsNotifications.AIRPODS_CONNECTED) + ) + } + } + bluetoothAdapter.closeProfileProxy(profile, proxy) + } - synchronized(inMemoryLogs) { - inMemoryLogs.add(logEntry) - if (inMemoryLogs.size > maxLogEntries) { - inMemoryLogs.iterator().next().let { - inMemoryLogs.remove(it) + override fun onServiceDisconnected(profile: Int) {} + }, + BluetoothProfile.A2DP + ) } } + } - _packetLogsFlow.value = inMemoryLogs.toSet() + if (!isConnectedLocally && !CrossDevice.isAvailable) { + clearPacketLogs() } CoroutineScope(Dispatchers.IO).launch { - val logs = sharedPreferencesLogs.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet() - ?: mutableSetOf() - logs.add(logEntry) - - if (logs.size > maxLogEntries) { - val toKeep = logs.toList().takeLast(maxLogEntries).toSet() - sharedPreferencesLogs.edit { putStringSet(packetLogKey, toKeep) } - } else { - sharedPreferencesLogs.edit { putStringSet(packetLogKey, logs) } - } + bleManager.startScanning() } } - fun getPacketLogs(): Set { - return inMemoryLogs.toSet() - } + fun cameraOpened() { + Log.d("AirPodsService", "Camera opened, gonna handle stem presses and take action if enabled") + val isCameraShutterUsed = listOf( + config.leftSinglePressAction, + config.rightSinglePressAction, + config.leftDoublePressAction, + config.rightDoublePressAction, + config.leftTriplePressAction, + config.rightTriplePressAction, + config.leftLongPressAction, + config.rightLongPressAction + ).any { it == StemAction.CAMERA_SHUTTER } - private fun clearPacketLogs() { - synchronized(inMemoryLogs) { - inMemoryLogs.clear() - _packetLogsFlow.value = emptySet() + if (isCameraShutterUsed) { + Log.d("AirPodsService", "Camera opened, setting up stem actions") + cameraActive = true + setupStemActions(isCameraActive = true) } - sharedPreferencesLogs.edit { remove(packetLogKey) } } - fun clearLogs() { - clearPacketLogs() - _packetLogsFlow.value = emptySet() + fun cameraClosed() { + cameraActive = false + setupStemActions() } - override fun onBind(intent: Intent?): IBinder { - return LocalBinder() + fun isCustomAction( + action: StemAction?, + default: StemAction?, + isCameraActive: Boolean = false + ): Boolean { + Log.d("AirPodsService", "Checking if action $action is custom against default $default, camera active: $isCameraActive") + return action != default && (action != StemAction.CAMERA_SHUTTER || isCameraActive) } - private var gestureDetector: GestureDetector? = null - private var isInCall = false - private var callNumber: String? = null + fun setupStemActions(isCameraActive: Boolean = false) { + val singlePressDefault = StemAction.defaultActions[StemPressType.SINGLE_PRESS] + val doublePressDefault = StemAction.defaultActions[StemPressType.DOUBLE_PRESS] + val triplePressDefault = StemAction.defaultActions[StemPressType.TRIPLE_PRESS] + val longPressDefault = StemAction.defaultActions[StemPressType.LONG_PRESS] - @RequiresApi(Build.VERSION_CODES.Q) - private fun initGestureDetector() { - if (gestureDetector == null) { - gestureDetector = GestureDetector(this) - } + val singlePressCustomized = isCustomAction(config.leftSinglePressAction, singlePressDefault, isCameraActive) || + isCustomAction(config.rightSinglePressAction, singlePressDefault, isCameraActive) + val doublePressCustomized = isCustomAction(config.leftDoublePressAction, doublePressDefault, isCameraActive) || + isCustomAction(config.rightDoublePressAction, doublePressDefault, isCameraActive) + val triplePressCustomized = isCustomAction(config.leftTriplePressAction, triplePressDefault, isCameraActive) || + isCustomAction(config.rightTriplePressAction, triplePressDefault, isCameraActive) + val longPressCustomized = isCustomAction(config.leftLongPressAction, longPressDefault, isCameraActive) || + isCustomAction(config.rightLongPressAction, longPressDefault, isCameraActive) + Log.d("AirPodsService", "Setting up stem actions: " + + "Single Press Customized: $singlePressCustomized, " + + "Double Press Customized: $doublePressCustomized, " + + "Triple Press Customized: $triplePressCustomized, " + + "Long Press Customized: $longPressCustomized") + aacpManager.sendStemConfigPacket( + singlePressCustomized, + doublePressCustomized, + triplePressCustomized, + longPressCustomized, + ) } + @ExperimentalEncodingApi + private fun initializeAACPManagerCallback() { + aacpManager.setPacketCallback(object : AACPManager.PacketCallback { + @SuppressLint("MissingPermission") + override fun onBatteryInfoReceived(batteryInfo: ByteArray) { + batteryNotification.setBattery(batteryInfo) + sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { + putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery())) + }) + updateBattery() + updateNotificationContent( + true, + this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE) + .getString("name", device?.name), + batteryNotification.getBattery() + ) + CrossDevice.sendRemotePacket(batteryInfo) + CrossDevice.batteryBytes = batteryInfo - var popupShown = false - fun showPopup(service: Service, name: String) { - if (!Settings.canDrawOverlays(service)) { - Log.d("AirPodsService", "No permission for SYSTEM_ALERT_WINDOW") - return - } - if (popupShown) { - return - } - val popupWindow = PopupWindow(service.applicationContext) - popupWindow.open(name, batteryNotification) - popupShown = true - } - - var islandOpen = false - var islandWindow: IslandWindow? = null - @SuppressLint("MissingPermission") - fun showIsland(service: Service, batteryPercentage: Int, type: IslandType = IslandType.CONNECTED) { - Log.d("AirPodsService", "Showing island window") - if (!Settings.canDrawOverlays(service)) { - Log.d("AirPodsService", "No permission for SYSTEM_ALERT_WINDOW") - return - } - CoroutineScope(Dispatchers.Main).launch { - islandWindow = IslandWindow(service.applicationContext) - islandWindow!!.show(sharedPreferences.getString("name", "AirPods Pro").toString(), batteryPercentage, this@AirPodsService, type) - } - } + for (battery in batteryNotification.getBattery()) { + Log.d( + "AirPodsParser", + "${battery.getComponentName()}: ${battery.getStatusName()} at ${battery.level}% " + ) + } - @OptIn(ExperimentalMaterial3Api::class) - fun startMainActivity() { - val intent = Intent(this, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } + if (batteryNotification.getBattery()[0].status == BatteryStatus.CHARGING && batteryNotification.getBattery()[1].status == BatteryStatus.CHARGING) { + disconnectAudio(this@AirPodsService, device) + } else { + connectAudio(this@AirPodsService, device) + } + } - var isConnectedLocally = false - var device: BluetoothDevice? = null + override fun onEarDetectionReceived(earDetection: ByteArray) { + sendBroadcast(Intent(AirPodsNotifications.EAR_DETECTION_DATA).apply { + val list = earDetectionNotification.status + val bytes = ByteArray(2) + bytes[0] = list[0] + bytes[1] = list[1] + putExtra("data", bytes) + }) + Log.d( + "AirPodsParser", + "Ear Detection: ${earDetectionNotification.status[0]} ${earDetectionNotification.status[1]}" + ) + processEarDetectionChange(earDetection) + } - private lateinit var earReceiver: BroadcastReceiver - var widgetMobileBatteryEnabled = false + override fun onConversationAwarenessReceived(conversationAwareness: ByteArray) { + conversationAwarenessNotification.setData(conversationAwareness) + sendBroadcast(Intent(AirPodsNotifications.CA_DATA).apply { + putExtra("data", conversationAwarenessNotification.status) + }) - object BatteryChangedIntentReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent) { - if (intent.action == Intent.ACTION_BATTERY_CHANGED) { - ServiceManager.getService()?.updateBattery() - } else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { - try { - context?.unregisterReceiver(this) - } catch (e: Exception) { - e.printStackTrace() + if (conversationAwarenessNotification.status == 1.toByte() || conversationAwarenessNotification.status == 2.toByte()) { + MediaController.startSpeaking() + } else if (conversationAwarenessNotification.status == 8.toByte() || conversationAwarenessNotification.status == 9.toByte()) { + MediaController.stopSpeaking() } + + Log.d( + "AirPodsParser", + "Conversation Awareness: ${conversationAwarenessNotification.status}" + ) } - } - } - fun setPhoneBatteryInWidget(enabled: Boolean) { - widgetMobileBatteryEnabled = enabled - updateBattery() - } + override fun onControlCommandReceived(controlCommand: ByteArray) { + val command = AACPManager.ControlCommand.fromByteArray(controlCommand) + if (command.identifier == AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value) { + ancNotification.setStatus(byteArrayOf(command.value.takeIf { it.isNotEmpty() }?.get(0) ?: 0x00.toByte())) + sendANCBroadcast() + updateNoiseControlWidget() + } + } - @OptIn(ExperimentalMaterial3Api::class) - fun startForegroundNotification() { - val disconnectedNotificationChannel = NotificationChannel( - "background_service_status", - "Background Service Status", - NotificationManager.IMPORTANCE_LOW - ) + override fun onOwnershipChangeReceived(owns: Boolean) { + if (!owns) { + Log.d("AirPodsService", "ownership lost") + MediaController.sendPause() + MediaController.pausedForOtherDevice = true + } + } - val connectedNotificationChannel = NotificationChannel( - "airpods_connection_status", - "AirPods Connection Status", - NotificationManager.IMPORTANCE_LOW, - ) + override fun onOwnershipToFalseRequest(reasonReverseTapped: Boolean) { + // TODO: Show a reverse button, but that's a lot of effort -- i'd have to change the UI too, which i hate doing, and handle other device's reverses too, and disconnect audio etc... so for now, just pause the audio and show the island without asking to reverse. + // handling reverse is a problem because we'd have to disconnect the audio, but there's no option connect audio again natively, so notification would have to be changed. I wish there was a way to just "change the audio output device". + // (20 minutes later) i've done it nonetheless :] + Log.d("AirPodsService", "other device has hijacked the connection, reasonReverseTapped: $reasonReverseTapped") + aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION.value, + byteArrayOf(0x00) + ) + if (reasonReverseTapped) { + Log.d("AirPodsService", "reverse tapped, disconnecting audio") + disconnectedBecauseReversed = true + disconnectAudio(this@AirPodsService, device) + showIsland( + this@AirPodsService, + (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), + IslandType.MOVED_TO_OTHER_DEVICE, + reversed = reasonReverseTapped + ) + } + if (!aacpManager.owns) { + showIsland( + this@AirPodsService, + (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), + IslandType.MOVED_TO_OTHER_DEVICE, + reversed = reasonReverseTapped + ) + } + MediaController.sendPause() + } - val socketFailureChannel = NotificationChannel( - "socket_connection_failure", - "AirPods Socket Connection Issues", - NotificationManager.IMPORTANCE_HIGH - ).apply { - description = "Notifications about problems connecting to AirPods protocol" - enableLights(true) - lightColor = android.graphics.Color.RED - enableVibration(true) - } + override fun onShowNearbyUI() { + // showIsland( + // this@AirPodsService, + // (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), + // IslandType.MOVED_TO_OTHER_DEVICE, + // reversed = false + // ) + } - val notificationManager = getSystemService(NotificationManager::class.java) - notificationManager.createNotificationChannel(disconnectedNotificationChannel) - notificationManager.createNotificationChannel(connectedNotificationChannel) - notificationManager.createNotificationChannel(socketFailureChannel) + override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) { - val notificationSettingsIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, packageName) - putExtra(Settings.EXTRA_CHANNEL_ID, "background_service_status") - } - val pendingIntentNotifDisable = PendingIntent.getActivity( - this, - 0, - notificationSettingsIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + } - val notification = NotificationCompat.Builder(this, "background_service_status") - .setSmallIcon(R.drawable.airpods) - .setContentTitle("Background Service Running") - .setContentText("Useless notification, disable it by clicking on it.") - .setContentIntent(pendingIntentNotifDisable) - .setCategory(Notification.CATEGORY_SERVICE) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setOngoing(true) - .build() + @SuppressLint("NewApi") + override fun onHeadTrackingReceived(headTracking: ByteArray) { + if (isHeadTrackingActive) { + HeadTracking.processPacket(headTracking) + processHeadTrackingData(headTracking) + } + } - try { - startForeground(1, notification) - } catch (e: Exception) { - e.printStackTrace() - } - } + override fun onProximityKeysReceived(proximityKeys: ByteArray) { + val keys = aacpManager.parseProximityKeysResponse(proximityKeys) + Log.d("AirPodsParser", "Proximity keys: $keys") + sharedPreferences.edit { + for (key in keys) { + Log.d("AirPodsParser", "Proximity key: ${key.key.name} = ${key.value}") + putString(key.key.name, Base64.encode(key.value)) + } + } + } - @OptIn(ExperimentalMaterial3Api::class) - private fun showSocketConnectionFailureNotification(errorMessage: String) { - val notificationManager = getSystemService(NotificationManager::class.java) + override fun onStemPressReceived(stemPress: ByteArray) { + val (stemPressType, bud) = aacpManager.parseStemPressResponse(stemPress) - val notificationIntent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, - 0, - notificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + Log.d("AirPodsParser", "Stem press received: $stemPressType on $bud") - val notification = NotificationCompat.Builder(this, "socket_connection_failure") - .setSmallIcon(R.drawable.airpods) - .setContentTitle("AirPods Connection Issue") - .setContentText("Unable to connect to AirPods over L2CAP") - .setStyle(NotificationCompat.BigTextStyle() - .bigText("Your AirPods are connected via Bluetooth, but LibrePods couldn't connect to AirPods using L2CAP. " + - "Error: $errorMessage")) - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_ERROR) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setAutoCancel(true) - .build() + val action = getActionFor(bud, stemPressType) + Log.d("AirPodsParser", "$bud $stemPressType action: $action") - notificationManager.notify(3, notification) - } + action?.let { executeStemAction(it) } + } + override fun onAudioSourceReceived(audioSource: ByteArray) { + Log.d("AirPodsParser", "Audio source changed mac: ${aacpManager.audioSource?.mac}, type: ${aacpManager.audioSource?.type?.name}") + if (aacpManager.audioSource?.type != AACPManager.Companion.AudioSourceType.NONE && aacpManager.audioSource?.mac != localMac) { + Log.d("AirPodsParser", "Audio source is another device, better to give up aacp control") + aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION.value, + byteArrayOf(0x00) + ) + // this also means that the other device has start playing the audio, and if that's true, we can again start listening for audio config changes + Log.d("AirPodsService", "Another device started playing audio, listening for audio config changes again") + MediaController.pausedForOtherDevice = false + } + } - fun sendANCBroadcast() { - sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply { - putExtra("data", ancNotification.status) - }) - } + override fun onConnectedDevicesReceived(connectedDevices: List) { + for (device in connectedDevices) { + Log.d("AirPodsParser", "Connected device: ${device.mac}, info1: ${device.info1}, info2: ${device.info2})") + } + val newDevices = connectedDevices.filter { newDevice -> + val notInOld = aacpManager.oldConnectedDevices?.none { oldDevice -> oldDevice.mac == newDevice.mac } ?: true + val notLocal = newDevice.mac != localMac + notInOld && notLocal + } - fun sendBatteryBroadcast() { - sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { - putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery())) + for (device in newDevices) { + Log.d("AirPodsParser", "New connected device: ${device.mac}, info1: ${device.info1}, info2: ${device.info2})") + Log.d("AirPodsService", "Sending new Tipi packet for device ${device.mac}, and sending media info to the device") + aacpManager.sendMediaInformationNewDevice(selfMacAddress = localMac, targetMacAddress = device.mac) + aacpManager.sendAddTiPiDevice(selfMacAddress = localMac, targetMacAddress = device.mac) + } + } + override fun onUnknownPacketReceived(packet: ByteArray) { + Log.d("AACPManager", "Unknown packet received: ${packet.joinToString(" ") { "%02X".format(it) }}") + } }) } - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - fun sendBatteryNotification() { - updateNotificationContent( - true, - getSharedPreferences("settings", MODE_PRIVATE).getString("name", device?.name), - batteryNotification.getBattery() - ) + private fun getActionFor(bud: AACPManager.Companion.StemPressBudType, type: StemPressType): StemAction? { + return when (type) { + StemPressType.SINGLE_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftSinglePressAction else config.rightSinglePressAction + StemPressType.DOUBLE_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftDoublePressAction else config.rightDoublePressAction + StemPressType.TRIPLE_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftTriplePressAction else config.rightTriplePressAction + StemPressType.LONG_PRESS -> if (bud == AACPManager.Companion.StemPressBudType.LEFT) config.leftLongPressAction else config.rightLongPressAction + } } - fun setBatteryMetadata() { - device?.let { - SystemApisUtils.setMetadata( - it, - it.METADATA_UNTETHERED_CASE_BATTERY, - batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }?.level.toString().toByteArray() - ) - SystemApisUtils.setMetadata( - it, - it.METADATA_UNTETHERED_CASE_CHARGING, - (if (batteryNotification.getBattery().find { it.component == BatteryComponent.CASE}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray()) - ) - SystemApisUtils.setMetadata( - it, - it.METADATA_UNTETHERED_LEFT_BATTERY, - batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }?.level.toString().toByteArray() - ) - SystemApisUtils.setMetadata( - it, - it.METADATA_UNTETHERED_LEFT_CHARGING, - (if (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray()) - ) - SystemApisUtils.setMetadata( - it, - it.METADATA_UNTETHERED_RIGHT_BATTERY, - batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }?.level.toString().toByteArray() - ) - SystemApisUtils.setMetadata( - it, - it.METADATA_UNTETHERED_RIGHT_CHARGING, - (if (batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray()) - ) + private fun executeStemAction(action: StemAction) { + when (action) { + StemAction.defaultActions[StemPressType.SINGLE_PRESS] -> { + Log.d("AirPodsParser", "Default single press action: Play/Pause, not taking action.") + } + StemAction.PLAY_PAUSE -> MediaController.sendPlayPause() + StemAction.PREVIOUS_TRACK -> MediaController.sendPreviousTrack() + StemAction.NEXT_TRACK -> MediaController.sendNextTrack() + StemAction.CAMERA_SHUTTER -> Runtime.getRuntime().exec(arrayOf("su", "-c", "input keyevent 27")) + StemAction.DIGITAL_ASSISTANT -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val intent = Intent(Intent.ACTION_VOICE_COMMAND).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + startActivity(intent) + } else { + Log.w("AirPodsParser", "Digital Assistant action is not supported on this Android version.") + } + } + StemAction.CYCLE_NOISE_CONTROL_MODES -> { + Log.d("AirPodsParser", "Cycling noise control modes") + sendBroadcast(Intent("me.kavishdevar.librepods.SET_ANC_MODE")) + } } } - @OptIn(ExperimentalMaterial3Api::class) - fun updateBatteryWidget() { - val appWidgetManager = AppWidgetManager.getInstance(this) - val componentName = ComponentName(this, BatteryWidget::class.java) - val widgetIds = appWidgetManager.getAppWidgetIds(componentName) + private fun processEarDetectionChange(earDetection: ByteArray) { + var inEar = false + var inEarData = listOf(earDetectionNotification.status[0] == 0x00.toByte(), earDetectionNotification.status[1] == 0x00.toByte()) + var justEnabledA2dp = false + earDetectionNotification.setStatus(earDetection) + if (config.earDetectionEnabled) { + val data = earDetection.copyOfRange(earDetection.size - 2, earDetection.size) + inEar = data[0] == 0x00.toByte() && data[1] == 0x00.toByte() - val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also { - val openActivityIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - it.setOnClickPendingIntent(R.id.battery_widget, openActivityIntent) + val newInEarData = listOf( + data[0] == 0x00.toByte(), + data[1] == 0x00.toByte() + ) - val leftBattery = - batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT } - val rightBattery = - batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT } - val caseBattery = - batteryNotification.getBattery().find { it.component == BatteryComponent.CASE } + if (inEarData.sorted() == listOf(false, false) && newInEarData.sorted() != listOf(false, false) && islandWindow?.isVisible != true) { + showIsland( + this@AirPodsService, + (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0)) + } - it.setTextViewText( - R.id.left_battery_widget, - leftBattery?.let { - "${it.level}%" - } ?: "" - ) - it.setProgressBar( - R.id.left_battery_progress, - 100, - leftBattery?.level ?: 0, - false - ) - it.setViewVisibility( - R.id.left_charging_icon, - if (leftBattery?.status == BatteryStatus.CHARGING) View.VISIBLE else View.GONE - ) + if (newInEarData == listOf(false, false) && islandWindow?.isVisible == true) { + islandWindow?.close() + } - it.setTextViewText( - R.id.right_battery_widget, - rightBattery?.let { - "${it.level}%" - } ?: "" - ) - it.setProgressBar( - R.id.right_battery_progress, - 100, - rightBattery?.level ?: 0, - false - ) - it.setViewVisibility( - R.id.right_charging_icon, - if (rightBattery?.status == BatteryStatus.CHARGING) View.VISIBLE else View.GONE - ) + if (newInEarData.contains(true) && inEarData == listOf(false, false)) { + connectAudio(this@AirPodsService, device) + justEnabledA2dp = true + registerA2dpConnectionReceiver() + if (MediaController.getMusicActive()) { + MediaController.userPlayedTheMedia = true + } + } else if (newInEarData == listOf(false, false)) { + MediaController.sendPause(force = true) + if (config.disconnectWhenNotWearing) { + disconnectAudio(this@AirPodsService, device) + } + } - it.setTextViewText( - R.id.case_battery_widget, - caseBattery?.let { - "${it.level}%" - } ?: "" - ) - it.setProgressBar( - R.id.case_battery_progress, - 100, - caseBattery?.level ?: 0, - false - ) - it.setViewVisibility( - R.id.case_charging_icon, - if (caseBattery?.status == BatteryStatus.CHARGING) View.VISIBLE else View.GONE - ) + if (inEarData.contains(false) && newInEarData == listOf(true, true)) { + Log.d("AirPodsParser", "User put in both AirPods from just one.") + MediaController.userPlayedTheMedia = false + } - it.setViewVisibility( - R.id.phone_battery_widget_container, - if (widgetMobileBatteryEnabled) View.VISIBLE else View.GONE - ) - if (widgetMobileBatteryEnabled) { - val batteryManager = getSystemService(BatteryManager::class.java) - val batteryLevel = - batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) - val charging = - batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) == BatteryManager.BATTERY_STATUS_CHARGING - it.setTextViewText( - R.id.phone_battery_widget, - "$batteryLevel%" - ) - it.setViewVisibility( - R.id.phone_charging_icon, - if (charging) View.VISIBLE else View.GONE - ) - it.setProgressBar( - R.id.phone_battery_progress, - 100, - batteryLevel, - false - ) + if (newInEarData.contains(false) && inEarData == listOf(true, true)) { + Log.d("AirPodsParser", "User took one of two out.") + MediaController.userPlayedTheMedia = false } - } - appWidgetManager.updateAppWidget(widgetIds, remoteViews) - } - @SuppressLint("MissingPermission") - @OptIn(ExperimentalMaterial3Api::class) - fun updateBattery() { - setBatteryMetadata() - updateBatteryWidget() - sendBatteryBroadcast() - sendBatteryNotification() - } + Log.d("AirPodsParser", "inEarData: ${inEarData.sorted()}, newInEarData: ${newInEarData.sorted()}") - fun updateNoiseControlWidget() { - val appWidgetManager = AppWidgetManager.getInstance(this) - val componentName = ComponentName(this, NoiseControlWidget::class.java) - val widgetIds = appWidgetManager.getAppWidgetIds(componentName) - val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { - val ancStatus = ancNotification.status - val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } - val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() - it.setInt( - R.id.widget_off_button, - "setBackgroundResource", - if (ancStatus == 1) R.drawable.widget_button_checked_shape_start else R.drawable.widget_button_shape_start - ) - it.setInt( - R.id.widget_transparency_button, - "setBackgroundResource", - if (ancStatus == 3) (if (allowOffMode) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_checked_shape_start) else (if (allowOffMode) R.drawable.widget_button_shape_middle else R.drawable.widget_button_shape_start) - ) - it.setInt( - R.id.widget_adaptive_button, - "setBackgroundResource", - if (ancStatus == 4) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_shape_middle - ) - it.setInt( - R.id.widget_anc_button, - "setBackgroundResource", - if (ancStatus == 2) R.drawable.widget_button_checked_shape_end else R.drawable.widget_button_shape_end - ) - it.setViewVisibility( - R.id.widget_off_button, - if (allowOffMode) View.VISIBLE else View.GONE - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - it.setViewLayoutMargin( - R.id.widget_transparency_button, - RemoteViews.MARGIN_START, - if (allowOffMode) 2f else 12f, - TypedValue.COMPLEX_UNIT_DIP - ) - } else { - it.setViewPadding( - R.id.widget_transparency_button, - if (allowOffMode) 2.dpToPx() else 12.dpToPx(), - 12.dpToPx(), - 2.dpToPx(), - 12.dpToPx() - ) + if (newInEarData.sorted() != inEarData.sorted()) { + inEarData = newInEarData + if (inEar == true) { + if (!justEnabledA2dp) { + justEnabledA2dp = false + MediaController.sendPlay() + MediaController.iPausedTheMedia = false + } + } else { + MediaController.sendPause() + } } } - - appWidgetManager.updateAppWidget(widgetIds, remoteViews) } - @OptIn(ExperimentalMaterial3Api::class) - fun updateNotificationContent( - connected: Boolean, - airpodsName: String? = null, - batteryList: List? = null - ) { - val notificationManager = getSystemService(NotificationManager::class.java) - var updatedNotification: Notification? = null + private fun registerA2dpConnectionReceiver() { + val a2dpConnectionStateReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") { + val state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) + val previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_DISCONNECTED) + val device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) - val notificationIntent = Intent(this, MainActivity::class.java) - val pendingIntent = PendingIntent.getActivity( - this, - 0, - notificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - if (!::socket.isInitialized && !config.bleOnlyMode) { - return - } - if (connected && (config.bleOnlyMode || socket.isConnected)) { - updatedNotification = NotificationCompat.Builder(this, "airpods_connection_status") - .setSmallIcon(R.drawable.airpods) - .setContentTitle(airpodsName ?: config.deviceName) - .setContentText( - """${ - batteryList?.find { it.component == BatteryComponent.LEFT }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { - "L: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } - } ?: "" - } ${ - batteryList?.find { it.component == BatteryComponent.RIGHT }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { - "R: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } - } ?: "" - } ${ - batteryList?.find { it.component == BatteryComponent.CASE }?.let { - if (it.status != BatteryStatus.DISCONNECTED) { - "Case: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" - } else { - "" - } - } ?: "" - }""") - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_STATUS) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setOngoing(true) - .build() - - notificationManager.notify(2, updatedNotification) - - notificationManager.cancel(1) - } else if (!connected) { - updatedNotification = NotificationCompat.Builder(this, "background_service_status") - .setSmallIcon(R.drawable.airpods) - .setContentTitle("AirPods not connected") - .setContentText("Tap to open app") - .setContentIntent(pendingIntent) - .setCategory(Notification.CATEGORY_SERVICE) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setOngoing(true) - .build() + Log.d("MediaController", "A2DP state changed: $previousState -> $state for device: ${device?.address}") - notificationManager.notify(1, updatedNotification) - notificationManager.cancel(2) - } else if (!config.bleOnlyMode && !socket.isConnected && isConnectedLocally) { - Log.d("AirPodsService", " Socket not connected") - showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?") - } - } + if (state == BluetoothProfile.STATE_CONNECTED && + previousState != BluetoothProfile.STATE_CONNECTED && + device?.address == this@AirPodsService.device?.address) { - @RequiresApi(Build.VERSION_CODES.Q) - fun handleIncomingCall() { - if (isInCall) return - if (config.headGestures) { - initGestureDetector() - startHeadTracking() - gestureDetector?.startDetection { accepted -> - if (accepted) { - answerCall() - handleIncomingCallOnceConnected = false - } else { - rejectCall() - handleIncomingCallOnceConnected = false - } - } - } - } - @OptIn(ExperimentalCoroutinesApi::class) - @RequiresApi(Build.VERSION_CODES.Q) + Log.d("MediaController", "A2DP connected, sending play command") + MediaController.sendPlay() + MediaController.iPausedTheMedia = false - suspend fun testHeadGestures(): Boolean { - return suspendCancellableCoroutine { continuation -> - gestureDetector?.startDetection(doNotStop = true) { accepted -> - if (continuation.isActive) { - continuation.resume(accepted) { - gestureDetector?.stopDetection() + context.unregisterReceiver(this) } } } } - } - private fun answerCall() { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager - if (checkSelfPermission(Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) { - telecomManager.acceptRingingCall() - } - } else { - val telephonyService = getSystemService(TELEPHONY_SERVICE) as TelephonyManager - val telephonyClass = Class.forName(telephonyService.javaClass.name) - val method = telephonyClass.getDeclaredMethod("getITelephony") - method.isAccessible = true - val telephonyInterface = method.invoke(telephonyService) - val answerCallMethod = telephonyInterface.javaClass.getDeclaredMethod("answerRingingCall") - answerCallMethod.invoke(telephonyInterface) - } - - sendToast("Call answered via head gesture") - } catch (e: Exception) { - e.printStackTrace() - sendToast("Failed to answer call: ${e.message}") - } finally { - islandWindow?.close() - } - } - private fun rejectCall() { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager - if (checkSelfPermission(Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) { - telecomManager.endCall() - } - } else { - val telephonyService = getSystemService(TELEPHONY_SERVICE) as TelephonyManager - val telephonyClass = Class.forName(telephonyService.javaClass.name) - val method = telephonyClass.getDeclaredMethod("getITelephony") - method.isAccessible = true - val telephonyInterface = method.invoke(telephonyService) - val endCallMethod = telephonyInterface.javaClass.getDeclaredMethod("endCall") - endCallMethod.invoke(telephonyInterface) - } - - sendToast("Call rejected via head gesture") - } catch (e: Exception) { - e.printStackTrace() - sendToast("Failed to reject call: ${e.message}") - } finally { - islandWindow?.close() - } - } - fun sendToast(message: String) { - Handler(Looper.getMainLooper()).post { - Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show() + val a2dpIntentFilter = IntentFilter("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + registerReceiver(a2dpConnectionStateReceiver, a2dpIntentFilter, RECEIVER_EXPORTED) + } else { + registerReceiver(a2dpConnectionStateReceiver, a2dpIntentFilter) } } - @RequiresApi(Build.VERSION_CODES.R) - fun processHeadTrackingData(data: ByteArray) { - val horizontal = ByteBuffer.wrap(data, 51, 2).order(ByteOrder.LITTLE_ENDIAN).short.toInt() - val vertical = ByteBuffer.wrap(data, 53, 2).order(ByteOrder.LITTLE_ENDIAN).short.toInt() - gestureDetector?.processHeadOrientation(horizontal, vertical) - } + private fun initializeConfig() { + config = ServiceConfig( + deviceName = sharedPreferences.getString("name", "AirPods") ?: "AirPods", + earDetectionEnabled = sharedPreferences.getBoolean("automatic_ear_detection", true), + conversationalAwarenessPauseMusic = sharedPreferences.getBoolean("conversational_awareness_pause_music", false), + showPhoneBatteryInWidget = sharedPreferences.getBoolean("show_phone_battery_in_widget", true), + relativeConversationalAwarenessVolume = sharedPreferences.getBoolean("relative_conversational_awareness_volume", true), + headGestures = sharedPreferences.getBoolean("head_gestures", true), + disconnectWhenNotWearing = sharedPreferences.getBoolean("disconnect_when_not_wearing", false), + conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 43), + textColor = sharedPreferences.getLong("textColor", -1L), + qsClickBehavior = sharedPreferences.getString("qs_click_behavior", "cycle") ?: "cycle", + bleOnlyMode = sharedPreferences.getBoolean("ble_only_mode", false), - private lateinit var connectionReceiver: BroadcastReceiver - private lateinit var disconnectionReceiver: BroadcastReceiver + // AirPods state-based takeover + takeoverWhenDisconnected = sharedPreferences.getBoolean("takeover_when_disconnected", true), + takeoverWhenIdle = sharedPreferences.getBoolean("takeover_when_idle", true), + takeoverWhenMusic = sharedPreferences.getBoolean("takeover_when_music", false), + takeoverWhenCall = sharedPreferences.getBoolean("takeover_when_call", true), - private fun resToUri(resId: Int): Uri? { - return try { - Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority("me.kavishdevar.librepods") - .appendPath(applicationContext.resources.getResourceTypeName(resId)) - .appendPath(applicationContext.resources.getResourceEntryName(resId)) - .build() - } catch (e: Resources.NotFoundException) { - null - } - } + // Phone state-based takeover + takeoverWhenRingingCall = sharedPreferences.getBoolean("takeover_when_ringing_call", true), + takeoverWhenMediaStart = sharedPreferences.getBoolean("takeover_when_media_start", true), - @Suppress("PrivatePropertyName") - private val VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV" - @Suppress("PrivatePropertyName") - private val VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1 - @Suppress("PrivatePropertyName") - private val APPLE = 0x004C - @Suppress("PrivatePropertyName") - private val ACTION_BATTERY_LEVEL_CHANGED = "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" - @Suppress("PrivatePropertyName") - private val EXTRA_BATTERY_LEVEL = "android.bluetooth.device.extra.BATTERY_LEVEL" - @Suppress("PrivatePropertyName") - private val PACKAGE_ASI = "com.google.android.settings.intelligence" - @Suppress("PrivatePropertyName") - private val ACTION_ASI_UPDATE_BLUETOOTH_DATA = "batterywidget.impl.action.update_bluetooth_data" + // Stem actions + leftSinglePressAction = StemAction.fromString(sharedPreferences.getString("left_single_press_action", "PLAY_PAUSE") ?: "PLAY_PAUSE")!!, + rightSinglePressAction = StemAction.fromString(sharedPreferences.getString("right_single_press_action", "PLAY_PAUSE") ?: "PLAY_PAUSE")!!, - @Suppress("MissingPermission") - fun broadcastBatteryInformation() { - if (device == null) return + leftDoublePressAction = StemAction.fromString(sharedPreferences.getString("left_double_press_action", "PREVIOUS_TRACK") ?: "NEXT_TRACK")!!, + rightDoublePressAction = StemAction.fromString(sharedPreferences.getString("right_double_press_action", "NEXT_TRACK") ?: "NEXT_TRACK")!!, - val batteryList = batteryNotification.getBattery() - val leftBattery = batteryList.find { it.component == BatteryComponent.LEFT } - val rightBattery = batteryList.find { it.component == BatteryComponent.RIGHT } + leftTriplePressAction = StemAction.fromString(sharedPreferences.getString("left_triple_press_action", "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK")!!, + rightTriplePressAction = StemAction.fromString(sharedPreferences.getString("right_triple_press_action", "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK")!!, - // Calculate unified battery level (minimum of left and right) - val batteryUnified = minOf( - leftBattery?.level ?: 100, - rightBattery?.level ?: 100 + leftLongPressAction = StemAction.fromString(sharedPreferences.getString("left_long_press_action", "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES")!!, + rightLongPressAction = StemAction.fromString(sharedPreferences.getString("right_long_press_action", "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT")!! ) + } - // Check charging status - val isLeftCharging = leftBattery?.status == BatteryStatus.CHARGING - val isRightCharging = rightBattery?.status == BatteryStatus.CHARGING - isLeftCharging && isRightCharging - - // Create arguments for vendor-specific event - val arguments = arrayOf( - 1, // Number of key/value pairs - VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, // IndicatorType: Battery Level - batteryUnified // Battery Level - ) + override fun onSharedPreferenceChanged(preferences: SharedPreferences?, key: String?) { + if (preferences == null || key == null) return - // Broadcast vendor-specific event - val intent = Intent(android.bluetooth.BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT).apply { - putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV) - putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, android.bluetooth.BluetoothHeadset.AT_CMD_TYPE_SET) - putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments) - putExtra(BluetoothDevice.EXTRA_DEVICE, device) - putExtra(BluetoothDevice.EXTRA_NAME, device?.name) - addCategory("${android.bluetooth.BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.$APPLE") - } - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - sendBroadcastAsUser( - intent, - UserHandle.getUserHandleForUid(-1), - Manifest.permission.BLUETOOTH_CONNECT - ) - } else { - sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(-1)) + when(key) { + "name" -> config.deviceName = preferences.getString(key, "AirPods") ?: "AirPods" + "automatic_ear_detection" -> config.earDetectionEnabled = preferences.getBoolean(key, true) + "conversational_awareness_pause_music" -> config.conversationalAwarenessPauseMusic = preferences.getBoolean(key, false) + "show_phone_battery_in_widget" -> { + config.showPhoneBatteryInWidget = preferences.getBoolean(key, true) + widgetMobileBatteryEnabled = config.showPhoneBatteryInWidget + updateBattery() } - } catch (e: Exception) { - Log.e("AirPodsService", "Failed to send vendor-specific event: ${e.message}") - } + "relative_conversational_awareness_volume" -> config.relativeConversationalAwarenessVolume = preferences.getBoolean(key, true) + "head_gestures" -> config.headGestures = preferences.getBoolean(key, true) + "disconnect_when_not_wearing" -> config.disconnectWhenNotWearing = preferences.getBoolean(key, false) + "conversational_awareness_volume" -> config.conversationalAwarenessVolume = preferences.getInt(key, 43) + "textColor" -> config.textColor = preferences.getLong(key, -1L) + "qs_click_behavior" -> config.qsClickBehavior = preferences.getString(key, "cycle") ?: "cycle" + "ble_only_mode" -> config.bleOnlyMode = preferences.getBoolean(key, false) - // Broadcast battery level changes - val batteryIntent = Intent(ACTION_BATTERY_LEVEL_CHANGED).apply { - putExtra(BluetoothDevice.EXTRA_DEVICE, device) - putExtra(EXTRA_BATTERY_LEVEL, batteryUnified) - } + // AirPods state-based takeover + "takeover_when_disconnected" -> config.takeoverWhenDisconnected = preferences.getBoolean(key, true) + "takeover_when_idle" -> config.takeoverWhenIdle = preferences.getBoolean(key, true) + "takeover_when_music" -> config.takeoverWhenMusic = preferences.getBoolean(key, false) + "takeover_when_call" -> config.takeoverWhenCall = preferences.getBoolean(key, true) - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - sendBroadcast(batteryIntent, Manifest.permission.BLUETOOTH_CONNECT) - } else { - sendBroadcastAsUser(batteryIntent, UserHandle.getUserHandleForUid(-1)) + // Phone state-based takeover + "takeover_when_ringing_call" -> config.takeoverWhenRingingCall = preferences.getBoolean(key, true) + "takeover_when_media_start" -> config.takeoverWhenMediaStart = preferences.getBoolean(key, true) + + "left_single_press_action" -> { + config.leftSinglePressAction = StemAction.fromString( + preferences.getString(key, "PLAY_PAUSE") ?: "PLAY_PAUSE" + )!! + setupStemActions() + } + "right_single_press_action" -> { + config.rightSinglePressAction = StemAction.fromString( + preferences.getString(key, "PLAY_PAUSE") ?: "PLAY_PAUSE" + )!! + setupStemActions() + } + "left_double_press_action" -> { + config.leftDoublePressAction = StemAction.fromString( + preferences.getString(key, "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK" + )!! + setupStemActions() + } + "right_double_press_action" -> { + config.rightDoublePressAction = StemAction.fromString( + preferences.getString(key, "NEXT_TRACK") ?: "NEXT_TRACK" + )!! + setupStemActions() + } + "left_triple_press_action" -> { + config.leftTriplePressAction = StemAction.fromString( + preferences.getString(key, "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK" + )!! + setupStemActions() + } + "right_triple_press_action" -> { + config.rightTriplePressAction = StemAction.fromString( + preferences.getString(key, "PREVIOUS_TRACK") ?: "PREVIOUS_TRACK" + )!! + setupStemActions() + } + "left_long_press_action" -> { + config.leftLongPressAction = StemAction.fromString( + preferences.getString(key, "CYCLE_NOISE_CONTROL_MODES") ?: "CYCLE_NOISE_CONTROL_MODES" + )!! + setupStemActions() + } + "right_long_press_action" -> { + config.rightLongPressAction = StemAction.fromString( + preferences.getString(key, "DIGITAL_ASSISTANT") ?: "DIGITAL_ASSISTANT" + )!! + setupStemActions() } - } catch (e: Exception) { - Log.e("AirPodsService", "Failed to send battery level broadcast: ${e.message}") } - // Update Android Settings Intelligence's battery widget - val statusIntent = Intent(ACTION_ASI_UPDATE_BLUETOOTH_DATA).apply { - setPackage(PACKAGE_ASI) - putExtra(ACTION_BATTERY_LEVEL_CHANGED, intent) + if (key == "mac_address") { + macAddress = preferences.getString(key, "") ?: "" } + } - try { - sendBroadcastAsUser(statusIntent, UserHandle.getUserHandleForUid(-1)) - } catch (e: Exception) { - Log.e("AirPodsService", "Failed to send ASI battery level broadcast: ${e.message}") - } + private fun logPacket(packet: ByteArray, source: String) { + val packetHex = packet.joinToString(" ") { "%02X".format(it) } + val logEntry = "$source: $packetHex" - Log.d("AirPodsService", "Broadcast battery level $batteryUnified% to system") - } + synchronized(inMemoryLogs) { + inMemoryLogs.add(logEntry) + if (inMemoryLogs.size > maxLogEntries) { + inMemoryLogs.iterator().next().let { + inMemoryLogs.remove(it) + } + } - private fun setMetadatas(d: BluetoothDevice) { - d.let{ device -> - val metadataSet = SystemApisUtils.setMetadata( - device, - device.METADATA_MAIN_ICON, - resToUri(R.drawable.pro_2).toString().toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_MODEL_NAME, - "AirPods Pro (2 Gen.)".toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_DEVICE_TYPE, - device.DEVICE_TYPE_UNTETHERED_HEADSET.toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_UNTETHERED_CASE_ICON, - resToUri(R.drawable.pro_2_case).toString().toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_UNTETHERED_RIGHT_ICON, - resToUri(R.drawable.pro_2_right).toString().toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_UNTETHERED_LEFT_ICON, - resToUri(R.drawable.pro_2_left).toString().toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_MANUFACTURER_NAME, - "Apple".toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_COMPANION_APP, - "me.kavisdevar.librepods".toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD, - "20".toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, - "20".toByteArray() - ) && - SystemApisUtils.setMetadata( - device, - device.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, - "20".toByteArray() - ) - Log.d("AirPodsService", "Metadata set: $metadataSet") + _packetLogsFlow.value = inMemoryLogs.toSet() } - } - @Suppress("ClassName") - private object bluetoothReceiver : BroadcastReceiver() { - @SuppressLint("MissingPermission") - override fun onReceive(context: Context?, intent: Intent) { - val bluetoothDevice = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra( - "android.bluetooth.device.extra.DEVICE", - BluetoothDevice::class.java - ) - } else { - intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE") as BluetoothDevice? - } - val action = intent.action - val context = context?.applicationContext - val name = context?.getSharedPreferences("settings", MODE_PRIVATE) - ?.getString("name", bluetoothDevice?.name) - if (bluetoothDevice != null && action != null && !action.isEmpty()) { - Log.d("AirPodsService", "Received bluetooth connection broadcast") - if (ServiceManager.getService()?.isConnectedLocally == true) { - Log.d("AirPodsService", "Checking if audio should be connected") - ServiceManager.getService()?.manuallyCheckForAudioSource() - return - } - if (BluetoothDevice.ACTION_ACL_CONNECTED == action) { - val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") - bluetoothDevice.fetchUuidsWithSdp() - if (bluetoothDevice.uuids != null) { - if (bluetoothDevice.uuids.contains(uuid)) { - val intent = - Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) - intent.putExtra("name", name) - intent.putExtra("device", bluetoothDevice) - context?.sendBroadcast(intent) - } - } - } + CoroutineScope(Dispatchers.IO).launch { + val logs = sharedPreferencesLogs.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet() + ?: mutableSetOf() + logs.add(logEntry) + + if (logs.size > maxLogEntries) { + val toKeep = logs.toList().takeLast(maxLogEntries).toSet() + sharedPreferencesLogs.edit { putStringSet(packetLogKey, toKeep) } + } else { + sharedPreferencesLogs.edit { putStringSet(packetLogKey, logs) } } } } - val ancModeFilter = IntentFilter("me.kavishdevar.librepods.SET_ANC_MODE") - var ancModeReceiver: BroadcastReceiver? = null + fun getPacketLogs(): Set { + return inMemoryLogs.toSet() + } - @SuppressLint("InlinedApi", "MissingPermission", "UnspecifiedRegisterReceiverFlag") - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.d("AirPodsService", "Service started") - ServiceManager.setService(this) - startForegroundNotification() - initGestureDetector() + private fun clearPacketLogs() { + synchronized(inMemoryLogs) { + inMemoryLogs.clear() + _packetLogsFlow.value = emptySet() + } + sharedPreferencesLogs.edit { remove(packetLogKey) } + } - bleManager = BLEManager(this) - bleManager.setAirPodsStatusListener(bleStatusListener) + fun clearLogs() { + clearPacketLogs() + _packetLogsFlow.value = emptySet() + } - sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) + override fun onBind(intent: Intent?): IBinder { + return LocalBinder() + } - with(sharedPreferences) { - val editor = edit() + private var gestureDetector: GestureDetector? = null + private var isInCall = false + private var callNumber: String? = null - if (!contains("conversational_awareness_pause_music")) editor.putBoolean("conversational_awareness_pause_music", false) - if (!contains("personalized_volume")) editor.putBoolean("personalized_volume", false) - if (!contains("automatic_ear_detection")) editor.putBoolean("automatic_ear_detection", true) - if (!contains("long_press_nc")) editor.putBoolean("long_press_nc", true) - if (!contains("off_listening_mode")) editor.putBoolean("off_listening_mode", false) - if (!contains("show_phone_battery_in_widget")) editor.putBoolean("show_phone_battery_in_widget", true) - if (!contains("single_anc")) editor.putBoolean("single_anc", true) - if (!contains("long_press_transparency")) editor.putBoolean("long_press_transparency", true) - if (!contains("conversational_awareness")) editor.putBoolean("conversational_awareness", true) - if (!contains("relative_conversational_awareness_volume")) editor.putBoolean("relative_conversational_awareness_volume", true) - if (!contains("long_press_adaptive")) editor.putBoolean("long_press_adaptive", true) - if (!contains("loud_sound_reduction")) editor.putBoolean("loud_sound_reduction", true) - if (!contains("long_press_off")) editor.putBoolean("long_press_off", false) - if (!contains("volume_control")) editor.putBoolean("volume_control", true) - if (!contains("head_gestures")) editor.putBoolean("head_gestures", true) - if (!contains("disconnect_when_not_wearing")) editor.putBoolean("disconnect_when_not_wearing", false) + @RequiresApi(Build.VERSION_CODES.Q) + private fun initGestureDetector() { + if (gestureDetector == null) { + gestureDetector = GestureDetector(this) + } + } - // AirPods state-based takeover - if (!contains("takeover_when_disconnected")) editor.putBoolean("takeover_when_disconnected", true) - if (!contains("takeover_when_idle")) editor.putBoolean("takeover_when_idle", true) - if (!contains("takeover_when_music")) editor.putBoolean("takeover_when_music", false) - if (!contains("takeover_when_call")) editor.putBoolean("takeover_when_call", true) - // Phone state-based takeover - if (!contains("takeover_when_ringing_call")) editor.putBoolean("takeover_when_ringing_call", true) - if (!contains("takeover_when_media_start")) editor.putBoolean("takeover_when_media_start", true) + var popupShown = false + fun showPopup(service: Service, name: String) { + if (!Settings.canDrawOverlays(service)) { + Log.d("AirPodsService", "No permission for SYSTEM_ALERT_WINDOW") + return + } + if (popupShown) { + return + } + val popupWindow = PopupWindow(service.applicationContext) + popupWindow.open(name, batteryNotification) + popupShown = true + } - if (!contains("adaptive_strength")) editor.putInt("adaptive_strength", 51) - if (!contains("tone_volume")) editor.putInt("tone_volume", 75) - if (!contains("conversational_awareness_volume")) editor.putInt("conversational_awareness_volume", 43) + var islandOpen = false + var islandWindow: IslandWindow? = null + @SuppressLint("MissingPermission") + fun showIsland(service: Service, batteryPercentage: Int, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false) { + Log.d("AirPodsService", "Showing island window") + if (!Settings.canDrawOverlays(service)) { + Log.d("AirPodsService", "No permission for SYSTEM_ALERT_WINDOW") + return + } + CoroutineScope(Dispatchers.Main).launch { + islandWindow = IslandWindow(service.applicationContext) + islandWindow!!.show(sharedPreferences.getString("name", "AirPods Pro").toString(), batteryPercentage, this@AirPodsService, type, reversed) + } + } - if (!contains("textColor")) editor.putLong("textColor", -1L) + @OptIn(ExperimentalMaterial3Api::class) + fun startMainActivity() { + val intent = Intent(this, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(intent) + } - if (!contains("qs_click_behavior")) editor.putString("qs_click_behavior", "cycle") - if (!contains("name")) editor.putString("name", "AirPods") - if (!contains("ble_only_mode")) editor.putBoolean("ble_only_mode", false) + var isConnectedLocally = false + var device: BluetoothDevice? = null - if (!contains("left_single_press_action")) editor.putString("left_single_press_action", - StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name) - if (!contains("right_single_press_action")) editor.putString("right_single_press_action", - StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name) - if (!contains("left_double_press_action")) editor.putString("left_double_press_action", - StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name) - if (!contains("right_double_press_action")) editor.putString("right_double_press_action", - StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name) - if (!contains("left_triple_press_action")) editor.putString("left_triple_press_action", - StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name) - if (!contains("right_triple_press_action")) editor.putString("right_triple_press_action", - StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name) - if (!contains("left_long_press_action")) editor.putString("left_long_press_action", - StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name) - if (!contains("right_long_press_action")) editor.putString("right_long_press_action", - StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name) + private lateinit var earReceiver: BroadcastReceiver + var widgetMobileBatteryEnabled = false - editor.apply() + object BatteryChangedIntentReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent) { + if (intent.action == Intent.ACTION_BATTERY_CHANGED) { + ServiceManager.getService()?.updateBattery() + } else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { + try { + context?.unregisterReceiver(this) + } catch (e: Exception) { + e.printStackTrace() + } + } } + } - initializeConfig() + fun setPhoneBatteryInWidget(enabled: Boolean) { + widgetMobileBatteryEnabled = enabled + updateBattery() + } - ancModeReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action == "me.kavishdevar.librepods.SET_ANC_MODE") { - if (intent.hasExtra("mode")) { - val mode = intent.getIntExtra("mode", -1) - if (mode in 1..4) { - aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value, - mode - ) - } - } else { - val currentMode = ancNotification.status - val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } - val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() + @OptIn(ExperimentalMaterial3Api::class) + fun startForegroundNotification() { + val disconnectedNotificationChannel = NotificationChannel( + "background_service_status", + "Background Service Status", + NotificationManager.IMPORTANCE_LOW + ) + + val connectedNotificationChannel = NotificationChannel( + "airpods_connection_status", + "AirPods Connection Status", + NotificationManager.IMPORTANCE_LOW, + ) + + val socketFailureChannel = NotificationChannel( + "socket_connection_failure", + "AirPods Socket Connection Issues", + NotificationManager.IMPORTANCE_HIGH + ).apply { + description = "Notifications about problems connecting to AirPods protocol" + enableLights(true) + lightColor = android.graphics.Color.RED + enableVibration(true) + } + + val notificationManager = getSystemService(NotificationManager::class.java) + notificationManager.createNotificationChannel(disconnectedNotificationChannel) + notificationManager.createNotificationChannel(connectedNotificationChannel) + notificationManager.createNotificationChannel(socketFailureChannel) + + val notificationSettingsIntent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, packageName) + putExtra(Settings.EXTRA_CHANNEL_ID, "background_service_status") + } + val pendingIntentNotifDisable = PendingIntent.getActivity( + this, + 0, + notificationSettingsIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val notification = NotificationCompat.Builder(this, "background_service_status") + .setSmallIcon(R.drawable.airpods) + .setContentTitle("Background Service Running") + .setContentText("Useless notification, disable it by clicking on it.") + .setContentIntent(pendingIntentNotifDisable) + .setCategory(Notification.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .build() + + try { + startForeground(1, notification) + } catch (e: Exception) { + e.printStackTrace() + } + } + + @OptIn(ExperimentalMaterial3Api::class) + private fun showSocketConnectionFailureNotification(errorMessage: String) { + val notificationManager = getSystemService(NotificationManager::class.java) + + val notificationIntent = Intent(this, MainActivity::class.java) + val pendingIntent = PendingIntent.getActivity( + this, + 0, + notificationIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + val notification = NotificationCompat.Builder(this, "socket_connection_failure") + .setSmallIcon(R.drawable.airpods) + .setContentTitle("AirPods Connection Issue") + .setContentText("Unable to connect to AirPods over L2CAP") + .setStyle(NotificationCompat.BigTextStyle() + .bigText("Your AirPods are connected via Bluetooth, but LibrePods couldn't connect to AirPods using L2CAP. " + + "Error: $errorMessage")) + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_ERROR) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .build() + + notificationManager.notify(3, notification) + } + + fun sendANCBroadcast() { + sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply { + putExtra("data", ancNotification.status) + }) + } + + fun sendBatteryBroadcast() { + sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { + putParcelableArrayListExtra("data", ArrayList(batteryNotification.getBattery())) + }) + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + fun sendBatteryNotification() { + updateNotificationContent( + true, + getSharedPreferences("settings", MODE_PRIVATE).getString("name", device?.name), + batteryNotification.getBattery() + ) + } + + fun setBatteryMetadata() { + device?.let { + SystemApisUtils.setMetadata( + it, + it.METADATA_UNTETHERED_CASE_BATTERY, + batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }?.level.toString().toByteArray() + ) + SystemApisUtils.setMetadata( + it, + it.METADATA_UNTETHERED_CASE_CHARGING, + (if (batteryNotification.getBattery().find { it.component == BatteryComponent.CASE}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray()) + ) + SystemApisUtils.setMetadata( + it, + it.METADATA_UNTETHERED_LEFT_BATTERY, + batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }?.level.toString().toByteArray() + ) + SystemApisUtils.setMetadata( + it, + it.METADATA_UNTETHERED_LEFT_CHARGING, + (if (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray()) + ) + SystemApisUtils.setMetadata( + it, + it.METADATA_UNTETHERED_RIGHT_BATTERY, + batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }?.level.toString().toByteArray() + ) + SystemApisUtils.setMetadata( + it, + it.METADATA_UNTETHERED_RIGHT_CHARGING, + (if (batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.status == BatteryStatus.CHARGING) "1".toByteArray() else "0".toByteArray()) + ) + } + } + + @OptIn(ExperimentalMaterial3Api::class) + fun updateBatteryWidget() { + val appWidgetManager = AppWidgetManager.getInstance(this) + val componentName = ComponentName(this, BatteryWidget::class.java) + val widgetIds = appWidgetManager.getAppWidgetIds(componentName) + + val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also { + val openActivityIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + it.setOnClickPendingIntent(R.id.battery_widget, openActivityIntent) + + val leftBattery = + batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT } + val rightBattery = + batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT } + val caseBattery = + batteryNotification.getBattery().find { it.component == BatteryComponent.CASE } + + it.setTextViewText( + R.id.left_battery_widget, + leftBattery?.let { + "${it.level}%" + } ?: "" + ) + it.setProgressBar( + R.id.left_battery_progress, + 100, + leftBattery?.level ?: 0, + false + ) + it.setViewVisibility( + R.id.left_charging_icon, + if (leftBattery?.status == BatteryStatus.CHARGING) View.VISIBLE else View.GONE + ) + + it.setTextViewText( + R.id.right_battery_widget, + rightBattery?.let { + "${it.level}%" + } ?: "" + ) + it.setProgressBar( + R.id.right_battery_progress, + 100, + rightBattery?.level ?: 0, + false + ) + it.setViewVisibility( + R.id.right_charging_icon, + if (rightBattery?.status == BatteryStatus.CHARGING) View.VISIBLE else View.GONE + ) + + it.setTextViewText( + R.id.case_battery_widget, + caseBattery?.let { + "${it.level}%" + } ?: "" + ) + it.setProgressBar( + R.id.case_battery_progress, + 100, + caseBattery?.level ?: 0, + false + ) + it.setViewVisibility( + R.id.case_charging_icon, + if (caseBattery?.status == BatteryStatus.CHARGING) View.VISIBLE else View.GONE + ) + + it.setViewVisibility( + R.id.phone_battery_widget_container, + if (widgetMobileBatteryEnabled) View.VISIBLE else View.GONE + ) + if (widgetMobileBatteryEnabled) { + val batteryManager = getSystemService(BatteryManager::class.java) + val batteryLevel = + batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) + val charging = + batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS) == BatteryManager.BATTERY_STATUS_CHARGING + it.setTextViewText( + R.id.phone_battery_widget, + "$batteryLevel%" + ) + it.setViewVisibility( + R.id.phone_charging_icon, + if (charging) View.VISIBLE else View.GONE + ) + it.setProgressBar( + R.id.phone_battery_progress, + 100, + batteryLevel, + false + ) + } + } + appWidgetManager.updateAppWidget(widgetIds, remoteViews) + } + + @SuppressLint("MissingPermission") + @OptIn(ExperimentalMaterial3Api::class) + fun updateBattery() { + setBatteryMetadata() + updateBatteryWidget() + sendBatteryBroadcast() + sendBatteryNotification() + } + + fun updateNoiseControlWidget() { + val appWidgetManager = AppWidgetManager.getInstance(this) + val componentName = ComponentName(this, NoiseControlWidget::class.java) + val widgetIds = appWidgetManager.getAppWidgetIds(componentName) + val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { + val ancStatus = ancNotification.status + val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } + val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() + it.setInt( + R.id.widget_off_button, + "setBackgroundResource", + if (ancStatus == 1) R.drawable.widget_button_checked_shape_start else R.drawable.widget_button_shape_start + ) + it.setInt( + R.id.widget_transparency_button, + "setBackgroundResource", + if (ancStatus == 3) (if (allowOffMode) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_checked_shape_start) else (if (allowOffMode) R.drawable.widget_button_shape_middle else R.drawable.widget_button_shape_start) + ) + it.setInt( + R.id.widget_adaptive_button, + "setBackgroundResource", + if (ancStatus == 4) R.drawable.widget_button_checked_shape_middle else R.drawable.widget_button_shape_middle + ) + it.setInt( + R.id.widget_anc_button, + "setBackgroundResource", + if (ancStatus == 2) R.drawable.widget_button_checked_shape_end else R.drawable.widget_button_shape_end + ) + it.setViewVisibility( + R.id.widget_off_button, + if (allowOffMode) View.VISIBLE else View.GONE + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + it.setViewLayoutMargin( + R.id.widget_transparency_button, + RemoteViews.MARGIN_START, + if (allowOffMode) 2f else 12f, + TypedValue.COMPLEX_UNIT_DIP + ) + } else { + it.setViewPadding( + R.id.widget_transparency_button, + if (allowOffMode) 2.dpToPx() else 12.dpToPx(), + 12.dpToPx(), + 2.dpToPx(), + 12.dpToPx() + ) + } + } + + appWidgetManager.updateAppWidget(widgetIds, remoteViews) + } + + @OptIn(ExperimentalMaterial3Api::class) + fun updateNotificationContent( + connected: Boolean, + airpodsName: String? = null, + batteryList: List? = null + ) { + val notificationManager = getSystemService(NotificationManager::class.java) + var updatedNotification: Notification? = null + + val notificationIntent = Intent(this, MainActivity::class.java) + val pendingIntent = PendingIntent.getActivity( + this, + 0, + notificationIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + + if (!::socket.isInitialized && !config.bleOnlyMode) { + return + } + if (connected && (config.bleOnlyMode || socket.isConnected)) { + val updatedNotificationBuilder = NotificationCompat.Builder(this, "airpods_connection_status") + .setSmallIcon(R.drawable.airpods) + .setContentTitle(airpodsName ?: config.deviceName) + .setContentText( + """${ + batteryList?.find { it.component == BatteryComponent.LEFT }?.let { + if (it.status != BatteryStatus.DISCONNECTED) { + "L: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" + } else { + "" + } + } ?: "" + } ${ + batteryList?.find { it.component == BatteryComponent.RIGHT }?.let { + if (it.status != BatteryStatus.DISCONNECTED) { + "R: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" + } else { + "" + } + } ?: "" + } ${ + batteryList?.find { it.component == BatteryComponent.CASE }?.let { + if (it.status != BatteryStatus.DISCONNECTED) { + "Case: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%" + } else { + "" + } + } ?: "" + }""") + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_STATUS) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + + if (disconnectedBecauseReversed) { + updatedNotificationBuilder.addAction( + R.drawable.ic_bluetooth, + "Reconnect", + PendingIntent.getService( + this, + 0, + Intent(this, AirPodsService::class.java).apply { + action = "me.kavishdevar.librepods.RECONNECT_AFTER_REVERSE" + }, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + + val updatedNotification = updatedNotificationBuilder.build() + + notificationManager.notify(2, updatedNotification) + notificationManager.cancel(1) + } else if (!connected) { + updatedNotification = NotificationCompat.Builder(this, "background_service_status") + .setSmallIcon(R.drawable.airpods) + .setContentTitle("AirPods not connected") + .setContentText("Tap to open app") + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SERVICE) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .build() - val nextMode = if (allowOffMode) { - when (currentMode) { - 1 -> 2 - 2 -> 3 - 3 -> 4 - 4 -> 1 - else -> 1 - } - } else { - when (currentMode) { - 1 -> 2 - 2 -> 3 - 3 -> 4 - 4 -> 2 - else -> 2 - } - } + notificationManager.notify(1, updatedNotification) + notificationManager.cancel(2) + } else if (!config.bleOnlyMode && !socket.isConnected && isConnectedLocally) { + Log.d("AirPodsService", " Socket not connected") + showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?") + } + } - aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE.value, - nextMode - ) - Log.d("AirPodsService", "Cycling ANC mode from $currentMode to $nextMode (offListeningMode: $allowOffMode)") - } + @RequiresApi(Build.VERSION_CODES.Q) + fun handleIncomingCall() { + if (isInCall) return + if (config.headGestures) { + initGestureDetector() + startHeadTracking() + gestureDetector?.startDetection { accepted -> + if (accepted) { + answerCall() + handleIncomingCallOnceConnected = false + } else { + rejectCall() + handleIncomingCallOnceConnected = false } } } + } + @OptIn(ExperimentalCoroutinesApi::class) + @RequiresApi(Build.VERSION_CODES.Q) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(ancModeReceiver, ancModeFilter, RECEIVER_EXPORTED) - } else { - registerReceiver(ancModeReceiver, ancModeFilter) - } - val audioManager = - this@AirPodsService.getSystemService(AUDIO_SERVICE) as AudioManager - MediaController.initialize( - audioManager, - this@AirPodsService.getSharedPreferences( - "settings", - MODE_PRIVATE - ) - ) - Log.d("AirPodsService", "Initializing CrossDevice") - CoroutineScope(Dispatchers.IO).launch { - CrossDevice.init(this@AirPodsService) - Log.d("AirPodsService", "CrossDevice initialized") - } - - sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) - macAddress = sharedPreferences.getString("mac_address", "") ?: "" - - telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager - phoneStateListener = object : PhoneStateListener() { - @SuppressLint("SwitchIntDef") - override fun onCallStateChanged(state: Int, phoneNumber: String?) { - super.onCallStateChanged(state, phoneNumber) - when (state) { - TelephonyManager.CALL_STATE_RINGING -> { - val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true - if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope(Dispatchers.IO).launch { - takeOver("call") - } - if (config.headGestures) { - callNumber = phoneNumber - handleIncomingCall() - } - } - TelephonyManager.CALL_STATE_OFFHOOK -> { - val leAvailableForAudio = bleManager.getMostRecentStatus()?.isLeftInEar == true || bleManager.getMostRecentStatus()?.isRightInEar == true - if ((CrossDevice.isAvailable && !isConnectedLocally && earDetectionNotification.status.contains(0x00)) || leAvailableForAudio) CoroutineScope( - Dispatchers.IO).launch { - takeOver("call") - } - isInCall = true - } - TelephonyManager.CALL_STATE_IDLE -> { - isInCall = false - callNumber = null + suspend fun testHeadGestures(): Boolean { + return suspendCancellableCoroutine { continuation -> + gestureDetector?.startDetection(doNotStop = true) { accepted -> + if (continuation.isActive) { + continuation.resume(accepted) { gestureDetector?.stopDetection() } } } } - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE) + } + private fun answerCall() { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager + if (checkSelfPermission(Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) { + telecomManager.acceptRingingCall() + } + } else { + val telephonyService = getSystemService(TELEPHONY_SERVICE) as TelephonyManager + val telephonyClass = Class.forName(telephonyService.javaClass.name) + val method = telephonyClass.getDeclaredMethod("getITelephony") + method.isAccessible = true + val telephonyInterface = method.invoke(telephonyService) + val answerCallMethod = telephonyInterface.javaClass.getDeclaredMethod("answerRingingCall") + answerCallMethod.invoke(telephonyInterface) + } - if (config.showPhoneBatteryInWidget) { - widgetMobileBatteryEnabled = true - val batteryChangedIntentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) - batteryChangedIntentFilter.addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver( - BatteryChangedIntentReceiver, - batteryChangedIntentFilter, - RECEIVER_EXPORTED - ) + sendToast("Call answered via head gesture") + } catch (e: Exception) { + e.printStackTrace() + sendToast("Failed to answer call: ${e.message}") + } finally { + islandWindow?.close() + } + } + private fun rejectCall() { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val telecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager + if (checkSelfPermission(Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_GRANTED) { + telecomManager.endCall() + } } else { - registerReceiver(BatteryChangedIntentReceiver, batteryChangedIntentFilter) + val telephonyService = getSystemService(TELEPHONY_SERVICE) as TelephonyManager + val telephonyClass = Class.forName(telephonyService.javaClass.name) + val method = telephonyClass.getDeclaredMethod("getITelephony") + method.isAccessible = true + val telephonyInterface = method.invoke(telephonyService) + val endCallMethod = telephonyInterface.javaClass.getDeclaredMethod("endCall") + endCallMethod.invoke(telephonyInterface) } + + sendToast("Call rejected via head gesture") + } catch (e: Exception) { + e.printStackTrace() + sendToast("Failed to reject call: ${e.message}") + } finally { + islandWindow?.close() } - val serviceIntentFilter = IntentFilter().apply { - addAction("android.bluetooth.device.action.ACL_CONNECTED") - addAction("android.bluetooth.device.action.ACL_DISCONNECTED") - addAction("android.bluetooth.device.action.BOND_STATE_CHANGED") - addAction("android.bluetooth.device.action.NAME_CHANGED") - addAction("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED") - addAction("android.bluetooth.adapter.action.STATE_CHANGED") - addAction("android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED") - addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT") - addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") - addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED") + } + + fun sendToast(message: String) { + Handler(Looper.getMainLooper()).post { + Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show() } + } - connectionReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action == AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) { - device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - intent.getParcelableExtra("device", BluetoothDevice::class.java)!! - } else { - intent.getParcelableExtra("device") as BluetoothDevice? - } + @RequiresApi(Build.VERSION_CODES.R) + fun processHeadTrackingData(data: ByteArray) { + val horizontal = ByteBuffer.wrap(data, 51, 2).order(ByteOrder.LITTLE_ENDIAN).short.toInt() + val vertical = ByteBuffer.wrap(data, 53, 2).order(ByteOrder.LITTLE_ENDIAN).short.toInt() + gestureDetector?.processHeadOrientation(horizontal, vertical) + } - if (config.deviceName == "AirPods" && device?.name != null) { - config.deviceName = device?.name ?: "AirPods" - sharedPreferences.edit { putString("name", config.deviceName) } - } + private lateinit var connectionReceiver: BroadcastReceiver + private lateinit var disconnectionReceiver: BroadcastReceiver - Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString()) - if (!CrossDevice.isAvailable && !config.bleOnlyMode) { - Log.d("AirPodsService", "${config.deviceName} connected") - CoroutineScope(Dispatchers.IO).launch { - connectToSocket(device!!) - } - Log.d("AirPodsService", "Setting metadata") - setMetadatas(device!!) - isConnectedLocally = true - macAddress = device!!.address - sharedPreferences.edit { - putString("mac_address", macAddress) - } - } else if (config.bleOnlyMode) { - Log.d("AirPodsService", "BLE-only mode: skipping L2CAP connection") - macAddress = device!!.address - sharedPreferences.edit { - putString("mac_address", macAddress) - } - } - } else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) { - device = null - isConnectedLocally = false - popupShown = false - updateNotificationContent(false) - } - } + private fun resToUri(resId: Int): Uri? { + return try { + Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority("me.kavishdevar.librepods") + .appendPath(applicationContext.resources.getResourceTypeName(resId)) + .appendPath(applicationContext.resources.getResourceEntryName(resId)) + .build() + } catch (e: Resources.NotFoundException) { + null + } + } + + @Suppress("PrivatePropertyName") + private val VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV" + @Suppress("PrivatePropertyName") + private val VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1 + @Suppress("PrivatePropertyName") + private val APPLE = 0x004C + @Suppress("PrivatePropertyName") + private val ACTION_BATTERY_LEVEL_CHANGED = "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" + @Suppress("PrivatePropertyName") + private val EXTRA_BATTERY_LEVEL = "android.bluetooth.device.extra.BATTERY_LEVEL" + @Suppress("PrivatePropertyName") + private val PACKAGE_ASI = "com.google.android.settings.intelligence" + @Suppress("PrivatePropertyName") + private val ACTION_ASI_UPDATE_BLUETOOTH_DATA = "batterywidget.impl.action.update_bluetooth_data" + + @Suppress("MissingPermission") + fun broadcastBatteryInformation() { + if (device == null) return + + val batteryList = batteryNotification.getBattery() + val leftBattery = batteryList.find { it.component == BatteryComponent.LEFT } + val rightBattery = batteryList.find { it.component == BatteryComponent.RIGHT } + + // Calculate unified battery level (minimum of left and right) + val batteryUnified = minOf( + leftBattery?.level ?: 100, + rightBattery?.level ?: 100 + ) + + // Check charging status + val isLeftCharging = leftBattery?.status == BatteryStatus.CHARGING + val isRightCharging = rightBattery?.status == BatteryStatus.CHARGING + isLeftCharging && isRightCharging + + // Create arguments for vendor-specific event + val arguments = arrayOf( + 1, // Number of key/value pairs + VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL, // IndicatorType: Battery Level + batteryUnified // Battery Level + ) + + // Broadcast vendor-specific event + val intent = Intent(android.bluetooth.BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT).apply { + putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV) + putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, android.bluetooth.BluetoothHeadset.AT_CMD_TYPE_SET) + putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments) + putExtra(BluetoothDevice.EXTRA_DEVICE, device) + putExtra(BluetoothDevice.EXTRA_NAME, device?.name) + addCategory("${android.bluetooth.BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.$APPLE") } - val showIslandReceiver = object: BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action == "me.kavishdevar.librepods.cross_device_island") { - showIsland(this@AirPodsService, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!)) - } else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { - try { - context?.unregisterReceiver(this) - } catch (e: Exception) { - e.printStackTrace() - } - } + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + sendBroadcastAsUser( + intent, + UserHandle.getUserHandleForUid(-1), + Manifest.permission.BLUETOOTH_CONNECT + ) + } else { + sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(-1)) } + } catch (e: Exception) { + Log.e("AirPodsService", "Failed to send vendor-specific event: ${e.message}") } - val showIslandIntentFilter = IntentFilter().apply { - addAction("me.kavishdevar.librepods.cross_device_island") - addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) + // Broadcast battery level changes + val batteryIntent = Intent(ACTION_BATTERY_LEVEL_CHANGED).apply { + putExtra(BluetoothDevice.EXTRA_DEVICE, device) + putExtra(EXTRA_BATTERY_LEVEL, batteryUnified) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(showIslandReceiver, showIslandIntentFilter, RECEIVER_EXPORTED) - } else { - registerReceiver(showIslandReceiver, showIslandIntentFilter) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + sendBroadcast(batteryIntent, Manifest.permission.BLUETOOTH_CONNECT) + } else { + sendBroadcastAsUser(batteryIntent, UserHandle.getUserHandleForUid(-1)) + } + } catch (e: Exception) { + Log.e("AirPodsService", "Failed to send battery level broadcast: ${e.message}") } - val deviceIntentFilter = IntentFilter().apply { - addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) - addAction(AirPodsNotifications.AIRPODS_DISCONNECTED) + // Update Android Settings Intelligence's battery widget + val statusIntent = Intent(ACTION_ASI_UPDATE_BLUETOOTH_DATA).apply { + setPackage(PACKAGE_ASI) + putExtra(ACTION_BATTERY_LEVEL_CHANGED, intent) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED) - registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED) - } else { - registerReceiver(connectionReceiver, deviceIntentFilter) - registerReceiver(bluetoothReceiver, serviceIntentFilter) + try { + sendBroadcastAsUser(statusIntent, UserHandle.getUserHandleForUid(-1)) + } catch (e: Exception) { + Log.e("AirPodsService", "Failed to send ASI battery level broadcast: ${e.message}") } - val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter + Log.d("AirPodsService", "Broadcast battery level $batteryUnified% to system") + } - bluetoothAdapter.bondedDevices.forEach { device -> - device.fetchUuidsWithSdp() - if (device.uuids != null) { - if (device.uuids.contains(ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { - bluetoothAdapter.getProfileProxy( - this, - object : BluetoothProfile.ServiceListener { - override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { - if (profile == BluetoothProfile.A2DP) { - val connectedDevices = proxy.connectedDevices - if (connectedDevices.isNotEmpty()) { - if (!CrossDevice.isAvailable && !config.bleOnlyMode) { - CoroutineScope(Dispatchers.IO).launch { - connectToSocket(device) - } - setMetadatas(device) - macAddress = device.address - sharedPreferences.edit { - putString("mac_address", macAddress) - } - } else if (config.bleOnlyMode) { - Log.d("AirPodsService", "BLE-only mode: skipping L2CAP connection") - macAddress = device.address - sharedPreferences.edit { - putString("mac_address", macAddress) - } - } - this@AirPodsService.sendBroadcast( - Intent(AirPodsNotifications.AIRPODS_CONNECTED) - ) - } - } - bluetoothAdapter.closeProfileProxy(profile, proxy) - } + private fun setMetadatas(d: BluetoothDevice) { + d.let{ device -> + val metadataSet = SystemApisUtils.setMetadata( + device, + device.METADATA_MAIN_ICON, + resToUri(R.drawable.pro_2).toString().toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_MODEL_NAME, + "AirPods Pro (2 Gen.)".toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_DEVICE_TYPE, + device.DEVICE_TYPE_UNTETHERED_HEADSET.toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_UNTETHERED_CASE_ICON, + resToUri(R.drawable.pro_2_case).toString().toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_UNTETHERED_RIGHT_ICON, + resToUri(R.drawable.pro_2_right).toString().toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_UNTETHERED_LEFT_ICON, + resToUri(R.drawable.pro_2_left).toString().toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_MANUFACTURER_NAME, + "Apple".toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_COMPANION_APP, + "me.kavisdevar.librepods".toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD, + "20".toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, + "20".toByteArray() + ) && + SystemApisUtils.setMetadata( + device, + device.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, + "20".toByteArray() + ) + Log.d("AirPodsService", "Metadata set: $metadataSet") + } + } - override fun onServiceDisconnected(profile: Int) {} - }, - BluetoothProfile.A2DP + @Suppress("ClassName") + private object bluetoothReceiver : BroadcastReceiver() { + @SuppressLint("MissingPermission") + override fun onReceive(context: Context?, intent: Intent) { + val bluetoothDevice = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + "android.bluetooth.device.extra.DEVICE", + BluetoothDevice::class.java ) + } else { + intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE") as BluetoothDevice? + } + val action = intent.action + val context = context?.applicationContext + val name = context?.getSharedPreferences("settings", MODE_PRIVATE) + ?.getString("name", bluetoothDevice?.name) + if (bluetoothDevice != null && action != null && !action.isEmpty()) { + Log.d("AirPodsService", "Received bluetooth connection broadcast") + if (ServiceManager.getService()?.isConnectedLocally == true) { + Log.d("AirPodsService", "Checking if audio should be connected") + ServiceManager.getService()?.manuallyCheckForAudioSource() + return + } + if (BluetoothDevice.ACTION_ACL_CONNECTED == action) { + val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") + bluetoothDevice.fetchUuidsWithSdp() + if (bluetoothDevice.uuids != null) { + if (bluetoothDevice.uuids.contains(uuid)) { + val intent = + Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) + intent.putExtra("name", name) + intent.putExtra("device", bluetoothDevice) + context?.sendBroadcast(intent) + } + } } } } + } - if (!isConnectedLocally && !CrossDevice.isAvailable) { - clearPacketLogs() - } + val ancModeFilter = IntentFilter("me.kavishdevar.librepods.SET_ANC_MODE") + var ancModeReceiver: BroadcastReceiver? = null - CoroutineScope(Dispatchers.IO).launch { - bleManager.startScanning() + @SuppressLint("InlinedApi", "MissingPermission", "UnspecifiedRegisterReceiverFlag") + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d("AirPodsService", "Service started with intent action: ${intent?.action}") + + if (intent?.action == "me.kavishdevar.librepods.RECONNECT_AFTER_REVERSE") { + Log.d("AirPodsService", "reconnect after reversed received, taking over") + disconnectedBecauseReversed = false + takeOver("music", manualTakeOverAfterReversed = true) } return START_STICKY @@ -1916,7 +2023,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList fun manuallyCheckForAudioSource() { val shouldResume = MediaController.getMusicActive() - if (earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) { + if ((earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) || disconnectedBecauseReversed) { Log.d( "AirPodsService", "For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!" @@ -1926,10 +2033,67 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } @RequiresApi(Build.VERSION_CODES.R) - @SuppressLint("MissingPermission") - fun takeOver(takingOverFor: String) { + @SuppressLint("MissingPermission", "HardwareIds") + fun takeOver(takingOverFor: String, manualTakeOverAfterReversed: Boolean = false, startHeadTrackingAgain: Boolean = false) { + if (takingOverFor == "reverse") { + aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION.value, + 1 + ) + aacpManager.sendMediaInformataion( + localMac + ) + aacpManager.sendHijackReversed( + localMac + ) + } + Log.d("AirPodsService", "owns connection: ${aacpManager.getControlCommandStatus(AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION)?.value?.get(0)?.toInt()}") if (isConnectedLocally) { - Log.d("AirPodsService", "Already connected locally, skipping") + if (aacpManager.getControlCommandStatus(AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION)?.value[0]?.toInt() != 1 || (aacpManager.audioSource?.mac != localMac && aacpManager.audioSource?.type != AACPManager.Companion.AudioSourceType.NONE)) { + if (disconnectedBecauseReversed) { + if (manualTakeOverAfterReversed) { + Log.d("AirPodsService", "forcefully taking over despite reverse as user requested") + connectAudio(this, device) + disconnectedBecauseReversed = false + } else { + Log.d("AirPodsService", "connected locally, but can not hijack as other device had reversed") + return + } + } + + Log.d("AirPodsService", "already connected locally, hijacking connection by asking AirPods") + aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION.value, + 1 + ) + aacpManager.sendMediaInformataion( + localMac + ) + aacpManager.sendSmartRoutingShowUI( + localMac + ) + aacpManager.sendHijackRequest( + localMac + ) + showIsland(this, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!), + IslandType.CONNECTED) + + CoroutineScope(Dispatchers.IO).launch { + delay(500) + if (takingOverFor == "music") { + MediaController.sendPlay() + } else if (startHeadTrackingAgain) { + Log.d("AirPodsService", "Starting head tracking again after taking control") + if (sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false)) { + aacpManager.sendDataPacket(aacpManager.createAlternateStartHeadTrackingPacket()) + } else { + aacpManager.sendStartHeadTracking() + } + } + } + } else { + Log.d("AirPodsService", "Already connected locally and already own connection, skipping takeover") + } return } @@ -1972,7 +2136,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (takingOverFor == "music") { Log.d("AirPodsService", "Pausing music so that it doesn't play through speakers") - MediaController.pausedForCrossDevice = true + MediaController.pausedWhileTakingOver = true MediaController.sendPause(true) } else { handleIncomingCallOnceConnected = true @@ -2004,7 +2168,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList isConnectedLocally = true } } - showIsland(this, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!), IslandType.TAKING_OVER) @@ -2150,6 +2313,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } else if (bytesRead == -1) { Log.d("AirPods Service", "Socket closed (bytesRead = -1)") sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED)) + aacpManager.disconnected() return@launch } } @@ -2157,6 +2321,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList Log.d("AirPods Service", "Socket closed") isConnectedLocally = false socket.close() + aacpManager.disconnected() sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED)) } } @@ -2174,7 +2339,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList fun disconnect() { if (!this::socket.isInitialized) return socket.close() - MediaController.pausedForCrossDevice = false + MediaController.pausedWhileTakingOver = false Log.d("AirPodsService", "Disconnected from AirPods, showing island.") showIsland(this, batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level!!.coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level!!), IslandType.MOVED_TO_REMOTE) @@ -2285,7 +2450,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList e.printStackTrace() } finally { bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, proxy) - if (MediaController.pausedForCrossDevice) { + if (MediaController.pausedWhileTakingOver) { MediaController.sendPlay() } } @@ -2374,6 +2539,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList fun startHeadTracking() { isHeadTrackingActive = true val useAlternatePackets = sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + takeOver("call", startHeadTrackingAgain = true) + Log.d("AirPodsService", "Taking over for head tracking") + } else { + Log.w("AirPodsService", "Will not be taking over for head tracking, might not work.") + } if (useAlternatePackets) { aacpManager.sendDataPacket(aacpManager.createAlternateStartHeadTrackingPacket()) } else { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 34b60528..3eeebb14 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -21,12 +21,9 @@ package me.kavishdevar.librepods.utils import android.util.Log -import me.kavishdevar.librepods.utils.AACPManager.Companion.ControlCommandIdentifiers.entries -import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressBudType.entries -import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType.entries -import kotlin.io.encoding.ExperimentalEncodingApi import java.nio.ByteBuffer import java.nio.ByteOrder +import kotlin.io.encoding.ExperimentalEncodingApi /** * Manager class for Apple Accessory Communication Protocol (AACP) @@ -50,7 +47,13 @@ class AACPManager { const val PROXIMITY_KEYS_REQ: Byte = 0x30 const val PROXIMITY_KEYS_RSP: Byte = 0x31 const val STEM_PRESS: Byte = 0x19 - const val EQ_SETTINGS: Byte = 0x35 + const val EQ_DATA: Byte = 0x53 + const val CONNECTED_DEVICES: Byte = 0x2E // TiPi 1 + const val AUDIO_SOURCE: Byte = 0x0E // TiPi 2 + const val SMART_ROUTING: Byte = 0x10 + const val TIPI_3: Byte = 0x0C // Don't know this one + const val SMART_ROUTING_RESP: Byte = 0x11 + const val SEND_CONNECTED_MAC: Byte = 0x14 } private val HEADER_BYTES = byteArrayOf(0x04, 0x00, 0x04, 0x00) @@ -78,7 +81,7 @@ class AACPManager { } } -// @Suppress("unused") + // @Suppress("unused") enum class ControlCommandIdentifiers(val value: Byte) { MIC_MODE(0x01), BUTTON_SEND_MODE(0x05), @@ -109,7 +112,9 @@ class AACPManager { SIRI_MULTITONE_CONFIG(0x32), HEARING_ASSIST_CONFIG(0x33), ALLOW_OFF_OPTION(0x34), - STEM_CONFIG(0x39); + STEM_CONFIG(0x39), + OWNS_CONNECTION(0x06); + companion object { fun fromByte(byte: Byte): ControlCommandIdentifiers? = entries.find { it.value == byte } @@ -122,7 +127,8 @@ class AACPManager { companion object { fun fromByte(byte: Byte): ProximityKeyType = - ProximityKeyType.entries.find { it.value == byte }?: throw IllegalArgumentException("Unknown ProximityKeyType: $byte") + ProximityKeyType.entries.find { it.value == byte } + ?: throw IllegalArgumentException("Unknown ProximityKeyType: $byte") } } @@ -147,15 +153,55 @@ class AACPManager { entries.find { it.value == byte } } } + + enum class AudioSourceType(val value: Byte) { + NONE(0x00), + CALL(0x01), + MEDIA(0x02); + + companion object { + fun fromByte(byte: Byte): AudioSourceType? = + entries.find { it.value == byte } + } + } + + data class AudioSource( + val mac: String, + val type: AudioSourceType + ) + + data class ConnectedDevice( + val mac: String, + val info1: Byte, + val info2: Byte + ) } - var controlCommandStatusList: MutableList = mutableListOf() - var controlCommandListeners: MutableMap> = mutableMapOf() + + var controlCommandStatusList: MutableList = + mutableListOf() + var controlCommandListeners: MutableMap> = + mutableMapOf() + + var owns: Boolean = false + private set + + var oldConnectedDevices: List = listOf() + private set + + var connectedDevices: List = listOf() + private set + + var audioSource: AudioSource? = null + private set fun getControlCommandStatus(identifier: ControlCommandIdentifiers): ControlCommandStatus? { return controlCommandStatusList.find { it.identifier == identifier } } - private fun setControlCommandStatusValue(identifier: ControlCommandIdentifiers, value: ByteArray) { + private fun setControlCommandStatusValue( + identifier: ControlCommandIdentifiers, + value: ByteArray + ) { val existingStatus = getControlCommandStatus(identifier) if (existingStatus == value) { controlCommandStatusList.remove(existingStatus) @@ -167,6 +213,10 @@ class AACPManager { listener.onControlCommandReceived(ControlCommand(identifier.value, value)) } controlCommandStatusList.add(ControlCommandStatus(identifier, value)) + + if (identifier == ControlCommandIdentifiers.OWNS_CONNECTION) { + owns = value.isNotEmpty() && value[0] == 0x01.toByte() + } } interface PacketCallback { @@ -179,6 +229,11 @@ class AACPManager { fun onUnknownPacketReceived(packet: ByteArray) fun onProximityKeysReceived(proximityKeys: ByteArray) fun onStemPressReceived(stemPress: ByteArray) + fun onAudioSourceReceived(audioSource: ByteArray) + fun onOwnershipChangeReceived(owns: Boolean) + fun onConnectedDevicesReceived(connectedDevices: List) + fun onOwnershipToFalseRequest(reasonReverseTapped: Boolean) + fun onShowNearbyUI() } fun parseStemPressResponse(data: ByteArray): Pair { @@ -189,8 +244,10 @@ class AACPManager { if (data[4] != Opcodes.STEM_PRESS) { throw IllegalArgumentException("Data array does not start with STEM_PRESS opcode") } - val type = StemPressType.fromByte(data[6]) ?: throw IllegalArgumentException("Unknown Stem Press Type: ${data[5]}") - val bud = StemPressBudType.fromByte(data[7]) ?: throw IllegalArgumentException("Unknown Stem Press Bud Type: ${data[6]}") + val type = StemPressType.fromByte(data[6]) + ?: throw IllegalArgumentException("Unknown Stem Press Type: ${data[5]}") + val bud = StemPressBudType.fromByte(data[7]) + ?: throw IllegalArgumentException("Unknown Stem Press Bud Type: ${data[6]}") return Pair(type, bud) } @@ -198,7 +255,10 @@ class AACPManager { fun onControlCommandReceived(controlCommand: ControlCommand) } - fun registerControlCommandListener(identifier: ControlCommandIdentifiers, callback: ControlCommandListener) { + fun registerControlCommandListener( + identifier: ControlCommandIdentifiers, + callback: ControlCommandListener + ) { controlCommandListeners.getOrPut(identifier) { mutableListOf() }.add(callback) } @@ -249,7 +309,10 @@ class AACPManager { } fun sendControlCommand(identifier: Byte, value: Boolean): Boolean { - val controlPacket = createControlCommandPacket(identifier, if (value) byteArrayOf(0x01) else byteArrayOf(0x02)) + val controlPacket = createControlCommandPacket( + identifier, + if (value) byteArrayOf(0x01) else byteArrayOf(0x02) + ) setControlCommandStatusValue( ControlCommandIdentifiers.fromByte(identifier) ?: return false, if (value) byteArrayOf(0x01) else byteArrayOf(0x02) @@ -267,7 +330,10 @@ class AACPManager { } fun parseProximityKeysResponse(data: ByteArray): Map { - Log.d(TAG, "Parsing Proximity Keys Response: ${data.joinToString(" ") { "%02X".format(it) }}") + Log.d( + TAG, + "Parsing Proximity Keys Response: ${data.joinToString(" ") { "%02X".format(it) }}" + ) if (data.size < 4) { throw IllegalArgumentException("Data array too short to parse Proximity Keys Response") } @@ -293,7 +359,12 @@ class AACPManager { System.arraycopy(data, offset, key, 0, keyLength) keys[ProximityKeyType.fromByte(keyType)] = key offset += keyLength - Log.d(TAG, "Parsed Proximity Key: Type: ${keyType}, Length: $keyLength, Key: ${key.joinToString(" ") { "%02X".format(it) }}") + Log.d( + TAG, + "Parsed Proximity Key: Type: ${keyType}, Length: $keyLength, Key: ${ + key.joinToString(" ") { "%02X".format(it) } + }" + ) } return keys } @@ -312,11 +383,21 @@ class AACPManager { @OptIn(ExperimentalStdlibApi::class) fun receivePacket(packet: ByteArray) { if (!packet.toHexString().startsWith("04000400")) { - Log.w(TAG, "Received packet does not start with expected header: ${packet.joinToString(" ") { "%02X".format(it) }}") + Log.w( + TAG, + "Received packet does not start with expected header: ${ + packet.joinToString(" ") { + "%02X".format(it) + } + }" + ) return } if (packet.size < 6) { - Log.w(TAG, "Received packet too short: ${packet.joinToString(" ") { "%02X".format(it) }}") + Log.w( + TAG, + "Received packet too short: ${packet.joinToString(" ") { "%02X".format(it) }}" + ) return } @@ -326,50 +407,111 @@ class AACPManager { Opcodes.BATTERY_INFO -> { callback?.onBatteryInfoReceived(packet) } + Opcodes.CONTROL_COMMAND -> { val controlCommand = ControlCommand.fromByteArray(packet) setControlCommandStatusValue( ControlCommandIdentifiers.fromByte(controlCommand.identifier) ?: return, controlCommand.value ) - Log.d(TAG, "Control command received: ${controlCommand.identifier.toHexString()} - ${controlCommand.value.joinToString(" ") { "%02X".format(it) }}") - Log.d(TAG, "Control command list is now: ${ - controlCommandStatusList.joinToString(", ") { "${it.identifier.name} (${it.identifier.value.toHexString()}) - ${it.value.joinToString(" ") { "%02X".format(it) }}" } + Log.d( + TAG, + "Control command received: ${controlCommand.identifier.toHexString()} - ${ + controlCommand.value.joinToString(" ") { "%02X".format(it) } + }" + ) + Log.d( + TAG, "Control command list is now: ${ + controlCommandStatusList.joinToString(", ") { + "${it.identifier.name} (${it.identifier.value.toHexString()}) - ${ + it.value.joinToString( + " " + ) { "%02X".format(it) } + }" + } }") - val controlCommandIdentifier = ControlCommandIdentifiers.fromByte(controlCommand.identifier) + val controlCommandIdentifier = + ControlCommandIdentifiers.fromByte(controlCommand.identifier) if (controlCommandIdentifier != null) { controlCommandListeners[controlCommandIdentifier]?.forEach { listener -> listener.onControlCommandReceived(controlCommand) } } else { - Log.w(TAG, "Unknown control command identifier: ${controlCommand.identifier.toHexString()}") + Log.w( + TAG, + "Unknown control command identifier: ${controlCommand.identifier.toHexString()}" + ) + } + + if (controlCommandIdentifier == ControlCommandIdentifiers.OWNS_CONNECTION) { + callback?.onOwnershipChangeReceived(owns) } callback?.onControlCommandReceived(packet) } + Opcodes.EAR_DETECTION -> { callback?.onEarDetectionReceived(packet) } + Opcodes.CONVERSATION_AWARENESS -> { callback?.onConversationAwarenessReceived(packet) } + Opcodes.DEVICE_METADATA -> { callback?.onDeviceMetadataReceived(packet) } + Opcodes.HEADTRACKING -> { if (packet.size < 70) { - Log.w(TAG, "Received HEADTRACKING packet too short: ${packet.joinToString(" ") { "%02X".format(it) }}") + Log.w( + TAG, + "Received HEADTRACKING packet too short: ${ + packet.joinToString(" ") { + "%02X".format(it) + } + }" + ) return } callback?.onHeadTrackingReceived(packet) } + Opcodes.PROXIMITY_KEYS_RSP -> { callback?.onProximityKeysReceived(packet) } + Opcodes.STEM_PRESS -> { callback?.onStemPressReceived(packet) } + + Opcodes.AUDIO_SOURCE -> { + try { + val (mac, type) = parseAudioSourceResponse(packet) + audioSource = AudioSource(mac, type) + } catch (e: Exception) { + Log.e(TAG, "Error parsing audio source response: ${e.message}") + } + callback?.onAudioSourceReceived(packet) + } + + Opcodes.CONNECTED_DEVICES -> { + oldConnectedDevices = connectedDevices + connectedDevices = parseConnectedDevicesResponse(packet) + callback?.onConnectedDevicesReceived(connectedDevices) + } + + Opcodes.SMART_ROUTING_RESP -> { + val packetString = packet.decodeToString() + if (packetString.contains("SetOwnershipToFalse")) { + callback?.onOwnershipToFalseRequest(packetString.contains("ReverseBannerTapped")) + } + if (packetString.contains("ShowNearbyUI")) { + callback?.onShowNearbyUI() + } + } + else -> { callback?.onUnknownPacketReceived(packet) } @@ -412,7 +554,28 @@ class AACPManager { fun createStartHeadTrackingPacket(): ByteArray { val opcode = byteArrayOf(Opcodes.HEADTRACKING, 0x00) val data = byteArrayOf( - 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x08, 0xA1.toByte(), 0x02, 0x42, 0x0B, 0x08, 0x0E, 0x10, 0x02, 0x1A, 0x05, 0x01, 0x40, 0x9C.toByte(), 0x00, 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x08, + 0xA1.toByte(), + 0x02, + 0x42, + 0x0B, + 0x08, + 0x0E, + 0x10, + 0x02, + 0x1A, + 0x05, + 0x01, + 0x40, + 0x9C.toByte(), + 0x00, + 0x00, ) return opcode + data } @@ -420,7 +583,27 @@ class AACPManager { fun createAlternateStartHeadTrackingPacket(): ByteArray { val opcode = byteArrayOf(Opcodes.HEADTRACKING, 0x00) val data = byteArrayOf( - 0x00, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x08, 0x73, 0x42, 0x0B, 0x08, 0x10, 0x10, 0x02, 0x1A, 0x05, 0x01, 0x40, 0x9C.toByte(), 0x00, 0x00 + 0x00, + 0x00, + 0x10, + 0x00, + 0x0F, + 0x00, + 0x08, + 0x73, + 0x42, + 0x0B, + 0x08, + 0x10, + 0x10, + 0x02, + 0x1A, + 0x05, + 0x01, + 0x40, + 0x9C.toByte(), + 0x00, + 0x00 ) return opcode + data } @@ -432,7 +615,29 @@ class AACPManager { fun createStopHeadTrackingPacket(): ByteArray { val opcode = byteArrayOf(Opcodes.HEADTRACKING, 0x00) val data = byteArrayOf( - 0x00, 0x00, 0x10, 0x00, 0x11, 0x00, 0x08, 0x7E, 0x10, 0x02, 0x42, 0x0B, 0x08, 0x4E, 0x10, 0x02, 0x1A, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00 + 0x00, + 0x00, + 0x10, + 0x00, + 0x11, + 0x00, + 0x08, + 0x7E, + 0x10, + 0x02, + 0x42, + 0x0B, + 0x08, + 0x4E, + 0x10, + 0x02, + 0x1A, + 0x05, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00 ) return opcode + data } @@ -440,7 +645,27 @@ class AACPManager { fun createAlternateStopHeadTrackingPacket(): ByteArray { val opcode = byteArrayOf(Opcodes.HEADTRACKING, 0x00) val data = byteArrayOf( - 0x00, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x08, 0x75, 0x42, 0x0B, 0x08, 0x10, 0x10, 0x02, 0x1A, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00 + 0x00, + 0x00, + 0x10, + 0x00, + 0x0F, + 0x00, + 0x08, + 0x75, + 0x42, + 0x0B, + 0x08, + 0x10, + 0x10, + 0x02, + 0x1A, + 0x05, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00 ) return opcode + data } @@ -462,6 +687,254 @@ class AACPManager { return packet } + fun sendMediaInformationNewDevice(selfMacAddress: String, targetMacAddress: String): Boolean { + if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")) || targetMacAddress.length != 17 || !targetMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { + throw IllegalArgumentException("MAC address must be 6 bytes") + } + Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: ${targetMacAddress}") + Log.d(TAG, "Sending Media Information packet to ${targetMacAddress}") + return sendDataPacket(createMediaInformationNewDevicePacket(selfMacAddress, targetMacAddress)) + } + + fun createMediaInformationNewDevicePacket(selfMacAddress: String, targetMacAddress: String): ByteArray { + val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) + val buffer = ByteBuffer.allocate(112) + buffer.put( + targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() + ) + buffer.put(byteArrayOf(0x68, 0x00)) + buffer.put(byteArrayOf(0x01, 0xE5.toByte(), 0x4A)) + buffer.put("playingApp".toByteArray()) + buffer.put(0x42) + buffer.put("NA".toByteArray()) + buffer.put(0x52) + buffer.put("hostStreamingState".toByteArray()) + buffer.put(0x42) + buffer.put("NO".toByteArray()) + buffer.put(0x49) + buffer.put("btAddress".toByteArray()) + buffer.put(0x51) + buffer.put(selfMacAddress.toByteArray()) + buffer.put(0x46) + buffer.put("btName".toByteArray()) + buffer.put(0x43) + buffer.put("And".toByteArray()) + buffer.put(0x58) + buffer.put("otherDevice".toByteArray()) + buffer.put("AudioCategory".toByteArray()) + buffer.put(byteArrayOf(0x30, 0x64)) + + return opcode + buffer.array() + } + + fun sendHijackRequest(selfMacAddress: String): Boolean { + if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { + throw IllegalArgumentException("MAC address must be 6 bytes") + } + var success = false + for (connectedDevice in connectedDevices) { + if (connectedDevice.mac != selfMacAddress) { + Log.d(TAG, "Sending Hijack Request packet to ${connectedDevice.mac}") + success = sendDataPacket(createHijackRequestPacket(connectedDevice.mac)) && success + } + } + return success + } + + fun createHijackRequestPacket(targetMacAddress: String): ByteArray { + val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) + val buffer = ByteBuffer.allocate(106) + buffer.put( + targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() + ) + // 620001E54A6C6F63616C73636F7265306446726561736F6E4848696A61636B763251617564696F526F7574696E6753636F7265312D015F617564696F526F7574696E675365744F776E657273686970546F46616C7365014B72656D6F746573636F7265A5 + + buffer.put(byteArrayOf(0x62, 0x00)) + buffer.put(byteArrayOf(0x01, 0xE5.toByte())) + buffer.put(0x4A) + buffer.put("localscore".toByteArray()) + buffer.put(byteArrayOf(0x30, 0x64)) + buffer.put(0x46) + buffer.put("reason".toByteArray()) + buffer.put(0x48) + buffer.put("Hijackv2".toByteArray()) + buffer.put(0x51) + buffer.put("audioRoutingScore".toByteArray()) + buffer.put(byteArrayOf(0x31, 0x2D, 0x01, 0x5F)) + buffer.put("audioRoutingSetOwnershipToFalse".toByteArray()) + buffer.put(0x01) + buffer.put(0x4B) + buffer.put("remotescore".toByteArray()) + buffer.put(0xA5.toByte()) + + return opcode + buffer.array() + } + + fun sendMediaInformataion(selfMacAddress: String, streamingState: Boolean = false): Boolean { + if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { + throw IllegalArgumentException("MAC address must be 6 bytes") + } + Log.d(TAG, "SELFMAC: ${selfMacAddress}") + val targetMac = connectedDevices.find { it.mac != selfMacAddress }?.mac + Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}") + return sendDataPacket( + createMediaInformationPacket( + selfMacAddress, + targetMac ?: return false, + streamingState + ) + ) + } + + fun createMediaInformationPacket( + selfMacAddress: String, + targetMacAddress: String, + streamingState: Boolean = true + ): ByteArray { + val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) + val buffer = ByteBuffer.allocate(134) + buffer.put( + targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() + ) + buffer.put( + byteArrayOf( + 0x7E, + 0x00 + ) + ) // something to do with the length, can't confirm, but changing causes airpods to soft reset + buffer.put(byteArrayOf(0x01, 0xE5.toByte(), 0x4A)) // unknown, constant + buffer.put("PlayingApp".toByteArray()) + buffer.put(byteArrayOf(0x56)) // 'V', seems like a identifier or a separator + buffer.put("com.google.ios.youtube".toByteArray()) // package name, hardcoding for now, aforementioned reason + buffer.put(byteArrayOf(0x52)) // 'R' + buffer.put("HostStreamingState".toByteArray()) + buffer.put(byteArrayOf(0x42)) // 'B' + buffer.put((if (streamingState) "YES" else "NO").toByteArray()) // streaming state + buffer.put(0x49) // 'I' + buffer.put("btAddress".toByteArray()) // self MAC + buffer.put(0x51) // 'Q' + buffer.put(selfMacAddress.toByteArray()) // self MAC + buffer.put("btName".toByteArray()) // self name + buffer.put(0x44) // 'D' + buffer.put("iPho".toByteArray()) // if set to iPad, shows "Moved to iPad, but most likely we're running on a phone. setting to anything else of the same length will show iPhone instead. + buffer.put(0x58) // 'X' + buffer.put("otherDevice".toByteArray()) + buffer.put("AudioCategory".toByteArray()) + buffer.put(byteArrayOf(0x31, 0x2D, 0x01)) + + return opcode+buffer.array() + } + + fun sendSmartRoutingShowUI(selfMacAddress: String): Boolean { + if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { + throw IllegalArgumentException("MAC address must be 6 bytes") + } + + val targetMac = connectedDevices.find { it.mac != selfMacAddress }?.mac + if (targetMac == null) { + Log.w(TAG, "Cannot send Smart Routing Show UI packet: No connected device found") + return false + } + Log.d(TAG, "Sending Smart Routing Show UI packet to $targetMac") + return sendDataPacket(createSmartRoutingShowUIPacket(targetMac)) + } + + fun createSmartRoutingShowUIPacket(targetMacAddress: String): ByteArray { + val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) + val buffer = ByteBuffer.allocate(134) + buffer.put( + targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() + ) + buffer.put(byteArrayOf(0x7E, 0x00)) + buffer.put(byteArrayOf(0x01, 0xE6.toByte(), 0x5B)) + buffer.put("SmartRoutingKeyShowNearbyUI".toByteArray()) + buffer.put(0x01) // separator? + buffer.put(0x4A) + buffer.put("localscore".toByteArray()) + buffer.put(0x31, 0x2D) + buffer.put(0x01) + buffer.put(0x46) + buffer.put("reasonHhijackv2".toByteArray()) + buffer.put(0x51.toByte()) + buffer.put("audioRoutingScore".toByteArray()) + buffer.put(0xA2.toByte()) + buffer.put(0x5F) + buffer.put("audioRoutingSetOwnershipToFalse".toByteArray()) + buffer.put(0x01) + buffer.put(0x4B) + buffer.put("remotescore".toByteArray()) + buffer.put(0xA2.toByte()) + return opcode + buffer.array() + } + + fun sendHijackReversed(selfMacAddress: String): Boolean { + var success = false + for (connectedDevice in connectedDevices) { + if (connectedDevice.mac != selfMacAddress) { + Log.d(TAG, "Sending Hijack Reversed packet to ${connectedDevice.mac}") + success = sendDataPacket(createHijackReversedPacket(connectedDevice.mac)) && success + } + } + return success + } + + fun createHijackReversedPacket(targetMacAddress: String): ByteArray { + val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) + val buffer = ByteBuffer.allocate(97) + buffer.put( + targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() + ) + buffer.put(byteArrayOf(0x59, 0x00)) + buffer.put(byteArrayOf(0x01, 0xE3.toByte())) + buffer.put(0x5F) + buffer.put("audioRoutingSetOwnershipToFalse".toByteArray()) + buffer.put(0x01) + buffer.put(0x59) + buffer.put("audioRoutingShowReverseUI".toByteArray()) + buffer.put(0x01) + buffer.put(0x46) + buffer.put("reason".toByteArray()) + buffer.put(0x53) + buffer.put("ReverseBannerTapped".toByteArray()) + + return opcode + buffer.array() + } + + + fun sendAddTiPiDevice(selfMacAddress: String, targetMacAddress: String): Boolean { + if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")) || targetMacAddress.length != 17 || !targetMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { + throw IllegalArgumentException("MAC address must be 6 bytes") + } + + Log.d(TAG, "Sending Add TiPi Device packet to $targetMacAddress") + return sendDataPacket(createAddTiPiDevicePacket(selfMacAddress, targetMacAddress)) + } + + fun createAddTiPiDevicePacket(selfMacAddress: String, targetMacAddress: String): ByteArray { + val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) + val buffer = ByteBuffer.allocate(86) + buffer.put( + targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() + ) + buffer.put(byteArrayOf(0x4E, 0x00)) + buffer.put(byteArrayOf(0x01, 0xE5.toByte())) + buffer.put(0x48) // 'H' + buffer.put("idleTime".toByteArray()) + buffer.put(byteArrayOf(0x08, 0x47)) + buffer.put("newTipi".toByteArray()) + buffer.put(byteArrayOf(0x01, 0x49)) + buffer.put("btAddress".toByteArray()) + buffer.put(0x51) + buffer.put(selfMacAddress.toByteArray()) + buffer.put(0x46) + buffer.put("btName".toByteArray()) + buffer.put(0x43) + buffer.put("And".toByteArray()) + buffer.put(0x50) + buffer.put("nearbyAudioScore".toByteArray()) + buffer.put(byteArrayOf(0x0E)) + return opcode + buffer.array() + } data class ControlCommand( val identifier: Byte, @@ -503,8 +976,9 @@ class AACPManager { val value = ByteArray(4) System.arraycopy(data, 3, value, 0, 4) - val trimmedValue = value.takeWhile { it != 0x00.toByte() }.toByteArray() - return ControlCommand(identifier, trimmedValue) + val trimmedValue = value.dropLastWhile { it == 0x00.toByte() }.toByteArray() + val finalValue = if (trimmedValue.isEmpty()) byteArrayOf(0x00) else trimmedValue + return ControlCommand(identifier, finalValue) } } } @@ -526,14 +1000,19 @@ class AACPManager { ) } - @OptIn(ExperimentalStdlibApi::class) - fun sendPacket(packet: ByteArray): Boolean { + @OptIn(ExperimentalStdlibApi::class) + fun sendPacket(packet: ByteArray): Boolean { try { Log.d(TAG, "Sending packet: ${packet.joinToString(" ") { "%02X".format(it) }}") if (packet[4] == Opcodes.CONTROL_COMMAND) { val controlCommand = ControlCommand.fromByteArray(packet) - Log.d(TAG, "Control command: ${controlCommand.identifier.toHexString()} - ${controlCommand.value.joinToString(" ") { "%02X".format(it) }}") + Log.d( + TAG, + "Control command: ${controlCommand.identifier.toHexString()} - ${ + controlCommand.value.joinToString(" ") { "%02X".format(it) } + }" + ) setControlCommandStatusValue( ControlCommandIdentifiers.fromByte(controlCommand.identifier) ?: return false, controlCommand.value @@ -555,33 +1034,22 @@ class AACPManager { } } - fun sendEQPacket(eqFloats: FloatArray, phone: Boolean, media: Boolean): Boolean { - val buffer = ByteBuffer.allocate(140).order(ByteOrder.LITTLE_ENDIAN) - buffer.put(0x04) - buffer.put(0x00) - buffer.put(0x04) - buffer.put(0x00) - buffer.put(0x53) - buffer.put(0x00) - buffer.put(0x84.toByte()) - buffer.put(0x00) - buffer.put(0x02) - buffer.put(0x02) - buffer.put(if (phone) 0x01 else 0x00) - buffer.put(if (media) 0x01 else 0x00) - for (i in 0..7) { - buffer.putFloat(eqFloats[i]) - } - while (buffer.hasRemaining()) { - buffer.put(0x00) - } - val packet = buffer.array() - return sendPacket(packet) - } - fun sendPhoneMediaEQ(eq: FloatArray, phone: Byte = 0x02.toByte(), media: Byte = 0x02.toByte()) { if (eq.size != 8) throw IllegalArgumentException("EQ must be 8 floats") - val header = byteArrayOf(0x04.toByte(), 0x00.toByte(), 0x04.toByte(), 0x00.toByte(), 0x53.toByte(), 0x00.toByte(), 0x84.toByte(), 0x00.toByte(), 0x02.toByte(), 0x02.toByte(), phone, media) + val header = byteArrayOf( + 0x04.toByte(), + 0x00.toByte(), + 0x04.toByte(), + 0x00.toByte(), + 0x53.toByte(), + 0x00.toByte(), + 0x84.toByte(), + 0x00.toByte(), + 0x02.toByte(), + 0x02.toByte(), + phone, + media + ) val buffer = ByteBuffer.allocate(128).order(ByteOrder.LITTLE_ENDIAN) for (block in 0..3) { for (i in 0..7) { @@ -592,4 +1060,60 @@ class AACPManager { val packet = header + payload sendPacket(packet) } -} \ No newline at end of file + + fun parseAudioSourceResponse(data: ByteArray): Pair { + Log.d(TAG, "Parsing Audio Source Response: ${data.joinToString(" ") { "%02X".format(it) }}") + if (data.size < 9) { + throw IllegalArgumentException("Data array too short to parse Audio Source Response") + } + if (data[4] != Opcodes.AUDIO_SOURCE) { + throw IllegalArgumentException("Data array does not start with AUDIO_SOURCE opcode") + } + val macBytes = data.sliceArray(6..11).reversedArray() + val mac = macBytes.joinToString(":") { "%02X".format(it) } + val typeByte = data[12] + val type = AudioSourceType.fromByte(typeByte) + ?: throw IllegalArgumentException("Unknown Audio Source Type: $typeByte") + return Pair(mac, type) + } + + fun parseConnectedDevicesResponse(data: ByteArray): List { + Log.d( + TAG, + "Parsing Connected Devices Response: ${data.joinToString(" ") { "%02X".format(it) }}" + ) + if (data.size < 8) { + throw IllegalArgumentException("Data array too short to parse Connected Devices Response") + } + if (data[4] != Opcodes.CONNECTED_DEVICES) { + throw IllegalArgumentException("Data array does not start with CONNECTED_DEVICES opcode") + } + val deviceCount = data[8].toInt() + val devices = mutableListOf() + + var offset = 9 + for (i in 0 until deviceCount) { + if (offset + 8 > data.size) { + throw IllegalArgumentException("Data array too short to parse all connected devices") + } + val macBytes = data.sliceArray(offset until offset + 6) + val mac = macBytes.joinToString(":") { "%02X".format(it) } + val info1 = data[offset + 6] + val info2 = data[offset + 7] + devices.add(ConnectedDevice(mac, info1, info2)) + offset += 8 + } + + return devices + } + + fun disconnected() { + Log.d(TAG, "Disconnected, clearing state") + controlCommandStatusList.clear() + controlCommandListeners.clear() + owns = false + oldConnectedDevices = listOf() + connectedDevices = listOf() + audioSource = null + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt index 978294ea..6010a26d 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt @@ -49,6 +49,7 @@ import android.view.animation.AnticipateOvershootInterpolator import android.view.animation.DecelerateInterpolator import android.view.animation.OvershootInterpolator import android.widget.FrameLayout +import android.widget.ImageButton import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.TextView @@ -70,6 +71,7 @@ enum class IslandType { CONNECTED, TAKING_OVER, MOVED_TO_REMOTE, + MOVED_TO_OTHER_DEVICE, } class IslandWindow(private val context: Context) { @@ -156,7 +158,7 @@ class IslandWindow(private val context: Context) { } @SuppressLint("SetTextI18s", "ClickableViewAccessibility", "UnspecifiedRegisterReceiverFlag") - fun show(name: String, batteryPercentage: Int, context: Context, type: IslandType = IslandType.CONNECTED) { + fun show(name: String, batteryPercentage: Int, context: Context, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false) { if (ServiceManager.getService()?.islandOpen == true) return else ServiceManager.getService()?.islandOpen = true @@ -197,6 +199,24 @@ class IslandWindow(private val context: Context) { batteryProgressBar.isIndeterminate = false islandView.findViewById(R.id.island_device_name).text = name + val actionButton = islandView.findViewById(R.id.island_action_button) + val batteryBg = islandView.findViewById(R.id.island_battery_bg) + if (type == IslandType.MOVED_TO_OTHER_DEVICE && !reversed) { + actionButton.visibility = View.VISIBLE + actionButton.setOnClickListener { + ServiceManager.getService()?.takeOver("reverse") + close() + } + islandView.findViewById(R.id.island_battery_text).visibility = View.GONE + islandView.findViewById(R.id.island_battery_progress).visibility = View.GONE + batteryBg.visibility = View.GONE + } else { + actionButton.visibility = View.GONE + islandView.findViewById(R.id.island_battery_text).visibility = View.VISIBLE + islandView.findViewById(R.id.island_battery_progress).visibility = View.VISIBLE + batteryBg.visibility = View.VISIBLE + } + val batteryIntentFilter = IntentFilter(AirPodsNotifications.BATTERY_DATA) batteryIntentFilter.addAction(AirPodsNotifications.DISCONNECT_RECEIVERS) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -331,6 +351,13 @@ class IslandWindow(private val context: Context) { IslandType.MOVED_TO_REMOTE -> { islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_moved_to_remote_text) } + IslandType.MOVED_TO_OTHER_DEVICE -> { + if (reversed) { + islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_moved_to_other_device_reversed_text) + } else { + islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_moved_to_other_device_text) + } + } } val videoView = islandView.findViewById(R.id.island_video_view) @@ -604,6 +631,10 @@ class IslandWindow(private val context: Context) { } fun close() { + if (Looper.myLooper() != Looper.getMainLooper()) { + Handler(Looper.getMainLooper()).post { close() } + return + } try { if (isClosing) return isClosing = true @@ -647,7 +678,15 @@ class IslandWindow(private val context: Context) { } private fun cleanupAndRemoveView() { - containerView.visibility = View.GONE + if (Looper.myLooper() != Looper.getMainLooper()) { + Handler(Looper.getMainLooper()).post { cleanupAndRemoveView() } + return + } + try { + containerView.visibility = View.GONE + } catch (e: Exception) { + e("IslandWindow", "Error setting visibility: $e") + } try { if (containerView.parent != null) { windowManager.removeView(containerView) @@ -662,6 +701,10 @@ class IslandWindow(private val context: Context) { } fun forceClose() { + if (Looper.myLooper() != Looper.getMainLooper()) { + Handler(Looper.getMainLooper()).post { forceClose() } + return + } try { if (isClosing) return isClosing = true diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt index c7193495..aa55c156 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt @@ -26,6 +26,7 @@ import android.media.AudioPlaybackConfiguration import android.os.Build import android.os.Handler import android.os.Looper +import android.os.SystemClock import android.util.Log import android.view.KeyEvent import androidx.annotation.RequiresApi @@ -41,7 +42,20 @@ object MediaController { private val handler = Handler(Looper.getMainLooper()) private lateinit var preferenceChangeListener: SharedPreferences.OnSharedPreferenceChangeListener - var pausedForCrossDevice = false + var pausedWhileTakingOver = false + var pausedForOtherDevice = false + + private var lastSelfActionAt: Long = 0L + private const val SELF_ACTION_IGNORE_MS = 800L + private const val PLAYBACK_DEBOUNCE_MS = 300L + private var lastPlaybackCallbackAt: Long = 0L + private var lastKnownIsMusicActive: Boolean? = null + + private const val PAUSED_FOR_OTHER_DEVICE_CLEAR_MS = 500L + private val clearPausedForOtherDeviceRunnable = Runnable { + pausedForOtherDevice = false + Log.d("MediaController", "Cleared pausedForOtherDevice after timeout, resuming normal playback monitoring") + } private var relativeVolume: Boolean = false private var conversationalAwarenessVolume: Int = 2 @@ -81,17 +95,65 @@ object MediaController { @RequiresApi(Build.VERSION_CODES.R) override fun onPlaybackConfigChanged(configs: MutableList?) { super.onPlaybackConfigChanged(configs) - Log.d("MediaController", "Playback config changed, iPausedTheMedia: $iPausedTheMedia") + val now = SystemClock.uptimeMillis() + val isActive = audioManager.isMusicActive + Log.d("MediaController", "Playback config changed, iPausedTheMedia: $iPausedTheMedia, isActive: $isActive, pausedForOtherDevice: $pausedForOtherDevice, lastKnownIsMusicActive: $lastKnownIsMusicActive") + + if (now - lastPlaybackCallbackAt < PLAYBACK_DEBOUNCE_MS) { + Log.d("MediaController", "Ignoring playback callback due to debounce (${now - lastPlaybackCallbackAt}ms)") + lastPlaybackCallbackAt = now + return + } + lastPlaybackCallbackAt = now + + if (now - lastSelfActionAt < SELF_ACTION_IGNORE_MS) { + Log.d("MediaController", "Ignoring playback callback because it's likely caused by our own action (${now - lastSelfActionAt}ms since last self-action)") + lastKnownIsMusicActive = isActive + return + } + + if (pausedForOtherDevice) { + handler.removeCallbacks(clearPausedForOtherDeviceRunnable) + handler.postDelayed(clearPausedForOtherDeviceRunnable, PAUSED_FOR_OTHER_DEVICE_CLEAR_MS) + + if (isActive) { + Log.d("MediaController", "Detected play while pausedForOtherDevice; attempting to take over") + pausedForOtherDevice = false + userPlayedTheMedia = true + if (!pausedWhileTakingOver) { + ServiceManager.getService()?.takeOver("music") + } + } else { + Log.d("MediaController", "Still not active while pausedForOtherDevice; will clear state after timeout") + } + + lastKnownIsMusicActive = isActive + return + } + if (configs != null && !iPausedTheMedia) { - Log.d("MediaController", "Seems like the user changed the state of media themselves, now I won't play until the ear detection pauses it.") + ServiceManager.getService()?.aacpManager?.sendMediaInformataion( + ServiceManager.getService()?.localMac ?: return, + isActive + ) + Log.d("MediaController", "User changed media state themselves; will wait for ear detection pause before auto-play") handler.postDelayed({ userPlayedTheMedia = audioManager.isMusicActive - }, 7) // i have no idea why android sends an event a hundred times after the user does something. + if (audioManager.isMusicActive) { + pausedForOtherDevice = false + } + }, 7) } - Log.d("MediaController", "pausedforcrossdevice: $pausedForCrossDevice") - if (!pausedForCrossDevice && audioManager.isMusicActive) { - ServiceManager.getService()?.takeOver("music") + + Log.d("MediaController", "pausedWhileTakingOver: $pausedWhileTakingOver") + if (!pausedWhileTakingOver && isActive) { + if (lastKnownIsMusicActive != true) { + Log.d("MediaController", "Music is active and not pausedWhileTakingOver; requesting takeOver") + ServiceManager.getService()?.takeOver("music") + } } + + lastKnownIsMusicActive = isActive } } @@ -126,6 +188,7 @@ object MediaController { KeyEvent.KEYCODE_MEDIA_PREVIOUS ) ) + lastSelfActionAt = SystemClock.uptimeMillis() } @Synchronized @@ -143,6 +206,7 @@ object MediaController { KeyEvent.KEYCODE_MEDIA_NEXT ) ) + lastSelfActionAt = SystemClock.uptimeMillis() } @Synchronized @@ -163,6 +227,7 @@ object MediaController { KeyEvent.KEYCODE_MEDIA_PAUSE ) ) + lastSelfActionAt = SystemClock.uptimeMillis() } } @@ -184,14 +249,15 @@ object MediaController { KeyEvent.KEYCODE_MEDIA_PLAY ) ) + lastSelfActionAt = SystemClock.uptimeMillis() } if (!audioManager.isMusicActive) { Log.d("MediaController", "Setting iPausedTheMedia to false") iPausedTheMedia = false } - if (pausedForCrossDevice) { - Log.d("MediaController", "Setting pausedForCrossDevice to false") - pausedForCrossDevice = false + if (pausedWhileTakingOver) { + Log.d("MediaController", "Setting pausedWhileTakingOver to false") + pausedWhileTakingOver = false } } @@ -245,4 +311,4 @@ object MediaController { } }) } -} +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_undo.xml b/android/app/src/main/res/drawable/ic_undo.xml new file mode 100644 index 00000000..a3f745b7 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_undo.xml @@ -0,0 +1,11 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_undo_button_bg.xml b/android/app/src/main/res/drawable/ic_undo_button_bg.xml new file mode 100644 index 00000000..c238ba1a --- /dev/null +++ b/android/app/src/main/res/drawable/ic_undo_button_bg.xml @@ -0,0 +1,5 @@ + + + + diff --git a/android/app/src/main/res/layout/island_window.xml b/android/app/src/main/res/layout/island_window.xml index fd8b8ef5..5cb1e143 100644 --- a/android/app/src/main/res/layout/island_window.xml +++ b/android/app/src/main/res/layout/island_window.xml @@ -12,7 +12,9 @@ android:orientation="horizontal" android:outlineAmbientShadowColor="#4EFFFFFF" android:outlineSpotShadowColor="#4EFFFFFF" - android:padding="8dp"> + android:padding="8dp" + android:clipToPadding="false" + android:clipChildren="false"> + android:gravity="center" + android:clipChildren="false"> + + \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 7f5fdb0c..3c3413dc 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -45,8 +45,10 @@ Control Noise Control Mode directly from your Home Screen. Connected Connected to Linux - Moved to phone + Connected Moved to Linux + Moved to other device + Reconnect from notification Head Tracking Nod to answer calls, and shake your head to decline. General From 0e9aadd672526b4fd64442bc03d60d28f3116585 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Wed, 10 Sep 2025 11:24:51 +0530 Subject: [PATCH 05/72] android: clean up a bit of AI gen'd code --- .../librepods/utils/AACPManager.kt | 8 +-- .../librepods/utils/IslandWindow.kt | 61 +++++++++++-------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 3eeebb14..0a43e931 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -34,6 +34,7 @@ class AACPManager { companion object { private const val TAG = "AACPManager" + @Suppress("unused") object Opcodes { const val SET_FEATURE_FLAGS: Byte = 0x4D const val REQUEST_NOTIFICATIONS: Byte = 0x0F @@ -187,7 +188,7 @@ class AACPManager { var oldConnectedDevices: List = listOf() private set - + var connectedDevices: List = listOf() private set @@ -735,7 +736,7 @@ class AACPManager { for (connectedDevice in connectedDevices) { if (connectedDevice.mac != selfMacAddress) { Log.d(TAG, "Sending Hijack Request packet to ${connectedDevice.mac}") - success = sendDataPacket(createHijackRequestPacket(connectedDevice.mac)) && success + success = sendDataPacket(createHijackRequestPacket(connectedDevice.mac)) || success } } return success @@ -872,7 +873,7 @@ class AACPManager { for (connectedDevice in connectedDevices) { if (connectedDevice.mac != selfMacAddress) { Log.d(TAG, "Sending Hijack Reversed packet to ${connectedDevice.mac}") - success = sendDataPacket(createHijackReversedPacket(connectedDevice.mac)) && success + success = sendDataPacket(createHijackReversedPacket(connectedDevice.mac)) || success } } return success @@ -905,7 +906,6 @@ class AACPManager { if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")) || targetMacAddress.length != 17 || !targetMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { throw IllegalArgumentException("MAC address must be 6 bytes") } - Log.d(TAG, "Sending Add TiPi Device packet to $targetMacAddress") return sendDataPacket(createAddTiPiDevicePacket(selfMacAddress, targetMacAddress)) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt index 6010a26d..769f04de 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt @@ -33,7 +33,6 @@ import android.content.IntentFilter import android.content.res.Resources import android.graphics.PixelFormat import android.graphics.drawable.GradientDrawable -import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper @@ -55,6 +54,7 @@ import android.widget.ProgressBar import android.widget.TextView import android.widget.VideoView import androidx.core.content.ContextCompat.getString +import androidx.core.net.toUri import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce @@ -109,7 +109,12 @@ class IslandWindow(private val context: Context) { private val batteryReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == AirPodsNotifications.BATTERY_DATA) { - val batteryList = intent.getParcelableArrayListExtra("data") + val batteryList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra("data", Battery::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableArrayListExtra("data") + } updateBatteryDisplay(batteryList) } else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { try { @@ -133,8 +138,8 @@ class IslandWindow(private val context: Context) { val leftLevel = leftBattery?.level ?: 0 val rightLevel = rightBattery?.level ?: 0 - val leftStatus = leftBattery?.status ?: BatteryStatus.DISCONNECTED - val rightStatus = rightBattery?.status ?: BatteryStatus.DISCONNECTED + leftBattery?.status ?: BatteryStatus.DISCONNECTED + rightBattery?.status ?: BatteryStatus.DISCONNECTED val batteryText = islandView.findViewById(R.id.island_battery_text) val batteryProgressBar = islandView.findViewById(R.id.island_battery_progress) @@ -157,7 +162,9 @@ class IslandWindow(private val context: Context) { } } - @SuppressLint("SetTextI18s", "ClickableViewAccessibility", "UnspecifiedRegisterReceiverFlag") + @SuppressLint("SetTextI18s", "ClickableViewAccessibility", "UnspecifiedRegisterReceiverFlag", + "SetTextI18n" + ) fun show(name: String, batteryPercentage: Int, context: Context, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false) { if (ServiceManager.getService()?.islandOpen == true) return else ServiceManager.getService()?.islandOpen = true @@ -175,10 +182,10 @@ class IslandWindow(private val context: Context) { val rightBattery = batteryList.find { it.component == BatteryComponent.RIGHT } when { - leftBattery?.level ?: 0 > 0 && rightBattery?.level ?: 0 > 0 -> + (leftBattery?.level ?: 0) > 0 && (rightBattery?.level ?: 0) > 0 -> minOf(leftBattery!!.level, rightBattery!!.level) - leftBattery?.level ?: 0 > 0 -> leftBattery!!.level - rightBattery?.level ?: 0 > 0 -> rightBattery!!.level + (leftBattery?.level ?: 0) > 0 -> leftBattery!!.level + (rightBattery?.level ?: 0) > 0 -> rightBattery!!.level batteryPercentage > 0 -> batteryPercentage else -> null } @@ -204,16 +211,18 @@ class IslandWindow(private val context: Context) { if (type == IslandType.MOVED_TO_OTHER_DEVICE && !reversed) { actionButton.visibility = View.VISIBLE actionButton.setOnClickListener { - ServiceManager.getService()?.takeOver("reverse") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + ServiceManager.getService()?.takeOver("reverse") + } close() } - islandView.findViewById(R.id.island_battery_text).visibility = View.GONE - islandView.findViewById(R.id.island_battery_progress).visibility = View.GONE + batteryText.visibility = View.GONE + batteryProgressBar.visibility = View.GONE batteryBg.visibility = View.GONE } else { actionButton.visibility = View.GONE - islandView.findViewById(R.id.island_battery_text).visibility = View.VISIBLE - islandView.findViewById(R.id.island_battery_progress).visibility = View.VISIBLE + batteryText.visibility = View.VISIBLE + batteryProgressBar.visibility = View.VISIBLE batteryBg.visibility = View.VISIBLE } @@ -300,7 +309,7 @@ class IslandWindow(private val context: Context) { if (isDraggingDown && deltaY > 0) { val stretchAmount = (deltaY * 0.5f).coerceAtMost(200f) - applyCustomStretchEffect(stretchAmount, deltaY) + applyCustomStretchEffect(stretchAmount) } } @@ -314,7 +323,7 @@ class IslandWindow(private val context: Context) { if (isBeingDragged) { val currentTranslationY = containerView.translationY - val significantVelocity = abs(yVelocity) > 800 + abs(yVelocity) > 800 val significantDrag = abs(dragDistance) > 80 when { @@ -361,7 +370,7 @@ class IslandWindow(private val context: Context) { } val videoView = islandView.findViewById(R.id.island_video_view) - val videoUri = Uri.parse("android.resource://me.kavishdevar.librepods/${R.raw.island}") + val videoUri = "android.resource://me.kavishdevar.librepods/${R.raw.island}".toUri() videoView.setVideoURI(videoUri) videoView.setOnPreparedListener { mediaPlayer -> mediaPlayer.isLooping = true @@ -409,13 +418,13 @@ class IslandWindow(private val context: Context) { } } - private fun applyCustomStretchEffect(stretchAmount: Float, dragY: Float) { + private fun applyCustomStretchEffect(stretchAmount: Float) { try { val mainLayout = islandView.findViewById(R.id.island_window_layout) - val connectedText = islandView.findViewById(R.id.island_connected_text) + islandView.findViewById(R.id.island_connected_text) val deviceText = islandView.findViewById(R.id.island_device_name) - val batteryView = islandView.findViewById(R.id.island_battery_container) - val videoView = islandView.findViewById(R.id.island_video_view) + islandView.findViewById(R.id.island_battery_container) + islandView.findViewById(R.id.island_video_view) val stretchFactor = 1f + (stretchAmount / 300f).coerceAtMost(4.0f) val newMinHeight = (initialHeight * stretchFactor).toInt() @@ -470,7 +479,7 @@ class IslandWindow(private val context: Context) { .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY) .setStiffness(dynamicStiffness) - resetStretchEffects(velocity) + resetStretchEffects() if (params != null) { params!!.height = WindowManager.LayoutParams.WRAP_CONTENT @@ -484,7 +493,7 @@ class IslandWindow(private val context: Context) { springAnimation.start() } - private fun resetStretchEffects(velocity: Float) { + private fun resetStretchEffects() { try { val mainLayout = islandView.findViewById(R.id.island_window_layout) val deviceText = islandView.findViewById(R.id.island_device_name) @@ -574,7 +583,7 @@ class IslandWindow(private val context: Context) { stretchAnimator.interpolator = OvershootInterpolator(0.5f) stretchAnimator.addUpdateListener { animation -> val progress = animation.animatedValue as Float - animateCustomStretch(progress, expandDuration) + animateCustomStretch(progress) } val normalizeAnimator = ValueAnimator.ofFloat(1.0f, 0.0f) @@ -601,7 +610,7 @@ class IslandWindow(private val context: Context) { normalizeAnimator.start() } - private fun animateCustomStretch(progress: Float, duration: Long) { + private fun animateCustomStretch(progress: Float) { try { val mainLayout = islandView.findViewById(R.id.island_window_layout) val connectedText = islandView.findViewById(R.id.island_connected_text) @@ -648,7 +657,7 @@ class IslandWindow(private val context: Context) { ServiceManager.getService()?.islandOpen = false autoCloseHandler?.removeCallbacks(autoCloseRunnable ?: return) - resetStretchEffects(0f) + resetStretchEffects() val videoView = islandView.findViewById(R.id.island_video_view) try { @@ -712,7 +721,7 @@ class IslandWindow(private val context: Context) { try { context.unregisterReceiver(batteryReceiver) } catch (e: Exception) { - // Silent catch - receiver might already be unregistered + e.printStackTrace() } ServiceManager.getService()?.islandOpen = false From aecbb066b5d7093a38f6ac80651a0940fe22341e Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Wed, 10 Sep 2025 11:30:22 +0530 Subject: [PATCH 06/72] android: clean up main service and remove minimum API on head gestures --- .../librepods/services/AirPodsService.kt | 240 ++++++++++-------- .../librepods/utils/GestureDetector.kt | 1 - .../librepods/utils/GestureFeedback.kt | 6 +- 3 files changed, 142 insertions(+), 105 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index f6ab0571..f2d3a9f5 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -17,6 +17,7 @@ */ @file:OptIn(ExperimentalEncodingApi::class) +@file:Suppress("DEPRECATION") package me.kavishdevar.librepods.services @@ -29,6 +30,7 @@ import android.app.PendingIntent import android.app.Service import android.appwidget.AppWidgetManager import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothHeadset import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothSocket @@ -41,6 +43,7 @@ import android.content.IntentFilter import android.content.SharedPreferences import android.content.pm.PackageManager import android.content.res.Resources +import android.graphics.Color import android.media.AudioManager import android.net.Uri import android.os.BatteryManager @@ -315,6 +318,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } + @SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag") override fun onCreate() { super.onCreate() @@ -337,7 +341,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ServiceManager.setService(this) startForegroundNotification() - initGestureDetector() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + initGestureDetector() + } else { + gestureDetector = null + config.headGestures = false + sharedPreferences.edit { putBoolean("head_gestures", false) } + Log.d("AirPodsService", "Head gestures disabled as device is running Android 9 or below") + } bleManager = BLEManager(this) bleManager.setAirPodsStatusListener(bleStatusListener) @@ -345,63 +356,111 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) with(sharedPreferences) { - val editor = edit() - - if (!contains("conversational_awareness_pause_music")) editor.putBoolean("conversational_awareness_pause_music", false) - if (!contains("personalized_volume")) editor.putBoolean("personalized_volume", false) - if (!contains("automatic_ear_detection")) editor.putBoolean("automatic_ear_detection", true) - if (!contains("long_press_nc")) editor.putBoolean("long_press_nc", true) - if (!contains("off_listening_mode")) editor.putBoolean("off_listening_mode", false) - if (!contains("show_phone_battery_in_widget")) editor.putBoolean("show_phone_battery_in_widget", true) - if (!contains("single_anc")) editor.putBoolean("single_anc", true) - if (!contains("long_press_transparency")) editor.putBoolean("long_press_transparency", true) - if (!contains("conversational_awareness")) editor.putBoolean("conversational_awareness", true) - if (!contains("relative_conversational_awareness_volume")) editor.putBoolean("relative_conversational_awareness_volume", true) - if (!contains("long_press_adaptive")) editor.putBoolean("long_press_adaptive", true) - if (!contains("loud_sound_reduction")) editor.putBoolean("loud_sound_reduction", true) - if (!contains("long_press_off")) editor.putBoolean("long_press_off", false) - if (!contains("volume_control")) editor.putBoolean("volume_control", true) - if (!contains("head_gestures")) editor.putBoolean("head_gestures", true) - if (!contains("disconnect_when_not_wearing")) editor.putBoolean("disconnect_when_not_wearing", false) + edit { + if (!contains("conversational_awareness_pause_music")) putBoolean( + "conversational_awareness_pause_music", + false + ) + if (!contains("personalized_volume")) putBoolean("personalized_volume", false) + if (!contains("automatic_ear_detection")) putBoolean( + "automatic_ear_detection", + true + ) + if (!contains("long_press_nc")) putBoolean("long_press_nc", true) + if (!contains("off_listening_mode")) putBoolean("off_listening_mode", false) + if (!contains("show_phone_battery_in_widget")) putBoolean( + "show_phone_battery_in_widget", + true + ) + if (!contains("single_anc")) putBoolean("single_anc", true) + if (!contains("long_press_transparency")) putBoolean( + "long_press_transparency", + true + ) + if (!contains("conversational_awareness")) putBoolean( + "conversational_awareness", + true + ) + if (!contains("relative_conversational_awareness_volume")) putBoolean( + "relative_conversational_awareness_volume", + true + ) + if (!contains("long_press_adaptive")) putBoolean("long_press_adaptive", true) + if (!contains("loud_sound_reduction")) putBoolean("loud_sound_reduction", true) + if (!contains("long_press_off")) putBoolean("long_press_off", false) + if (!contains("volume_control")) putBoolean("volume_control", true) + if (!contains("head_gestures")) putBoolean("head_gestures", true) + if (!contains("disconnect_when_not_wearing")) putBoolean( + "disconnect_when_not_wearing", + false + ) - // AirPods state-based takeover - if (!contains("takeover_when_disconnected")) editor.putBoolean("takeover_when_disconnected", true) - if (!contains("takeover_when_idle")) editor.putBoolean("takeover_when_idle", true) - if (!contains("takeover_when_music")) editor.putBoolean("takeover_when_music", false) - if (!contains("takeover_when_call")) editor.putBoolean("takeover_when_call", true) + // AirPods state-based takeover + if (!contains("takeover_when_disconnected")) putBoolean( + "takeover_when_disconnected", + true + ) + if (!contains("takeover_when_idle")) putBoolean("takeover_when_idle", true) + if (!contains("takeover_when_music")) putBoolean("takeover_when_music", false) + if (!contains("takeover_when_call")) putBoolean("takeover_when_call", true) + + // Phone state-based takeover + if (!contains("takeover_when_ringing_call")) putBoolean( + "takeover_when_ringing_call", + true + ) + if (!contains("takeover_when_media_start")) putBoolean( + "takeover_when_media_start", + true + ) - // Phone state-based takeover - if (!contains("takeover_when_ringing_call")) editor.putBoolean("takeover_when_ringing_call", true) - if (!contains("takeover_when_media_start")) editor.putBoolean("takeover_when_media_start", true) - - if (!contains("adaptive_strength")) editor.putInt("adaptive_strength", 51) - if (!contains("tone_volume")) editor.putInt("tone_volume", 75) - if (!contains("conversational_awareness_volume")) editor.putInt("conversational_awareness_volume", 43) - - if (!contains("textColor")) editor.putLong("textColor", -1L) - - if (!contains("qs_click_behavior")) editor.putString("qs_click_behavior", "cycle") - if (!contains("name")) editor.putString("name", "AirPods") - if (!contains("ble_only_mode")) editor.putBoolean("ble_only_mode", false) - - if (!contains("left_single_press_action")) editor.putString("left_single_press_action", - StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name) - if (!contains("right_single_press_action")) editor.putString("right_single_press_action", - StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name) - if (!contains("left_double_press_action")) editor.putString("left_double_press_action", - StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name) - if (!contains("right_double_press_action")) editor.putString("right_double_press_action", - StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name) - if (!contains("left_triple_press_action")) editor.putString("left_triple_press_action", - StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name) - if (!contains("right_triple_press_action")) editor.putString("right_triple_press_action", - StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name) - if (!contains("left_long_press_action")) editor.putString("left_long_press_action", - StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name) - if (!contains("right_long_press_action")) editor.putString("right_long_press_action", - StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name) - - editor.apply() + if (!contains("adaptive_strength")) putInt("adaptive_strength", 51) + if (!contains("tone_volume")) putInt("tone_volume", 75) + if (!contains("conversational_awareness_volume")) putInt( + "conversational_awareness_volume", + 43 + ) + + if (!contains("textColor")) putLong("textColor", -1L) + + if (!contains("qs_click_behavior")) putString("qs_click_behavior", "cycle") + if (!contains("name")) putString("name", "AirPods") + if (!contains("ble_only_mode")) putBoolean("ble_only_mode", false) + + if (!contains("left_single_press_action")) putString( + "left_single_press_action", + StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name + ) + if (!contains("right_single_press_action")) putString( + "right_single_press_action", + StemAction.defaultActions[StemPressType.SINGLE_PRESS]!!.name + ) + if (!contains("left_double_press_action")) putString( + "left_double_press_action", + StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name + ) + if (!contains("right_double_press_action")) putString( + "right_double_press_action", + StemAction.defaultActions[StemPressType.DOUBLE_PRESS]!!.name + ) + if (!contains("left_triple_press_action")) putString( + "left_triple_press_action", + StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name + ) + if (!contains("right_triple_press_action")) putString( + "right_triple_press_action", + StemAction.defaultActions[StemPressType.TRIPLE_PRESS]!!.name + ) + if (!contains("left_long_press_action")) putString( + "left_long_press_action", + StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name + ) + if (!contains("right_long_press_action")) putString( + "right_long_press_action", + StemAction.defaultActions[StemPressType.LONG_PRESS]!!.name + ) + + } } initializeConfig() @@ -453,6 +512,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { registerReceiver(ancModeReceiver, ancModeFilter, RECEIVER_EXPORTED) } else { + @Suppress("UnspecifiedRegisterReceiverFlag") registerReceiver(ancModeReceiver, ancModeFilter) } val audioManager = @@ -518,6 +578,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList RECEIVER_EXPORTED ) } else { + @Suppress("UnspecifiedRegisterReceiverFlag") registerReceiver(BatteryChangedIntentReceiver, batteryChangedIntentFilter) } } @@ -598,6 +659,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { registerReceiver(showIslandReceiver, showIslandIntentFilter, RECEIVER_EXPORTED) } else { + @Suppress("UnspecifiedRegisterReceiverFlag") registerReceiver(showIslandReceiver, showIslandIntentFilter) } @@ -610,6 +672,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED) registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED) } else { + @Suppress("UnspecifiedRegisterReceiverFlag") registerReceiver(connectionReceiver, deviceIntentFilter) registerReceiver(bluetoothReceiver, serviceIntentFilter) } @@ -623,6 +686,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList bluetoothAdapter.getProfileProxy( this, object : BluetoothProfile.ServiceListener { + @SuppressLint("NewApi") override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { if (profile == BluetoothProfile.A2DP) { val connectedDevices = proxy.connectedDevices @@ -668,6 +732,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } + @Suppress("unused") fun cameraOpened() { Log.d("AirPodsService", "Camera opened, gonna handle stem presses and take action if enabled") val isCameraShutterUsed = listOf( @@ -904,7 +969,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList Log.d("AirPodsParser", "Connected device: ${device.mac}, info1: ${device.info1}, info2: ${device.info2})") } val newDevices = connectedDevices.filter { newDevice -> - val notInOld = aacpManager.oldConnectedDevices?.none { oldDevice -> oldDevice.mac == newDevice.mac } ?: true + val notInOld = aacpManager.oldConnectedDevices.none { oldDevice -> oldDevice.mac == newDevice.mac } val notLocal = newDevice.mac != localMac notInOld && notLocal } @@ -958,8 +1023,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } private fun processEarDetectionChange(earDetection: ByteArray) { - var inEar = false - var inEarData = listOf(earDetectionNotification.status[0] == 0x00.toByte(), earDetectionNotification.status[1] == 0x00.toByte()) + var inEar: Boolean + val inEarData = listOf(earDetectionNotification.status[0] == 0x00.toByte(), earDetectionNotification.status[1] == 0x00.toByte()) var justEnabledA2dp = false earDetectionNotification.setStatus(earDetection) if (config.earDetectionEnabled) { @@ -1008,10 +1073,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList Log.d("AirPodsParser", "inEarData: ${inEarData.sorted()}, newInEarData: ${newInEarData.sorted()}") if (newInEarData.sorted() != inEarData.sorted()) { - inEarData = newInEarData - if (inEar == true) { + if (inEar) { if (!justEnabledA2dp) { - justEnabledA2dp = false MediaController.sendPlay() MediaController.iPausedTheMedia = false } @@ -1178,7 +1241,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } - private fun logPacket(packet: ByteArray, source: String) { + private fun logPacket(packet: ByteArray, @Suppress("SameParameterValue") source: String) { val packetHex = packet.joinToString(" ") { "%02X".format(it) } val logEntry = "$source: $packetHex" @@ -1207,10 +1270,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } - fun getPacketLogs(): Set { - return inMemoryLogs.toSet() - } - private fun clearPacketLogs() { synchronized(inMemoryLogs) { inMemoryLogs.clear() @@ -1232,7 +1291,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList private var isInCall = false private var callNumber: String? = null - @RequiresApi(Build.VERSION_CODES.Q) private fun initGestureDetector() { if (gestureDetector == null) { gestureDetector = GestureDetector(this) @@ -1296,11 +1354,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } - fun setPhoneBatteryInWidget(enabled: Boolean) { - widgetMobileBatteryEnabled = enabled - updateBattery() - } - @OptIn(ExperimentalMaterial3Api::class) fun startForegroundNotification() { val disconnectedNotificationChannel = NotificationChannel( @@ -1322,7 +1375,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ).apply { description = "Notifications about problems connecting to AirPods protocol" enableLights(true) - lightColor = android.graphics.Color.RED + lightColor = Color.RED enableVibration(true) } @@ -1609,7 +1662,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList batteryList: List? = null ) { val notificationManager = getSystemService(NotificationManager::class.java) - var updatedNotification: Notification? = null + var updatedNotification: Notification? val notificationIntent = Intent(this, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity( @@ -1695,7 +1748,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } - @RequiresApi(Build.VERSION_CODES.Q) fun handleIncomingCall() { if (isInCall) return if (config.headGestures) { @@ -1712,9 +1764,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } } - @OptIn(ExperimentalCoroutinesApi::class) - @RequiresApi(Build.VERSION_CODES.Q) + @OptIn(ExperimentalCoroutinesApi::class) suspend fun testHeadGestures(): Boolean { return suspendCancellableCoroutine { continuation -> gestureDetector?.startDetection(doNotStop = true) { accepted -> @@ -1801,7 +1852,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList .appendPath(applicationContext.resources.getResourceTypeName(resId)) .appendPath(applicationContext.resources.getResourceEntryName(resId)) .build() - } catch (e: Resources.NotFoundException) { + } catch (_: Resources.NotFoundException) { null } } @@ -1821,7 +1872,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList @Suppress("PrivatePropertyName") private val ACTION_ASI_UPDATE_BLUETOOTH_DATA = "batterywidget.impl.action.update_bluetooth_data" - @Suppress("MissingPermission") + @Suppress("MissingPermission", "unused") fun broadcastBatteryInformation() { if (device == null) return @@ -1848,13 +1899,13 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ) // Broadcast vendor-specific event - val intent = Intent(android.bluetooth.BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT).apply { - putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV) - putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, android.bluetooth.BluetoothHeadset.AT_CMD_TYPE_SET) - putExtra(android.bluetooth.BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments) + val intent = Intent(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT).apply { + putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD, VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV) + putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, BluetoothHeadset.AT_CMD_TYPE_SET) + putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments) putExtra(BluetoothDevice.EXTRA_DEVICE, device) putExtra(BluetoothDevice.EXTRA_NAME, device?.name) - addCategory("${android.bluetooth.BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.$APPLE") + addCategory("${BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.$APPLE") } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -2211,13 +2262,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList throw lastException ?: IllegalStateException(errorMessage) } - @RequiresApi(Build.VERSION_CODES.R) @SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag") fun connectToSocket(device: BluetoothDevice) { Log.d("AirPodsService", " Connecting to socket") HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a") - if (isConnectedLocally != true && !CrossDevice.isAvailable) { + if (!isConnectedLocally && !CrossDevice.isAvailable) { socket = try { createBluetoothSocket(device, uuid) } catch (e: Exception) { @@ -2284,11 +2334,11 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList setupStemActions() - while (socket.isConnected == true) { + while (socket.isConnected) { socket.let { val buffer = ByteArray(1024) val bytesRead = it.inputStream.read(buffer) - var data: ByteArray = byteArrayOf() + var data: ByteArray if (bytesRead > 0) { data = buffer.copyOfRange(0, bytesRead) sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DATA).apply { @@ -2367,6 +2417,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList val conversationAwarenessNotification = AirPodsNotifications.ConversationalAwarenessNotification() + @Suppress("unused") fun setEarDetection(enabled: Boolean) { if (config.earDetectionEnabled != enabled) { config.earDetectionEnabled = enabled @@ -2563,17 +2614,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList isHeadTrackingActive = false } - fun shouldTakeOverBasedOnAirPodsState(connectionState: String): Boolean { - if (CrossDevice.isAvailable) return true - - return when (connectionState) { - "Disconnected" -> config.takeoverWhenDisconnected - "Idle" -> config.takeoverWhenIdle - "Music" -> config.takeoverWhenMusic - "Call", "Ringing", "Hanging Up" -> config.takeoverWhenCall - else -> false - } - } } private fun Int.dpToPx(): Int { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt index 03e975a7..b0f2e245 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt @@ -21,7 +21,6 @@ import kotlin.math.max import kotlin.math.min import kotlin.math.pow -@RequiresApi(Build.VERSION_CODES.Q) class GestureDetector( private val airPodsService: AirPodsService ) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt index 711bcbec..6d2d0b2e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt @@ -12,8 +12,7 @@ import androidx.annotation.RequiresApi import me.kavishdevar.librepods.R import java.util.concurrent.atomic.AtomicBoolean -@RequiresApi(Build.VERSION_CODES.Q) -class GestureFeedback(private val context: Context) { +class GestureFeedback(context: Context) { private val TAG = "GestureFeedback" @@ -25,8 +24,7 @@ class GestureFeedback(private val context: Context) { AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setFlags(AudioAttributes.FLAG_LOW_LATENCY or - AudioAttributes.FLAG_AUDIBILITY_ENFORCED) + .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) .build() ) .build() From fa00620b5b411962bf39197365e96820de718ef4 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Wed, 10 Sep 2025 12:38:27 +0530 Subject: [PATCH 07/72] android: clean up a lot of stuff --- android/app/src/main/AndroidManifest.xml | 4 +- .../librepods/CustomDeviceActivity.kt | 118 ++++++++---------- .../me/kavishdevar/librepods/MainActivity.kt | 27 ++-- .../librepods/QuickSettingsDialogActivity.kt | 2 +- .../composables/AccessibilitySettings.kt | 2 +- .../composables/AccessibilitySlider.kt | 4 +- .../librepods/composables/BatteryView.kt | 2 +- .../composables/ControlCenterButton.kt | 4 +- .../composables/IndependentToggle.kt | 5 +- .../librepods/constants/Packets.kt | 2 +- .../librepods/constants/StemAction.kt | 1 - .../screens/AccessibilitySettingsScreen.kt | 3 +- .../librepods/screens/AppSettingsScreen.kt | 60 ++++----- .../librepods/screens/DebugScreen.kt | 9 +- .../screens/EqualizerSettingsScreen.kt | 8 +- .../librepods/screens/Onboarding.kt | 6 +- .../screens/PressAndHoldSettingsScreen.kt | 18 +-- .../librepods/screens/RenameScreen.kt | 5 +- .../screens/TroubleshootingScreen.kt | 13 +- .../librepods/services/AirPodsService.kt | 27 ++-- .../librepods/utils/AACPManager.kt | 10 +- .../kavishdevar/librepods/utils/BLEManager.kt | 16 +-- .../librepods/utils/BluetoothCryptography.kt | 10 +- .../librepods/utils/CrossDevice.kt | 15 +-- .../librepods/utils/IslandWindow.kt | 2 +- .../librepods/utils/KotlinModule.kt | 16 +-- .../librepods/utils/LogCollector.kt | 56 ++++----- .../librepods/utils/MediaController.kt | 4 +- .../librepods/utils/PopupWindow.kt | 11 +- .../res/drawable/app_widget_background.xml | 10 ++ .../app_widget_inner_view_background.xml | 9 ++ .../app/src/main/res/layout/island_window.xml | 4 +- .../main/res/layout/noise_control_widget.xml | 10 +- .../main/res/mipmap-anydpi/ic_launcher.xml | 6 + .../res/mipmap-anydpi/ic_launcher_round.xml | 6 + android/app/src/main/res/raw/blip_yes.wav | Bin 33444 -> 0 bytes .../app/src/main/res/values-v21/styles.xml | 14 --- android/app/src/main/res/values/strings.xml | 3 +- android/app/src/main/res/values/styles.xml | 10 +- 39 files changed, 269 insertions(+), 263 deletions(-) create mode 100644 android/app/src/main/res/drawable/app_widget_background.xml create mode 100644 android/app/src/main/res/drawable/app_widget_inner_view_background.xml create mode 100644 android/app/src/main/res/mipmap-anydpi/ic_launcher.xml create mode 100644 android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml delete mode 100644 android/app/src/main/res/values-v21/styles.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e7f26a17..c9604273 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -35,8 +35,6 @@ - - - --> diff --git a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt index 46b6415a..27e4679e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt @@ -1,24 +1,23 @@ /* * LibrePods - AirPods liberated from Apple’s ecosystem - * + * * Copyright (C) 2025 LibrePods contributors - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package me.kavishdevar.librepods -import android.Manifest import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager @@ -29,26 +28,12 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.annotation.RequiresPermission -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen -import me.kavishdevar.librepods.screens.EqualizerSettingsScreen -import me.kavishdevar.librepods.ui.theme.LibrePodsTheme -import org.lsposed.hiddenapibypass.HiddenApiBypass import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -56,36 +41,40 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout +import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen +import me.kavishdevar.librepods.screens.EqualizerSettingsScreen +import me.kavishdevar.librepods.ui.theme.LibrePodsTheme +import org.lsposed.hiddenapibypass.HiddenApiBypass import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder +@Suppress("PrivatePropertyName") class CustomDevice : ComponentActivity() { private val TAG = "AirPodsAccessibilitySettings" private var socket: BluetoothSocket? = null private val deviceAddress = "28:2D:7F:C2:05:5B" - private val psm = 31 private val uuid: ParcelUuid = ParcelUuid.fromString("00000000-0000-0000-0000-00000000000") // Data states private val isConnected = mutableStateOf(false) - private val leftAmplification = mutableStateOf(1.0f) - private val leftTone = mutableStateOf(1.0f) - private val leftAmbientNoiseReduction = mutableStateOf(0.5f) + private val leftAmplification = mutableFloatStateOf(1.0f) + private val leftTone = mutableFloatStateOf(1.0f) + private val leftAmbientNoiseReduction = mutableFloatStateOf(0.5f) private val leftConversationBoost = mutableStateOf(false) private val leftEQ = mutableStateOf(FloatArray(8) { 50.0f }) - private val rightAmplification = mutableStateOf(1.0f) - private val rightTone = mutableStateOf(1.0f) - private val rightAmbientNoiseReduction = mutableStateOf(0.5f) + private val rightAmplification = mutableFloatStateOf(1.0f) + private val rightTone = mutableFloatStateOf(1.0f) + private val rightAmbientNoiseReduction = mutableFloatStateOf(0.5f) private val rightConversationBoost = mutableStateOf(false) private val rightEQ = mutableStateOf(FloatArray(8) { 50.0f }) private val singleMode = mutableStateOf(false) - private val amplification = mutableStateOf(1.0f) - private val balance = mutableStateOf(0.5f) + private val amplification = mutableFloatStateOf(1.0f) + private val balance = mutableFloatStateOf(0.5f) - private val retryCount = mutableStateOf(0) + private val retryCount = mutableIntStateOf(0) private val showRetryButton = mutableStateOf(false) private val maxRetries = 3 @@ -146,18 +135,19 @@ class CustomDevice : ComponentActivity() { socket?.close() } + @SuppressLint("MissingPermission") private suspend fun connectL2CAP() { - retryCount.value = 0 + retryCount.intValue = 0 // Close any existing socket socket?.close() socket = null - while (retryCount.value < maxRetries) { + while (retryCount.intValue < maxRetries) { try { - Log.d(TAG, "Starting L2CAP connection setup, attempt ${retryCount.value + 1}") + Log.d(TAG, "Starting L2CAP connection setup, attempt ${retryCount.intValue + 1}") HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager val device: BluetoothDevice = manager.adapter.getRemoteDevice(deviceAddress) - socket = createBluetoothSocket(device, psm) + socket = createBluetoothSocket(device) withTimeout(5000L) { socket?.connect() @@ -177,9 +167,9 @@ class CustomDevice : ComponentActivity() { return } catch (e: Exception) { - Log.e(TAG, "Failed to connect, attempt ${retryCount.value + 1}: ${e.message}") - retryCount.value++ - if (retryCount.value < maxRetries) { + Log.e(TAG, "Failed to connect, attempt ${retryCount.intValue + 1}: ${e.message}") + retryCount.intValue++ + if (retryCount.intValue < maxRetries) { delay(2000) // Wait 2 seconds before retry } } @@ -193,7 +183,7 @@ class CustomDevice : ComponentActivity() { } } - private fun createBluetoothSocket(device: BluetoothDevice, psm: Int): BluetoothSocket { + private fun createBluetoothSocket(device: BluetoothDevice): BluetoothSocket { val type = 3 // L2CAP val constructorSpecs = listOf( arrayOf(device, type, true, true, 31, uuid), @@ -300,18 +290,18 @@ class CustomDevice : ComponentActivity() { leftEQ.value = newLeftEQ if (singleMode.value) rightEQ.value = newLeftEQ - leftAmplification.value = buffer.float - Log.d(TAG, "Parsed left amplification: ${leftAmplification.value}") - leftTone.value = buffer.float - Log.d(TAG, "Parsed left tone: ${leftTone.value}") - if (singleMode.value) rightTone.value = leftTone.value + leftAmplification.floatValue = buffer.float + Log.d(TAG, "Parsed left amplification: ${leftAmplification.floatValue}") + leftTone.floatValue = buffer.float + Log.d(TAG, "Parsed left tone: ${leftTone.floatValue}") + if (singleMode.value) rightTone.floatValue = leftTone.floatValue val leftConvFloat = buffer.float leftConversationBoost.value = leftConvFloat > 0.5f Log.d(TAG, "Parsed left conversation boost: $leftConvFloat (${leftConversationBoost.value})") if (singleMode.value) rightConversationBoost.value = leftConversationBoost.value - leftAmbientNoiseReduction.value = buffer.float - Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.value}") - if (singleMode.value) rightAmbientNoiseReduction.value = leftAmbientNoiseReduction.value + leftAmbientNoiseReduction.floatValue = buffer.float + Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.floatValue}") + if (singleMode.value) rightAmbientNoiseReduction.floatValue = leftAmbientNoiseReduction.floatValue // Right bud val newRightEQ = rightEQ.value.copyOf() @@ -321,24 +311,24 @@ class CustomDevice : ComponentActivity() { } rightEQ.value = newRightEQ - rightAmplification.value = buffer.float - Log.d(TAG, "Parsed right amplification: ${rightAmplification.value}") - rightTone.value = buffer.float - Log.d(TAG, "Parsed right tone: ${rightTone.value}") + rightAmplification.floatValue = buffer.float + Log.d(TAG, "Parsed right amplification: ${rightAmplification.floatValue}") + rightTone.floatValue = buffer.float + Log.d(TAG, "Parsed right tone: ${rightTone.floatValue}") val rightConvFloat = buffer.float rightConversationBoost.value = rightConvFloat > 0.5f Log.d(TAG, "Parsed right conversation boost: $rightConvFloat (${rightConversationBoost.value})") - rightAmbientNoiseReduction.value = buffer.float - Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.value}") + rightAmbientNoiseReduction.floatValue = buffer.float + Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.floatValue}") Log.d(TAG, "Settings parsed successfully") // Update single mode values if in single mode if (singleMode.value) { - val avg = (leftAmplification.value + rightAmplification.value) / 2 - amplification.value = avg.coerceIn(0f, 1f) - val diff = rightAmplification.value - leftAmplification.value - balance.value = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) + val avg = (leftAmplification.floatValue + rightAmplification.floatValue) / 2 + amplification.floatValue = avg.coerceIn(0f, 1f) + val diff = rightAmplification.floatValue - leftAmplification.floatValue + balance.floatValue = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) } } @@ -363,19 +353,19 @@ class CustomDevice : ComponentActivity() { for (eq in leftEQ.value) { buffer.putFloat(eq) } - buffer.putFloat(leftAmplification.value) - buffer.putFloat(leftTone.value) + buffer.putFloat(leftAmplification.floatValue) + buffer.putFloat(leftTone.floatValue) buffer.putFloat(if (leftConversationBoost.value) 1.0f else 0.0f) - buffer.putFloat(leftAmbientNoiseReduction.value) + buffer.putFloat(leftAmbientNoiseReduction.floatValue) // Right bud for (eq in rightEQ.value) { buffer.putFloat(eq) } - buffer.putFloat(rightAmplification.value) - buffer.putFloat(rightTone.value) + buffer.putFloat(rightAmplification.floatValue) + buffer.putFloat(rightTone.floatValue) buffer.putFloat(if (rightConversationBoost.value) 1.0f else 0.0f) - buffer.putFloat(rightAmbientNoiseReduction.value) + buffer.putFloat(rightAmbientNoiseReduction.floatValue) val packet = buffer.array() Log.d(TAG, "Packet length: ${packet.size}") @@ -393,4 +383,4 @@ class CustomDevice : ComponentActivity() { } } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 1d358135..9dba1467 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -97,6 +97,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.edit +import androidx.core.net.toUri import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -104,6 +106,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.MultiplePermissionsState import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberMultiplePermissionsState +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.constants.AirPodsNotifications import me.kavishdevar.librepods.screens.AirPodsSettingsScreen import me.kavishdevar.librepods.screens.AppSettingsScreen @@ -123,6 +126,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi lateinit var serviceConnection: ServiceConnection lateinit var connectionStatusReceiver: BroadcastReceiver +@ExperimentalHazeMaterialsApi @ExperimentalMaterial3Api class MainActivity : ComponentActivity() { companion object { @@ -137,8 +141,10 @@ class MainActivity : ComponentActivity() { setContent { LibrePodsTheme { - getSharedPreferences("settings", MODE_PRIVATE).edit().putLong("textColor", - MaterialTheme.colorScheme.onSurface.toArgb().toLong()).apply() + getSharedPreferences("settings", MODE_PRIVATE).edit { + putLong( + "textColor", + MaterialTheme.colorScheme.onSurface.toArgb().toLong())} Main() } } @@ -207,8 +213,7 @@ class MainActivity : ComponentActivity() { } private fun handleAddMagicKeys(uri: Uri) { - val context = this - val sharedPreferences = getSharedPreferences("settings", Context.MODE_PRIVATE) + val sharedPreferences = getSharedPreferences("settings", MODE_PRIVATE) val irkHex = uri.getQueryParameter("irk") val encKeyHex = uri.getQueryParameter("enc_key") @@ -217,13 +222,13 @@ class MainActivity : ComponentActivity() { if (irkHex != null && validateHexInput(irkHex)) { val irkBytes = hexStringToByteArray(irkHex) val irkBase64 = Base64.encode(irkBytes) - sharedPreferences.edit().putString("IRK", irkBase64).apply() + sharedPreferences.edit {putString("IRK", irkBase64)} } if (encKeyHex != null && validateHexInput(encKeyHex)) { val encKeyBytes = hexStringToByteArray(encKeyHex) val encKeyBase64 = Base64.encode(encKeyBytes) - sharedPreferences.edit().putString("ENC_KEY", encKeyBase64).apply() + sharedPreferences.edit { putString("ENC_KEY", encKeyBase64)} } Toast.makeText(this, "Magic keys added successfully!", Toast.LENGTH_SHORT).show() @@ -247,6 +252,7 @@ class MainActivity : ComponentActivity() { } } +@ExperimentalHazeMaterialsApi @SuppressLint("MissingPermission", "InlinedApi", "UnspecifiedRegisterReceiverFlag") @OptIn(ExperimentalPermissionsApi::class) @Composable @@ -404,6 +410,7 @@ fun Main() { } } +@ExperimentalHazeMaterialsApi @OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class) @Composable fun PermissionsScreen( @@ -586,7 +593,7 @@ fun PermissionsScreen( onClick = { val intent = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:${context.packageName}") + "package:${context.packageName}".toUri() ) context.startActivity(intent) onOverlaySettingsReturn() @@ -616,9 +623,9 @@ fun PermissionsScreen( Button( onClick = { - val editor = context.getSharedPreferences("settings", MODE_PRIVATE).edit() - editor.putBoolean("overlay_permission_skipped", true) - editor.apply() + context.getSharedPreferences("settings", MODE_PRIVATE).edit { + putBoolean("overlay_permission_skipped", true) + } val intent = Intent(context, MainActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt index d8a79d2a..b30c3ed4 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt @@ -133,7 +133,7 @@ class QuickSettingsDialogActivity : ComponentActivity() { window.setGravity(Gravity.BOTTOM) Intent(this, AirPodsService::class.java).also { intent -> - bindService(intent, connection, Context.BIND_AUTO_CREATE) + bindService(intent, connection, BIND_AUTO_CREATE) } setContent { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt index 75cbd2a6..42c942ba 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt @@ -134,7 +134,7 @@ fun AccessibilitySettings() { textColor = textColor ) - val volumeSwipeSpeedOptions = mapOf( + val volumeSwipeSpeedOptions = mapOf( 1.toByte() to "Default", 2.toByte() to "Longer", 3.toByte() to "Longest" diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt index d111c0a3..abd8d146 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt @@ -23,10 +23,8 @@ 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.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 @@ -135,4 +133,4 @@ fun AccessibilitySliderPreview() { onValueChange = {}, valueRange = 0f..2f ) -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt index c4740d7c..4f906628 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt @@ -99,7 +99,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { batteryStatus.value = service.getBattery() if (preview) { - batteryStatus.value = listOf( + batteryStatus.value = listOf( Battery(BatteryComponent.LEFT, 100, BatteryStatus.CHARGING), Battery(BatteryComponent.RIGHT, 50, BatteryStatus.NOT_CHARGING), Battery(BatteryComponent.CASE, 5, BatteryStatus.CHARGING) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt index 22626829..6de28766 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ControlCenterButton.kt @@ -15,7 +15,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - + +@file:Suppress("unused") + package me.kavishdevar.librepods.composables import androidx.compose.animation.animateColorAsState diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt index f3e320b8..2cb6e460 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt @@ -50,6 +50,7 @@ import androidx.compose.ui.unit.sp import me.kavishdevar.librepods.services.AirPodsService import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi +import androidx.core.content.edit @Composable fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null) { @@ -70,7 +71,7 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam fun cb() { if (controlCommandIdentifier == null) { - sharedPreferences.edit().putBoolean(snakeCasedName, checked).apply() + sharedPreferences.edit { putBoolean(snakeCasedName, checked) } } if (functionName != null && service != null) { val method = @@ -127,4 +128,4 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam @Composable fun IndependentTogglePreview() { IndependentToggle("Test", AirPodsService(), "test", LocalContext.current.getSharedPreferences("preview", 0), true) -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt b/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt index 6c8d661a..91f79f47 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt @@ -244,7 +244,7 @@ fun isHeadTrackingData(data: ByteArray): Boolean { ) for (i in prefixPattern.indices) { - if (data[i] != prefixPattern[i].toByte()) return false + if (data[i] != prefixPattern[i]) return false } if (data[10] != 0x44.toByte() && data[10] != 0x45.toByte()) return false diff --git a/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt b/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt index 3c5be496..fabe01a9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/constants/StemAction.kt @@ -18,7 +18,6 @@ package me.kavishdevar.librepods.constants -import me.kavishdevar.librepods.constants.StemAction.entries import me.kavishdevar.librepods.utils.AACPManager enum class StemAction { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 824098c4..f6d1fc21 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -18,7 +18,6 @@ package me.kavishdevar.librepods.screens -import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -469,4 +468,4 @@ fun AccessibilitySettingsScreen( Spacer(modifier = Modifier.height(16.dp)) } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index 308c2806..d49021ea 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -57,8 +57,6 @@ import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -86,6 +84,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.edit import androidx.navigation.NavController import dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState @@ -186,11 +185,11 @@ fun AppSettingsScreen(navController: NavController) { var bleOnlyMode by remember { mutableStateOf(sharedPreferences.getBoolean("ble_only_mode", false)) } - + // Ensure the default value is properly set if not exists LaunchedEffect(Unit) { if (!sharedPreferences.contains("ble_only_mode")) { - sharedPreferences.edit().putBoolean("ble_only_mode", false).apply() + sharedPreferences.edit { putBoolean("ble_only_mode", false) } } } @@ -312,7 +311,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { showPhoneBatteryInWidget = !showPhoneBatteryInWidget - sharedPreferences.edit().putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget).apply() + sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -340,7 +339,7 @@ fun AppSettingsScreen(navController: NavController) { checked = showPhoneBatteryInWidget, onCheckedChange = { showPhoneBatteryInWidget = it - sharedPreferences.edit().putBoolean("show_phone_battery_in_widget", it).apply() + sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", it)} } ) } @@ -376,7 +375,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { bleOnlyMode = !bleOnlyMode - sharedPreferences.edit().putBoolean("ble_only_mode", bleOnlyMode).apply() + sharedPreferences.edit { putBoolean("ble_only_mode", bleOnlyMode)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -403,7 +402,7 @@ fun AppSettingsScreen(navController: NavController) { checked = bleOnlyMode, onCheckedChange = { bleOnlyMode = it - sharedPreferences.edit().putBoolean("ble_only_mode", it).apply() + sharedPreferences.edit { putBoolean("ble_only_mode", it)} } ) } @@ -440,12 +439,12 @@ fun AppSettingsScreen(navController: NavController) { fun updateConversationalAwarenessPauseMusic(enabled: Boolean) { conversationalAwarenessPauseMusicEnabled = enabled - sharedPreferences.edit().putBoolean("conversational_awareness_pause_music", enabled).apply() + sharedPreferences.edit { putBoolean("conversational_awareness_pause_music", enabled)} } fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) { relativeConversationalAwarenessVolumeEnabled = enabled - sharedPreferences.edit().putBoolean("relative_conversational_awareness_volume", enabled).apply() + sharedPreferences.edit { putBoolean("relative_conversational_awareness_volume", enabled)} } Row( @@ -541,7 +540,7 @@ fun AppSettingsScreen(navController: NavController) { value = sliderValue.floatValue, onValueChange = { sliderValue.floatValue = it - sharedPreferences.edit().putInt("conversational_awareness_volume", it.toInt()).apply() + sharedPreferences.edit { putInt("conversational_awareness_volume", it.toInt())} }, valueRange = 10f..85f, onValueChangeFinished = { @@ -639,7 +638,7 @@ fun AppSettingsScreen(navController: NavController) { ) { fun updateQsClickBehavior(enabled: Boolean) { openDialogForControlling = enabled - sharedPreferences.edit().putString("qs_click_behavior", if (enabled) "dialog" else "cycle").apply() + sharedPreferences.edit { putString("qs_click_behavior", if (enabled) "dialog" else "cycle")} } Row( @@ -708,7 +707,7 @@ fun AppSettingsScreen(navController: NavController) { ) { fun updateDisconnectWhenNotWearing(enabled: Boolean) { disconnectWhenNotWearing = enabled - sharedPreferences.edit().putBoolean("disconnect_when_not_wearing", enabled).apply() + sharedPreferences.edit { putBoolean("disconnect_when_not_wearing", enabled)} } Row( @@ -789,7 +788,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { takeoverWhenDisconnected = !takeoverWhenDisconnected - sharedPreferences.edit().putBoolean("takeover_when_disconnected", takeoverWhenDisconnected).apply() + sharedPreferences.edit { putBoolean("takeover_when_disconnected", takeoverWhenDisconnected)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -817,7 +816,7 @@ fun AppSettingsScreen(navController: NavController) { checked = takeoverWhenDisconnected, onCheckedChange = { takeoverWhenDisconnected = it - sharedPreferences.edit().putBoolean("takeover_when_disconnected", it).apply() + sharedPreferences.edit { putBoolean("takeover_when_disconnected", it)} } ) } @@ -830,7 +829,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { takeoverWhenIdle = !takeoverWhenIdle - sharedPreferences.edit().putBoolean("takeover_when_idle", takeoverWhenIdle).apply() + sharedPreferences.edit { putBoolean("takeover_when_idle", takeoverWhenIdle)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -858,7 +857,7 @@ fun AppSettingsScreen(navController: NavController) { checked = takeoverWhenIdle, onCheckedChange = { takeoverWhenIdle = it - sharedPreferences.edit().putBoolean("takeover_when_idle", it).apply() + sharedPreferences.edit { putBoolean("takeover_when_idle", it)} } ) } @@ -871,7 +870,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { takeoverWhenMusic = !takeoverWhenMusic - sharedPreferences.edit().putBoolean("takeover_when_music", takeoverWhenMusic).apply() + sharedPreferences.edit { putBoolean("takeover_when_music", takeoverWhenMusic)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -899,7 +898,7 @@ fun AppSettingsScreen(navController: NavController) { checked = takeoverWhenMusic, onCheckedChange = { takeoverWhenMusic = it - sharedPreferences.edit().putBoolean("takeover_when_music", it).apply() + sharedPreferences.edit { putBoolean("takeover_when_music", it)} } ) } @@ -912,7 +911,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { takeoverWhenCall = !takeoverWhenCall - sharedPreferences.edit().putBoolean("takeover_when_call", takeoverWhenCall).apply() + sharedPreferences.edit { putBoolean("takeover_when_call", takeoverWhenCall)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -940,7 +939,7 @@ fun AppSettingsScreen(navController: NavController) { checked = takeoverWhenCall, onCheckedChange = { takeoverWhenCall = it - sharedPreferences.edit().putBoolean("takeover_when_call", it).apply() + sharedPreferences.edit { putBoolean("takeover_when_call", it)} } ) } @@ -963,7 +962,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { takeoverWhenRingingCall = !takeoverWhenRingingCall - sharedPreferences.edit().putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall).apply() + sharedPreferences.edit { putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -991,7 +990,7 @@ fun AppSettingsScreen(navController: NavController) { checked = takeoverWhenRingingCall, onCheckedChange = { takeoverWhenRingingCall = it - sharedPreferences.edit().putBoolean("takeover_when_ringing_call", it).apply() + sharedPreferences.edit { putBoolean("takeover_when_ringing_call", it)} } ) } @@ -1004,7 +1003,7 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { takeoverWhenMediaStart = !takeoverWhenMediaStart - sharedPreferences.edit().putBoolean("takeover_when_media_start", takeoverWhenMediaStart).apply() + sharedPreferences.edit { putBoolean("takeover_when_media_start", takeoverWhenMediaStart)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -1032,7 +1031,7 @@ fun AppSettingsScreen(navController: NavController) { checked = takeoverWhenMediaStart, onCheckedChange = { takeoverWhenMediaStart = it - sharedPreferences.edit().putBoolean("takeover_when_media_start", it).apply() + sharedPreferences.edit { putBoolean("takeover_when_media_start", it)} } ) } @@ -1126,7 +1125,10 @@ fun AppSettingsScreen(navController: NavController) { interactionSource = remember { MutableInteractionSource() } ) { useAlternateHeadTrackingPackets = !useAlternateHeadTrackingPackets - sharedPreferences.edit().putBoolean("use_alternate_head_tracking_packets", useAlternateHeadTrackingPackets).apply() + sharedPreferences.edit { + putBoolean( + "use_alternate_head_tracking_packets", + useAlternateHeadTrackingPackets)} }, verticalAlignment = Alignment.CenterVertically ) { @@ -1154,7 +1156,7 @@ fun AppSettingsScreen(navController: NavController) { checked = useAlternateHeadTrackingPackets, onCheckedChange = { useAlternateHeadTrackingPackets = it - sharedPreferences.edit().putBoolean("use_alternate_head_tracking_packets", it).apply() + sharedPreferences.edit { putBoolean("use_alternate_head_tracking_packets", it)} } ) } @@ -1348,7 +1350,7 @@ fun AppSettingsScreen(navController: NavController) { } val base64Value = Base64.encode(hexBytes) - sharedPreferences.edit().putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value).apply() + sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value)} Toast.makeText(context, "IRK has been set successfully", Toast.LENGTH_SHORT).show() showIrkDialog = false @@ -1437,7 +1439,7 @@ fun AppSettingsScreen(navController: NavController) { } val base64Value = Base64.encode(hexBytes) - sharedPreferences.edit().putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value).apply() + sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value)} Toast.makeText(context, "Encryption key has been set successfully", Toast.LENGTH_SHORT).show() showEncKeyDialog = false diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt index 6529cbed..94fff3c2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt @@ -74,6 +74,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -344,11 +345,11 @@ fun DebugScreen(navController: NavController) { val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet() val shouldScrollToBottom = remember { mutableStateOf(true) } - val refreshTrigger = remember { mutableStateOf(0) } - LaunchedEffect(refreshTrigger.value) { + val refreshTrigger = remember { mutableIntStateOf(0) } + LaunchedEffect(refreshTrigger.intValue) { while(true) { delay(1000) - refreshTrigger.value = refreshTrigger.value + 1 + refreshTrigger.intValue = refreshTrigger.intValue + 1 } } @@ -361,7 +362,7 @@ fun DebugScreen(navController: NavController) { Toast.makeText(context, "Packet copied to clipboard", Toast.LENGTH_SHORT).show() } - LaunchedEffect(packetLogs.size, refreshTrigger.value) { + LaunchedEffect(packetLogs.size, refreshTrigger.intValue) { if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) { listState.animateScrollToItem(packetLogs.size - 1) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt index 8197c2d3..5b77ebb9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt @@ -20,7 +20,6 @@ package me.kavishdevar.librepods.screens -import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -36,7 +35,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults 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 @@ -44,9 +42,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.remember import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Alignment +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color @@ -64,7 +61,6 @@ import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.AccessibilitySlider import me.kavishdevar.librepods.services.ServiceManager - import kotlin.io.encoding.ExperimentalEncodingApi @OptIn(ExperimentalMaterial3Api::class) @@ -301,4 +297,4 @@ fun EqualizerSettingsScreen( } } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt index dc7a5402..34592668 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt @@ -84,6 +84,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.kavishdevar.librepods.R import me.kavishdevar.librepods.utils.RadareOffsetFinder +import androidx.core.content.edit @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -528,7 +529,7 @@ fun Onboarding(navController: NavController, activityContext: Context) { onClick = { showSkipDialog = false RadareOffsetFinder.clearHookOffsets() - sharedPreferences.edit().putBoolean("skip_setup", true).apply() + sharedPreferences.edit { putBoolean("skip_setup", true) } navController.navigate("settings") { popUpTo("onboarding") { inclusive = true } } @@ -665,6 +666,3 @@ fun OnboardingPreview() { Onboarding(navController = NavController(LocalContext.current), activityContext = LocalContext.current) } -private suspend fun delay(timeMillis: Long) { - kotlinx.coroutines.delay(timeMillis) -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index 4d2f2c8a..eb884c94 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -66,6 +66,7 @@ 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.core.content.edit import androidx.navigation.NavController import me.kavishdevar.librepods.R import me.kavishdevar.librepods.constants.StemAction @@ -178,7 +179,7 @@ fun LongPress(navController: NavController, name: String) { selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES, onClick = { longPressAction = StemAction.CYCLE_NOISE_CONTROL_MODES - sharedPreferences.edit().putString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name).apply() + sharedPreferences.edit { putString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name)} }, isFirst = true, isLast = false @@ -189,7 +190,7 @@ fun LongPress(navController: NavController, name: String) { selected = longPressAction == StemAction.DIGITAL_ASSISTANT, onClick = { longPressAction = StemAction.DIGITAL_ASSISTANT - sharedPreferences.edit().putString(prefKey, StemAction.DIGITAL_ASSISTANT.name).apply() + sharedPreferences.edit { putString(prefKey, StemAction.DIGITAL_ASSISTANT.name)} }, isFirst = false, isLast = true @@ -271,7 +272,9 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF it.identifier == AACPManager.Companion.ControlCommandIdentifiers.LISTENING_MODE_CONFIGS }?.value?.takeIf { it.isNotEmpty() }?.get(0) - val savedByte = context.getSharedPreferences("settings", Context.MODE_PRIVATE).getInt("long_press_byte", 0b0101.toInt()) + val savedByte = context.getSharedPreferences("settings", Context.MODE_PRIVATE).getInt("long_press_byte", + 0b0101 + ) val byteValue = currentByteValue ?: (savedByte and 0xFF).toByte() val isChecked = (byteValue.toInt() and bit) != 0 @@ -331,8 +334,8 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF updatedByte ) - context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit() - .putInt("long_press_byte", newValue).apply() + context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit { + putInt("long_press_byte", newValue)} checked.value = false Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: false, byte: ${updatedByte.toInt() and 0xFF}, bits: ${Integer.toBinaryString(updatedByte.toInt() and 0xFF)}") @@ -345,8 +348,9 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF updatedByte ) - context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit() - .putInt("long_press_byte", newValue).apply() + context.getSharedPreferences("settings", Context.MODE_PRIVATE).edit { + putInt("long_press_byte", newValue) + } checked.value = true Log.d("PressAndHoldSettingsScreen", "Updated: $name, enabled: true, byte: ${updatedByte.toInt() and 0xFF}, bits: ${newValue.toString(2)}") diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt index 9601e931..bcb4dc53 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt @@ -69,6 +69,7 @@ import androidx.navigation.NavController import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import kotlin.io.encoding.ExperimentalEncodingApi +import androidx.core.content.edit @OptIn(ExperimentalMaterial3Api::class) @@ -153,7 +154,7 @@ fun RenameScreen(navController: NavController) { value = name.value, onValueChange = { name.value = it - sharedPreferences.edit().putString("name", it.text).apply() + sharedPreferences.edit {putString("name", it.text)} ServiceManager.getService()?.setName(it.text) }, textStyle = TextStyle( @@ -175,7 +176,7 @@ fun RenameScreen(navController: NavController) { IconButton( onClick = { name.value = TextFieldValue("") - sharedPreferences.edit().putString("name", "").apply() + sharedPreferences.edit { putString("name", "") } ServiceManager.getService()?.setName("") } ) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt index 747ed32f..64bf6ffc 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt @@ -27,7 +27,6 @@ import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically @@ -46,17 +45,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft -import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Share import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button @@ -65,7 +59,6 @@ import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold @@ -91,10 +84,7 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -102,7 +92,6 @@ 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.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -199,7 +188,7 @@ fun TroubleshootingScreen(navController: NavController) { val buttonBgColor = if (isSystemInDarkTheme()) Color(0xFF333333) else Color(0xFFDDDDDD) var instructionText by remember { mutableStateOf("") } - var isDarkTheme = isSystemInDarkTheme() + val isDarkTheme = isSystemInDarkTheme() var mDensity by remember { mutableFloatStateOf(0f) } LaunchedEffect(Unit) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index f2d3a9f5..71374d60 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -753,6 +753,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } } + @Suppress("unused") fun cameraClosed() { cameraActive = false setupStemActions() @@ -894,7 +895,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList this@AirPodsService, (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), IslandType.MOVED_TO_OTHER_DEVICE, - reversed = reasonReverseTapped + reversed = true ) } if (!aacpManager.owns) { @@ -909,12 +910,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } override fun onShowNearbyUI() { - // showIsland( - // this@AirPodsService, - // (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), - // IslandType.MOVED_TO_OTHER_DEVICE, - // reversed = false - // ) + showIsland( + this@AirPodsService, + (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), + IslandType.MOVED_TO_OTHER_DEVICE, + reversed = false + ) } override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) { @@ -1462,7 +1463,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } fun setBatteryMetadata() { - device?.let { + device?.let { it -> SystemApisUtils.setMetadata( it, it.METADATA_UNTETHERED_CASE_BATTERY, @@ -1502,7 +1503,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList val componentName = ComponentName(this, BatteryWidget::class.java) val widgetIds = appWidgetManager.getAppWidgetIds(componentName) - val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also { + val remoteViews = RemoteViews(packageName, R.layout.battery_widget).also { it -> val openActivityIntent = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) it.setOnClickPendingIntent(R.id.battery_widget, openActivityIntent) @@ -1569,7 +1570,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (widgetMobileBatteryEnabled) View.VISIBLE else View.GONE ) if (widgetMobileBatteryEnabled) { - val batteryManager = getSystemService(BatteryManager::class.java) + val batteryManager = getSystemService(BatteryManager::class.java) val batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) val charging = @@ -1606,7 +1607,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList val appWidgetManager = AppWidgetManager.getInstance(this) val componentName = ComponentName(this, NoiseControlWidget::class.java) val widgetIds = appWidgetManager.getAppWidgetIds(componentName) - val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { + val remoteViews = RemoteViews(packageName, R.layout.noise_control_widget).also { it -> val ancStatus = ancNotification.status val allowOffModeValue = aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION } val allowOffMode = allowOffModeValue?.value?.takeIf { it.isNotEmpty() }?.get(0) == 0x01.toByte() @@ -2198,7 +2199,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList Log.d("AirPodsService", macAddress) sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false) } - device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find { + device = getSystemService(BluetoothManager::class.java).adapter.bondedDevices.find { it.address == macAddress } @@ -2335,7 +2336,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList setupStemActions() while (socket.isConnected) { - socket.let { + socket.let { it -> val buffer = ByteArray(1024) val bytesRead = it.inputStream.read(buffer) var data: ByteArray diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 0a43e931..342db7ae 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -423,7 +423,7 @@ class AACPManager { ) Log.d( TAG, "Control command list is now: ${ - controlCommandStatusList.joinToString(", ") { + controlCommandStatusList.joinToString(", ") { it -> "${it.identifier.name} (${it.identifier.value.toHexString()}) - ${ it.value.joinToString( " " @@ -692,8 +692,8 @@ class AACPManager { if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}")) || targetMacAddress.length != 17 || !targetMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { throw IllegalArgumentException("MAC address must be 6 bytes") } - Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: ${targetMacAddress}") - Log.d(TAG, "Sending Media Information packet to ${targetMacAddress}") + Log.d(TAG, "SELFMAC: ${selfMacAddress}, TARGETMAC: $targetMacAddress") + Log.d(TAG, "Sending Media Information packet to $targetMacAddress") return sendDataPacket(createMediaInformationNewDevicePacket(selfMacAddress, targetMacAddress)) } @@ -775,7 +775,7 @@ class AACPManager { if (selfMacAddress.length != 17 || !selfMacAddress.matches(Regex("([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"))) { throw IllegalArgumentException("MAC address must be 6 bytes") } - Log.d(TAG, "SELFMAC: ${selfMacAddress}") + Log.d(TAG, "SELFMAC: $selfMacAddress") val targetMac = connectedDevices.find { it.mac != selfMacAddress }?.mac Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}") return sendDataPacket( @@ -842,7 +842,7 @@ class AACPManager { fun createSmartRoutingShowUIPacket(targetMacAddress: String): ByteArray { val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) - val buffer = ByteBuffer.allocate(134) + val buffer = ByteBuffer.allocate(134) buffer.put( targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt index c62b24aa..a047d024 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/BLEManager.kt @@ -30,7 +30,6 @@ import android.content.SharedPreferences import android.os.Handler import android.os.Looper import android.util.Log -import me.kavishdevar.librepods.services.ServiceManager import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec import kotlin.io.encoding.Base64 @@ -223,12 +222,13 @@ class BLEManager(private val context: Context) { } } + @SuppressLint("GetInstance") private fun decryptLastBytes(data: ByteArray, key: ByteArray): ByteArray? { return try { if (data.size < 16) { return null } - + val block = data.copyOfRange(data.size - 16, data.size) val cipher = Cipher.getInstance("AES/ECB/NoPadding") val secretKey = SecretKeySpec(key, "AES") @@ -302,7 +302,7 @@ class BLEManager(private val context: Context) { if (previousGlobalState != parsedStatus.lidOpen) { listener.onLidStateChanged(parsedStatus.lidOpen) - Log.d(TAG, "Lid state changed from ${previousGlobalState} to ${parsedStatus.lidOpen}") + Log.d(TAG, "Lid state changed from $previousGlobalState to ${parsedStatus.lidOpen}") } } @@ -348,13 +348,13 @@ class BLEManager(private val context: Context) { val isRightInEar = if (xorFactor) (status and 0x02) != 0 else (status and 0x08) != 0 val isFlipped = !primaryLeft - + val leftByteIndex = if (isFlipped) 2 else 1 val rightByteIndex = if (isFlipped) 1 else 2 - + val (isLeftCharging, leftBattery) = formatBattery(decrypted[leftByteIndex].toInt() and 0xFF) val (isRightCharging, rightBattery) = formatBattery(decrypted[rightByteIndex].toInt() and 0xFF) - + val rawCaseBatteryByte = decrypted[3].toInt() and 0xFF val (isCaseCharging, rawCaseBattery) = formatBattery(rawCaseBatteryByte) @@ -442,10 +442,10 @@ class BLEManager(private val context: Context) { val isRightInEar = if (xorFactor) (status and 0x02) != 0 else (status and 0x08) != 0 val isFlipped = !primaryLeft - + val leftBatteryNibble = if (isFlipped) (podsBattery shr 4) and 0x0F else podsBattery and 0x0F val rightBatteryNibble = if (isFlipped) podsBattery and 0x0F else (podsBattery shr 4) and 0x0F - + val caseBattery = flagsCase and 0x0F val flags = (flagsCase shr 4) and 0x0F diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt index 145c89f3..4f0ed98e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt @@ -18,6 +18,7 @@ package me.kavishdevar.librepods.utils +import android.annotation.SuppressLint import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec @@ -26,10 +27,10 @@ import javax.crypto.spec.SecretKeySpec * verifying Resolvable Private Addresses (RPA) used by AirPods. */ object BluetoothCryptography { - + /** * Verifies if the provided Bluetooth address is an RPA that matches the given Identity Resolving Key (IRK) - * + * * @param addr The Bluetooth address to verify * @param irk The Identity Resolving Key to use for verification * @return true if the address is verified as an RPA matching the IRK @@ -44,11 +45,12 @@ object BluetoothCryptography { /** * Performs E function (AES-128) as specified in Bluetooth Core Specification - * + * * @param key The key for encryption * @param data The data to encrypt * @return The encrypted data */ + @SuppressLint("GetInstance") fun e(key: ByteArray, data: ByteArray): ByteArray { val swappedKey = key.reversedArray() val swappedData = data.reversedArray() @@ -60,7 +62,7 @@ object BluetoothCryptography { /** * Performs the ah function as specified in Bluetooth Core Specification - * + * * @param k The IRK key * @param r The random part of the address * @return The hash part of the address diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt index f5130ea5..8c9ee972 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/CrossDevice.kt @@ -34,6 +34,7 @@ import android.content.Intent import android.content.SharedPreferences import android.os.ParcelUuid import android.util.Log +import androidx.core.content.edit import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -76,7 +77,7 @@ object CrossDevice { CoroutineScope(Dispatchers.IO).launch { Log.d("CrossDevice", "Initializing CrossDevice") sharedPreferences = context.getSharedPreferences("packet_logs", Context.MODE_PRIVATE) - sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply() + sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)} this@CrossDevice.bluetoothAdapter = context.getSystemService(BluetoothManager::class.java).adapter this@CrossDevice.bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser // startAdvertising() @@ -111,7 +112,7 @@ object CrossDevice { } } - @SuppressLint("MissingPermission") + @SuppressLint("MissingPermission", "unused") private fun startAdvertising() { CoroutineScope(Dispatchers.IO).launch { val settings = AdvertiseSettings.Builder() @@ -147,7 +148,7 @@ object CrossDevice { fun setAirPodsConnected(connected: Boolean) { if (connected) { isAvailable = false - sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply() + sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)} clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_CONNECTED.packet) } else { clientSocket?.outputStream?.write(CrossDevicePackets.AIRPODS_DISCONNECTED.packet) @@ -168,7 +169,7 @@ object CrossDevice { val logEntry = "$source: $packetHex" val logs = sharedPreferences.getStringSet(PACKET_LOG_KEY, mutableSetOf())?.toMutableSet() ?: mutableSetOf() logs.add(logEntry) - sharedPreferences.edit().putStringSet(PACKET_LOG_KEY, logs).apply() + sharedPreferences.edit { putStringSet(PACKET_LOG_KEY, logs)} } @SuppressLint("MissingPermission") @@ -207,10 +208,10 @@ object CrossDevice { } } else if (packet.contentEquals(CrossDevicePackets.AIRPODS_CONNECTED.packet)) { isAvailable = true - sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", true).apply() + sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", true)} } else if (packet.contentEquals(CrossDevicePackets.AIRPODS_DISCONNECTED.packet)) { isAvailable = false - sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", false).apply() + sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", false)} } else if (packet.contentEquals(CrossDevicePackets.REQUEST_BATTERY_BYTES.packet)) { Log.d("CrossDevice", "Received battery request, battery data: ${batteryBytes.joinToString("") { "%02x".format(it) }}") sendRemotePacket(batteryBytes) @@ -223,7 +224,7 @@ object CrossDevice { } else { if (packet.sliceArray(0..3).contentEquals(CrossDevicePackets.AIRPODS_DATA_HEADER.packet)) { isAvailable = true - sharedPreferences.edit().putBoolean("CrossDeviceIsAvailable", true).apply() + sharedPreferences.edit { putBoolean("CrossDeviceIsAvailable", true) } if (packet.size % 2 == 0) { val half = packet.size / 2 if (packet.sliceArray(0 until half).contentEquals(packet.sliceArray(half until packet.size))) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt index 769f04de..d8ef502a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt @@ -113,7 +113,7 @@ class IslandWindow(private val context: Context) { intent.getParcelableArrayListExtra("data", Battery::class.java) } else { @Suppress("DEPRECATION") - intent.getParcelableArrayListExtra("data") + intent.getParcelableArrayListExtra("data") } updateBatteryDisplay(batteryList) } else if (intent?.action == AirPodsNotifications.DISCONNECT_RECEIVERS) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt index 1fc4d8ab..e2d5046c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/KotlinModule.kt @@ -1,5 +1,6 @@ package me.kavishdevar.librepods.utils +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo @@ -17,6 +18,7 @@ import android.widget.FrameLayout import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout +import androidx.core.net.toUri import io.github.libxposed.api.XposedInterface import io.github.libxposed.api.XposedInterface.AfterHookCallback import io.github.libxposed.api.XposedModule @@ -27,7 +29,7 @@ import io.github.libxposed.api.annotations.XposedHooker private const val TAG = "AirPodsHook" private lateinit var module: KotlinModule - +@SuppressLint("DiscouragedApi", "PrivateApi") class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModule(base, param) { init { Log.i(TAG, "AirPodsHook module initialized at :: ${param.processName}") @@ -60,7 +62,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul val updateIconMethod = headerControllerClass.getDeclaredMethod( "updateIcon", - android.widget.ImageView::class.java, + ImageView::class.java, String::class.java) hook(updateIconMethod, BluetoothIconHooker::class.java) @@ -89,7 +91,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul val updateIconMethod = headerControllerClass.getDeclaredMethod( "updateIcon", - android.widget.ImageView::class.java, + ImageView::class.java, String::class.java) hook(updateIconMethod, BluetoothIconHooker::class.java) @@ -209,7 +211,7 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul val imageView = callback.args[0] as ImageView val iconUri = callback.args[1] as String - val uri = android.net.Uri.parse(iconUri) + val uri = iconUri.toUri() if (uri.toString().startsWith("android.resource://me.kavishdevar.librepods")) { Log.i(TAG, "Handling AirPods icon URI: $uri") @@ -571,10 +573,10 @@ class KotlinModule(base: XposedInterface, param: ModuleLoadedParam): XposedModul addView(icon) - if (isSelected) { - background = createSelectedBackground(context) + background = if (isSelected) { + createSelectedBackground(context) } else { - background = null + null } setOnClickListener { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt index 8ce65ab6..cc59214e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt @@ -19,8 +19,6 @@ package me.kavishdevar.librepods.utils import android.content.Context -import android.content.Intent -import android.net.Uri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.BufferedReader @@ -30,7 +28,7 @@ import java.io.InputStreamReader class LogCollector(private val context: Context) { private var isCollecting = false private var logProcess: Process? = null - + suspend fun openXposedSettings(context: Context) { withContext(Dispatchers.IO) { val command = if (android.os.Build.VERSION.SDK_INT >= 29) { @@ -38,42 +36,42 @@ class LogCollector(private val context: Context) { } else { "am broadcast -a android.provider.Telephony.SECRET_CODE -d android_secret_code://5776733 android" } - + executeRootCommand(command) } } - + suspend fun clearLogs() { withContext(Dispatchers.IO) { executeRootCommand("logcat -c") } } - + suspend fun killBluetoothService() { withContext(Dispatchers.IO) { executeRootCommand("killall com.android.bluetooth") } } - + private suspend fun getPackageUIDs(): Pair { return withContext(Dispatchers.IO) { val btUid = executeRootCommand("dumpsys package com.android.bluetooth | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'") .trim() .takeIf { it.isNotEmpty() } - + val appUid = executeRootCommand("dumpsys package me.kavishdevar.librepods | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'") .trim() .takeIf { it.isNotEmpty() } - + Pair(btUid, appUid) } } - + suspend fun startLogCollection(listener: (String) -> Unit, connectionDetectedCallback: () -> Unit): String { return withContext(Dispatchers.IO) { isCollecting = true val (btUid, appUid) = getPackageUIDs() - + val uidFilter = buildString { if (!btUid.isNullOrEmpty() && !appUid.isNullOrEmpty()) { append("$btUid,$appUid") @@ -83,33 +81,33 @@ class LogCollector(private val context: Context) { append(appUid) } } - + val command = if (uidFilter.isNotEmpty()) { "su -c logcat --uid=$uidFilter -v threadtime" } else { "su -c logcat -v threadtime" } - + val logs = StringBuilder() try { logProcess = Runtime.getRuntime().exec(command) val reader = BufferedReader(InputStreamReader(logProcess!!.inputStream)) var line: String? = null var connectionDetected = false - + while (isCollecting && reader.readLine().also { line = it } != null) { line?.let { if (it.contains("")) { connectionDetected = true @@ -118,7 +116,7 @@ class LogCollector(private val context: Context) { connectionDetected = true connectionDetectedCallback() } else if (it.contains("")) { - } + } else if (it.contains("AirPodsService") && it.contains("Connected to device")) { connectionDetected = true connectionDetectedCallback() @@ -139,17 +137,17 @@ class LogCollector(private val context: Context) { logs.append("Error collecting logs: ${e.message}").append("\n") e.printStackTrace() } - + logs.toString() } } - + fun stopLogCollection() { isCollecting = false logProcess?.destroy() logProcess = null } - + suspend fun saveLogToInternalStorage(fileName: String, content: String): File? { return withContext(Dispatchers.IO) { try { @@ -157,7 +155,7 @@ class LogCollector(private val context: Context) { if (!logsDir.exists()) { logsDir.mkdir() } - + val file = File(logsDir, fileName) file.writeText(content) return@withContext file @@ -167,31 +165,31 @@ class LogCollector(private val context: Context) { } } } - + suspend fun addLogMarker(markerType: LogMarkerType, details: String = "") { withContext(Dispatchers.IO) { val timestamp = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", java.util.Locale.US) .format(java.util.Date()) - + val marker = when (markerType) { LogMarkerType.START -> " [$timestamp] Beginning connection test" LogMarkerType.SUCCESS -> " [$timestamp] Connection test completed successfully" LogMarkerType.FAILURE -> " [$timestamp] Connection test failed" LogMarkerType.CUSTOM -> " [$timestamp]" } - + val command = "log -t AirPodsService \"$marker\"" executeRootCommand(command) } } - + enum class LogMarkerType { START, SUCCESS, FAILURE, CUSTOM } - + private suspend fun executeRootCommand(command: String): String { return withContext(Dispatchers.IO) { try { @@ -199,11 +197,11 @@ class LogCollector(private val context: Context) { val reader = BufferedReader(InputStreamReader(process.inputStream)) val output = StringBuilder() var line: String? - + while (reader.readLine().also { line = it } != null) { output.append(line).append("\n") } - + process.waitFor() output.toString() } catch (e: Exception) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt index aa55c156..631673bb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt @@ -275,7 +275,7 @@ object MediaController { } else { initialVolume!! } - smoothVolumeTransition(initialVolume!!, targetVolume.toInt()) + smoothVolumeTransition(initialVolume!!, targetVolume) if (conversationalAwarenessPauseMusic) { sendPause(force = true) } @@ -311,4 +311,4 @@ object MediaController { } }) } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt index d050cbad..1d54aa95 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/PopupWindow.kt @@ -49,7 +49,6 @@ import me.kavishdevar.librepods.constants.AirPodsNotifications import me.kavishdevar.librepods.constants.Battery import me.kavishdevar.librepods.constants.BatteryComponent import me.kavishdevar.librepods.constants.BatteryStatus -import kotlin.collections.find @SuppressLint("InflateParams", "ClickableViewAccessibility") class PopupWindow( @@ -172,7 +171,12 @@ class PopupWindow( batteryUpdateReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == AirPodsNotifications.BATTERY_DATA) { - val batteryList = intent.getParcelableArrayListExtra("data") + val batteryList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra("data", Battery::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableArrayListExtra("data") + } if (batteryList != null) { updateBatteryStatusFromList(batteryList) } @@ -272,7 +276,4 @@ class PopupWindow( onCloseCallback() } } - - val isShowing: Boolean - get() = mView.parent != null && !isClosing } diff --git a/android/app/src/main/res/drawable/app_widget_background.xml b/android/app/src/main/res/drawable/app_widget_background.xml new file mode 100644 index 00000000..785445c6 --- /dev/null +++ b/android/app/src/main/res/drawable/app_widget_background.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/app_widget_inner_view_background.xml b/android/app/src/main/res/drawable/app_widget_inner_view_background.xml new file mode 100644 index 00000000..11a09f9b --- /dev/null +++ b/android/app/src/main/res/drawable/app_widget_inner_view_background.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/island_window.xml b/android/app/src/main/res/layout/island_window.xml index 5cb1e143..c804737b 100644 --- a/android/app/src/main/res/layout/island_window.xml +++ b/android/app/src/main/res/layout/island_window.xml @@ -113,7 +113,7 @@ android:layout_gravity="center" android:translationX="-12dp" android:background="@drawable/ic_undo_button_bg" - android:contentDescription="Undo button" + android:contentDescription="@string/undo" android:scaleType="centerInside" android:src="@drawable/ic_undo" android:tint="@android:color/white" @@ -121,4 +121,4 @@ android:translationZ="8dp" android:visibility="gone" /> - \ No newline at end of file + diff --git a/android/app/src/main/res/layout/noise_control_widget.xml b/android/app/src/main/res/layout/noise_control_widget.xml index 6b53bb1b..baa2f4bd 100644 --- a/android/app/src/main/res/layout/noise_control_widget.xml +++ b/android/app/src/main/res/layout/noise_control_widget.xml @@ -4,12 +4,14 @@ android:id="@+id/noise_control_widget" android:layout_width="match_parent" android:layout_height="match_parent" - android:theme="@style/Theme.LibrePods.AppWidgetContainer"> + android:theme="@style/Theme.LibrePods.AppWidgetContainer" + tools:ignore="ContentDescription,NestedWeights"> + android:textSize="12sp" + tools:ignore="NestedWeights" /> + android:textSize="12sp" + tools:ignore="NestedWeights" /> + + + + + diff --git a/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 00000000..8fde4563 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/src/main/res/raw/blip_yes.wav b/android/app/src/main/res/raw/blip_yes.wav index b796b599876f2c211243c8c0e3b0be3bbec77b21..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 33444 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 - - - - - diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 3c3413dc..25458ed6 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -48,7 +48,7 @@ Connected Moved to Linux Moved to other device - Reconnect from notification + Reconnect from notification Head Tracking Nod to answer calls, and shake your head to decline. General @@ -81,4 +81,5 @@ Your phone starts ringing Starting media playback Your phone starts playing media + Undo diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 30e8f43c..df666a4b 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,12 +1,6 @@ - + - + From c53356f77ed267cb3a7f69079a84352c390ea9c2 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Thu, 11 Sep 2025 12:21:23 +0530 Subject: [PATCH 08/72] android: implement the accessiblity settings page --- .../librepods/CustomDeviceActivity.kt | 337 +------ .../composables/AccessibilitySettings.kt | 3 - .../ConversationalAwarenessSwitch.kt | 4 +- .../composables/LoudSoundReductionSwitch.kt | 128 +++ .../librepods/composables/NavigationButton.kt | 6 +- .../librepods/composables/ToneVolumeSlider.kt | 2 +- .../screens/AccessibilitySettingsScreen.kt | 927 +++++++++++------- .../screens/AirPodsSettingsScreen.kt | 6 +- .../screens/EqualizerSettingsScreen.kt | 300 ------ .../librepods/services/AirPodsService.kt | 2 + .../librepods/utils/AACPManager.kt | 13 + .../kavishdevar/librepods/utils/ATTManager.kt | 112 +++ android/app/src/main/res/values/strings.xml | 5 +- 13 files changed, 841 insertions(+), 1004 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt index 27e4679e..bb9c9d8a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt @@ -19,70 +19,19 @@ package me.kavishdevar.librepods import android.annotation.SuppressLint -import android.bluetooth.BluetoothDevice -import android.bluetooth.BluetoothManager -import android.bluetooth.BluetoothSocket import android.os.Bundle -import android.os.ParcelUuid -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen -import me.kavishdevar.librepods.screens.EqualizerSettingsScreen import me.kavishdevar.librepods.ui.theme.LibrePodsTheme -import org.lsposed.hiddenapibypass.HiddenApiBypass -import java.io.IOException -import java.nio.ByteBuffer -import java.nio.ByteOrder -@Suppress("PrivatePropertyName") +@ExperimentalHazeMaterialsApi class CustomDevice : ComponentActivity() { - private val TAG = "AirPodsAccessibilitySettings" - private var socket: BluetoothSocket? = null - private val deviceAddress = "28:2D:7F:C2:05:5B" - private val uuid: ParcelUuid = ParcelUuid.fromString("00000000-0000-0000-0000-00000000000") - - // Data states - private val isConnected = mutableStateOf(false) - private val leftAmplification = mutableFloatStateOf(1.0f) - private val leftTone = mutableFloatStateOf(1.0f) - private val leftAmbientNoiseReduction = mutableFloatStateOf(0.5f) - private val leftConversationBoost = mutableStateOf(false) - private val leftEQ = mutableStateOf(FloatArray(8) { 50.0f }) - - private val rightAmplification = mutableFloatStateOf(1.0f) - private val rightTone = mutableFloatStateOf(1.0f) - private val rightAmbientNoiseReduction = mutableFloatStateOf(0.5f) - private val rightConversationBoost = mutableStateOf(false) - private val rightEQ = mutableStateOf(FloatArray(8) { 50.0f }) - - private val singleMode = mutableStateOf(false) - private val amplification = mutableFloatStateOf(1.0f) - private val balance = mutableFloatStateOf(0.5f) - - private val retryCount = mutableIntStateOf(0) - private val showRetryButton = mutableStateOf(false) - private val maxRetries = 3 - - private var debounceJob: Job? = null - - // Phone and Media EQ state - private val phoneMediaEQ = mutableStateOf(FloatArray(8) { 50.0f }) - @SuppressLint("MissingPermission") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -93,294 +42,14 @@ class CustomDevice : ComponentActivity() { NavHost(navController = navController, startDestination = "main") { composable("main") { - AccessibilitySettingsScreen( - navController = navController, - isConnected = isConnected.value, - leftAmplification = leftAmplification, - leftTone = leftTone, - leftAmbientNoiseReduction = leftAmbientNoiseReduction, - leftConversationBoost = leftConversationBoost, - rightAmplification = rightAmplification, - rightTone = rightTone, - rightAmbientNoiseReduction = rightAmbientNoiseReduction, - rightConversationBoost = rightConversationBoost, - singleMode = singleMode, - amplification = amplification, - balance = balance, - showRetryButton = showRetryButton.value, - onRetry = { CoroutineScope(Dispatchers.IO).launch { connectL2CAP() } }, - onSettingsChanged = { sendAccessibilitySettings() } - ) - } - composable("eq") { - EqualizerSettingsScreen( - navController = navController, - leftEQ = leftEQ, - rightEQ = rightEQ, - singleMode = singleMode, - onEQChanged = { sendAccessibilitySettings() }, - phoneMediaEQ = phoneMediaEQ - ) + AccessibilitySettingsScreen() } } } } - - // Connect automatically - CoroutineScope(Dispatchers.IO).launch { connectL2CAP() } } override fun onDestroy() { super.onDestroy() - socket?.close() - } - - @SuppressLint("MissingPermission") - private suspend fun connectL2CAP() { - retryCount.intValue = 0 - // Close any existing socket - socket?.close() - socket = null - while (retryCount.intValue < maxRetries) { - try { - Log.d(TAG, "Starting L2CAP connection setup, attempt ${retryCount.intValue + 1}") - HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") - val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager - val device: BluetoothDevice = manager.adapter.getRemoteDevice(deviceAddress) - socket = createBluetoothSocket(device) - - withTimeout(5000L) { - socket?.connect() - } - - withContext(Dispatchers.Main) { - isConnected.value = true - showRetryButton.value = false - Log.d(TAG, "L2CAP connection established successfully") - } - - // Read current settings - readCurrentSettings() - - // Start listening for responses - listenForData() - - return - } catch (e: Exception) { - Log.e(TAG, "Failed to connect, attempt ${retryCount.intValue + 1}: ${e.message}") - retryCount.intValue++ - if (retryCount.intValue < maxRetries) { - delay(2000) // Wait 2 seconds before retry - } - } - } - - // After max retries - withContext(Dispatchers.Main) { - isConnected.value = false - showRetryButton.value = true - Log.e(TAG, "Failed to connect after $maxRetries attempts") - } - } - - private fun createBluetoothSocket(device: BluetoothDevice): BluetoothSocket { - val type = 3 // L2CAP - val constructorSpecs = listOf( - arrayOf(device, type, true, true, 31, uuid), - arrayOf(device, type, 1, true, true, 31, uuid), - arrayOf(type, 1, true, true, device, 31, uuid), - arrayOf(type, true, true, device, 31, uuid) - ) - - val constructors = BluetoothSocket::class.java.declaredConstructors - Log.d(TAG, "BluetoothSocket has ${constructors.size} constructors") - - var lastException: Exception? = null - var attemptedConstructors = 0 - - for ((index, params) in constructorSpecs.withIndex()) { - try { - Log.d(TAG, "Trying constructor signature #${index + 1}") - attemptedConstructors++ - return HiddenApiBypass.newInstance(BluetoothSocket::class.java, *params) as BluetoothSocket - } catch (e: Exception) { - Log.e(TAG, "Constructor signature #${index + 1} failed: ${e.message}") - lastException = e - } - } - - val errorMessage = "Failed to create BluetoothSocket after trying $attemptedConstructors constructor signatures" - Log.e(TAG, errorMessage) - throw lastException ?: IllegalStateException(errorMessage) - } - - private fun readCurrentSettings() { - CoroutineScope(Dispatchers.IO).launch { - try { - Log.d(TAG, "Sending read settings command: 0A1800") - val readCommand = byteArrayOf(0x0A, 0x18, 0x00) - socket?.outputStream?.write(readCommand) - socket?.outputStream?.flush() - Log.d(TAG, "Read settings command sent") - } catch (e: IOException) { - Log.e(TAG, "Failed to send read command: ${e.message}") - } - } - } - - private fun listenForData() { - CoroutineScope(Dispatchers.IO).launch { - try { - val buffer = ByteArray(1024) - Log.d(TAG, "Started listening for incoming data") - while (socket?.isConnected == true) { - val bytesRead = socket?.inputStream?.read(buffer) - if (bytesRead != null && bytesRead > 0) { - val data = buffer.copyOfRange(0, bytesRead) - Log.d(TAG, "Received data: ${data.joinToString(" ") { "%02X".format(it) }}") - parseSettingsResponse(data) - } else if (bytesRead == -1) { - Log.d(TAG, "Connection closed by remote device") - withContext(Dispatchers.Main) { - isConnected.value = false - } - // Attempt to reconnect - connectL2CAP() - break - } - } - } catch (e: IOException) { - Log.e(TAG, "Connection lost: ${e.message}") - withContext(Dispatchers.Main) { - isConnected.value = false - } - // Close socket - socket?.close() - socket = null - // Attempt to reconnect - connectL2CAP() - } - } - } - - private fun parseSettingsResponse(data: ByteArray) { - if (data.size < 2 || data[0] != 0x0B.toByte()) { - Log.d(TAG, "Not a settings response") - return - } - - val settingsData = data.copyOfRange(1, data.size) - if (settingsData.size < 100) { // 25 floats * 4 bytes - Log.e(TAG, "Settings data too short: ${settingsData.size} bytes") - return - } - - val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) - - // Global enabled - val enabled = buffer.float - Log.d(TAG, "Parsed enabled: $enabled") - - // Left bud - val newLeftEQ = leftEQ.value.copyOf() - for (i in 0..7) { - newLeftEQ[i] = buffer.float - Log.d(TAG, "Parsed left EQ${i+1}: ${newLeftEQ[i]}") - } - leftEQ.value = newLeftEQ - if (singleMode.value) rightEQ.value = newLeftEQ - - leftAmplification.floatValue = buffer.float - Log.d(TAG, "Parsed left amplification: ${leftAmplification.floatValue}") - leftTone.floatValue = buffer.float - Log.d(TAG, "Parsed left tone: ${leftTone.floatValue}") - if (singleMode.value) rightTone.floatValue = leftTone.floatValue - val leftConvFloat = buffer.float - leftConversationBoost.value = leftConvFloat > 0.5f - Log.d(TAG, "Parsed left conversation boost: $leftConvFloat (${leftConversationBoost.value})") - if (singleMode.value) rightConversationBoost.value = leftConversationBoost.value - leftAmbientNoiseReduction.floatValue = buffer.float - Log.d(TAG, "Parsed left ambient noise reduction: ${leftAmbientNoiseReduction.floatValue}") - if (singleMode.value) rightAmbientNoiseReduction.floatValue = leftAmbientNoiseReduction.floatValue - - // Right bud - val newRightEQ = rightEQ.value.copyOf() - for (i in 0..7) { - newRightEQ[i] = buffer.float - Log.d(TAG, "Parsed right EQ${i+1}: ${newRightEQ[i]}") - } - rightEQ.value = newRightEQ - - rightAmplification.floatValue = buffer.float - Log.d(TAG, "Parsed right amplification: ${rightAmplification.floatValue}") - rightTone.floatValue = buffer.float - Log.d(TAG, "Parsed right tone: ${rightTone.floatValue}") - val rightConvFloat = buffer.float - rightConversationBoost.value = rightConvFloat > 0.5f - Log.d(TAG, "Parsed right conversation boost: $rightConvFloat (${rightConversationBoost.value})") - rightAmbientNoiseReduction.floatValue = buffer.float - Log.d(TAG, "Parsed right ambient noise reduction: ${rightAmbientNoiseReduction.floatValue}") - - Log.d(TAG, "Settings parsed successfully") - - // Update single mode values if in single mode - if (singleMode.value) { - val avg = (leftAmplification.floatValue + rightAmplification.floatValue) / 2 - amplification.floatValue = avg.coerceIn(0f, 1f) - val diff = rightAmplification.floatValue - leftAmplification.floatValue - balance.floatValue = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) - } - } - - private fun sendAccessibilitySettings() { - if (!isConnected.value || socket == null) { - Log.w(TAG, "Not connected, cannot send settings") - return - } - - debounceJob?.cancel() - debounceJob = CoroutineScope(Dispatchers.IO).launch { - delay(100) - try { - val buffer = ByteBuffer.allocate(103).order(ByteOrder.LITTLE_ENDIAN) // 3 header + 100 data bytes - - buffer.put(0x12) - buffer.put(0x18) - buffer.put(0x00) - buffer.putFloat(1.0f) // enabled - - // Left bud - for (eq in leftEQ.value) { - buffer.putFloat(eq) - } - buffer.putFloat(leftAmplification.floatValue) - buffer.putFloat(leftTone.floatValue) - buffer.putFloat(if (leftConversationBoost.value) 1.0f else 0.0f) - buffer.putFloat(leftAmbientNoiseReduction.floatValue) - - // Right bud - for (eq in rightEQ.value) { - buffer.putFloat(eq) - } - buffer.putFloat(rightAmplification.floatValue) - buffer.putFloat(rightTone.floatValue) - buffer.putFloat(if (rightConversationBoost.value) 1.0f else 0.0f) - buffer.putFloat(rightAmbientNoiseReduction.floatValue) - - val packet = buffer.array() - Log.d(TAG, "Packet length: ${packet.size}") - socket?.outputStream?.write(packet) - socket?.outputStream?.flush() - Log.d(TAG, "Accessibility settings sent: ${packet.joinToString(" ") { "%02X".format(it) }}") - } catch (e: IOException) { - Log.e(TAG, "Failed to send accessibility settings: ${e.message}") - withContext(Dispatchers.Main) { - isConnected.value = false - } - // Close socket - socket?.close() - socket = null - } - } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt index 42c942ba..ac870f23 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt @@ -154,9 +154,6 @@ fun AccessibilitySettings() { }, textColor = textColor ) - - SinglePodANCSwitch() - VolumeControlSwitch() } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt index 8c2aa7d9..668bdad7 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt @@ -42,9 +42,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi @@ -111,7 +113,7 @@ fun ConversationalAwarenessSwitch() { ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Lowers media volume and reduces background noise when you start speaking to other people.", + text = stringResource(R.string.conversational_awareness_description), fontSize = 12.sp, color = textColor.copy(0.6f), lineHeight = 14.sp, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt new file mode 100644 index 00000000..1eecf4dd --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt @@ -0,0 +1,128 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +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.isSystemInDarkTheme +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.utils.ATTManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun LoudSoundReductionSwitch(attManager: ATTManager) { + var loudSoundReductionEnabled by remember { + mutableStateOf( + false + ) + } + LaunchedEffect(Unit) { + while (attManager.socket?.isConnected != true) { + delay(100) + } + attManager.read(0x1b) + } + + LaunchedEffect(loudSoundReductionEnabled) { + if (attManager.socket?.isConnected != true) return@LaunchedEffect + attManager.write(0x1b, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0)) + } + + val isDarkTheme = isSystemInDarkTheme() + 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() } + ) { + loudSoundReductionEnabled = !loudSoundReductionEnabled + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = stringResource(R.string.loud_sound_reduction), + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.loud_sound_reduction_description), + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ) + } + StyledSwitch( + checked = loudSoundReductionEnabled, + onCheckedChange = { + loudSoundReductionEnabled = it + }, + ) + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt index 28e71bfd..3b4bbf30 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt @@ -50,7 +50,7 @@ import androidx.navigation.NavController @Composable -fun NavigationButton(to: String, name: String, navController: NavController) { +fun NavigationButton(to: String, name: String, navController: NavController, onClick: (() -> Unit)? = null) { val isDarkTheme = isSystemInDarkTheme() var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) @@ -67,7 +67,7 @@ fun NavigationButton(to: String, name: String, navController: NavController) { backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - navController.navigate(to) + if (onClick != null) onClick() else navController.navigate(to) } ) } @@ -79,7 +79,7 @@ fun NavigationButton(to: String, name: String, navController: NavController) { ) Spacer(modifier = Modifier.weight(1f)) IconButton( - onClick = { navController.navigate(to) }, + onClick = { if (onClick != null) onClick() else navController.navigate(to) }, colors = IconButtonDefaults.iconButtonColors( containerColor = Color.Transparent, contentColor = if (isDarkTheme) Color.White else Color.Black diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt index 38e190eb..c9db3614 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt @@ -77,7 +77,7 @@ fun ToneVolumeSlider() { Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth(0.95f), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index f6d1fc21..fb94720e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -18,454 +18,661 @@ package me.kavishdevar.librepods.screens +import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures 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.fillMaxHeight 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.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.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.drawBehind +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font +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 dev.chrisbanes.haze.HazeEffectScope +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.AccessibilitySlider -import me.kavishdevar.librepods.composables.NavigationButton +import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch +import me.kavishdevar.librepods.composables.SinglePodANCSwitch import me.kavishdevar.librepods.composables.StyledSwitch +import me.kavishdevar.librepods.composables.ToneVolumeSlider +import me.kavishdevar.librepods.composables.VolumeControlSwitch +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.ATTManager +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder +import kotlin.io.encoding.ExperimentalEncodingApi -@OptIn(ExperimentalMaterial3Api::class) +var debounceJob: Job? = null +const val TAG = "AccessibilitySettings" + +@SuppressLint("DefaultLocale") +@ExperimentalHazeMaterialsApi +@OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) @Composable -fun AccessibilitySettingsScreen( - navController: NavController, - isConnected: Boolean, - leftAmplification: MutableState, - leftTone: MutableState, - leftAmbientNoiseReduction: MutableState, - leftConversationBoost: MutableState, - rightAmplification: MutableState, - rightTone: MutableState, - rightAmbientNoiseReduction: MutableState, - rightConversationBoost: MutableState, - singleMode: MutableState, - amplification: MutableState, - balance: MutableState, - showRetryButton: Boolean, - onRetry: () -> Unit, - onSettingsChanged: () -> Unit -) { +fun AccessibilitySettingsScreen() { val isDarkTheme = isSystemInDarkTheme() - val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) - val cardBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black + val verticalScrollState = rememberScrollState() + val hazeState = remember { HazeState() } + val snackbarHostState = remember { SnackbarHostState() } + val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected")) + DisposableEffect(attManager) { + onDispose { + Log.d(TAG, "Disconnecting from ATT...") + try { + attManager.disconnect() + } catch (e: Exception) { + Log.w(TAG, "Error while disconnecting ATTManager: ${e.message}") + } + } + } Scaffold( + containerColor = if (isSystemInDarkTheme()) Color( + 0xFF000000 + ) else Color( + 0xFFF2F2F7 + ), topBar = { - TopAppBar( + val darkMode = isSystemInDarkTheme() + val mDensity = remember { mutableFloatStateOf(1f) } + + CenterAlignedTopAppBar( title = { Text( - "Accessibility Settings", + text = "Accessibility Settings", style = TextStyle( fontSize = 20.sp, fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + color = if (darkMode) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) ) ) }, - colors = TopAppBarDefaults.topAppBarColors( + modifier = Modifier + .hazeEffect( + state = hazeState, + style = CupertinoMaterials.thick(), + block = fun HazeEffectScope.() { + alpha = + if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f + }) + .drawBehind { + mDensity.floatValue = density + val strokeWidth = 0.7.dp.value * density + val y = size.height - strokeWidth / 2 + if (verticalScrollState.value > 60.dp.value * density) { + drawLine( + if (darkMode) Color.DarkGray else Color.LightGray, + Offset(0f, y), + Offset(size.width, y), + strokeWidth + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( containerColor = Color.Transparent ) ) }, - containerColor = backgroundColor + snackbarHost = { SnackbarHost(snackbarHostState) } ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .padding(paddingValues) .padding(horizontal = 16.dp) - .verticalScroll(rememberScrollState()), + .verticalScroll(verticalScrollState), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - // Retry Button if needed - if (!isConnected && showRetryButton) { - Button( - onClick = onRetry, - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF007AFF) + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + val enabled = remember { mutableStateOf(false) } + val amplificationSliderValue = remember { mutableFloatStateOf(0.5f) } + val balanceSliderValue = remember { mutableFloatStateOf(0.5f) } + val toneSliderValue = remember { mutableFloatStateOf(0.5f) } + val ambientNoiseReductionSliderValue = remember { mutableFloatStateOf(0.0f) } + val conversationBoostEnabled = remember { mutableStateOf(false) } + val eq = remember { mutableStateOf(FloatArray(8)) } + + // Flag to prevent sending default settings to device while we are loading device state + val initialLoadComplete = remember { mutableStateOf(false) } + + // Ensure we actually read device properties before allowing writes. + // Try up to 3 times silently; mark success only if parse succeeds. + val initialReadSucceeded = remember { mutableStateOf(false) } + val initialReadAttempts = remember { mutableStateOf(0) } + + // Populate a single stored representation for convenience (kept for debug/logging) + val transparencySettings = remember { + mutableStateOf( + TransparencySettings( + enabled = enabled.value, + leftEQ = eq.value, + rightEQ = eq.value, + leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, + rightAmplification = amplificationSliderValue.floatValue + (balanceSliderValue.floatValue - 0.5f) * amplificationSliderValue.floatValue * 2, + leftTone = toneSliderValue.floatValue, + rightTone = toneSliderValue.floatValue, + leftConversationBoost = conversationBoostEnabled.value, + rightConversationBoost = conversationBoostEnabled.value, + leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + netAmplification = amplificationSliderValue.floatValue, + balance = balanceSliderValue.floatValue ) - ) { - Text("Retry Connection") + ) + } + + LaunchedEffect(enabled.value, amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, eq.value, initialLoadComplete.value, initialReadSucceeded.value) { + // Do not send updates until we have populated UI from the device + if (!initialLoadComplete.value) { + Log.d(TAG, "Initial device load not complete - skipping send") + return@LaunchedEffect + } + + // Do not send until we've successfully read the device properties at least once. + if (!initialReadSucceeded.value) { + Log.d(TAG, "Initial device read not successful yet - skipping send until read succeeds") + return@LaunchedEffect + } + + transparencySettings.value = TransparencySettings( + enabled = enabled.value, + leftEQ = eq.value, + rightEQ = eq.value, + leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, + rightAmplification = amplificationSliderValue.floatValue + (balanceSliderValue.floatValue - 0.5f) * amplificationSliderValue.floatValue * 2, + leftTone = toneSliderValue.floatValue, + rightTone = toneSliderValue.floatValue, + leftConversationBoost = conversationBoostEnabled.value, + rightConversationBoost = conversationBoostEnabled.value, + leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + netAmplification = amplificationSliderValue.floatValue, + balance = balanceSliderValue.floatValue + ) + Log.d("TransparencySettings", "Updated settings: ${transparencySettings.value}") + sendTransparencySettings(attManager, transparencySettings.value) + } + + // Move initial connect / read here so we can populate the UI state variables above. + LaunchedEffect(Unit) { + Log.d(TAG, "Connecting to ATT...") + try { + attManager.connect() + while (attManager.socket?.isConnected != true) { + delay(100) + } + + var parsedSettings: TransparencySettings? = null + // Try up to 3 read attempts silently + for (attempt in 1..3) { + initialReadAttempts.value = attempt + try { + val data = attManager.read(0x18) + parsedSettings = parseTransparencySettingsResponse(data = data) + if (parsedSettings != null) { + Log.d(TAG, "Parsed settings on attempt $attempt") + break + } else { + Log.d(TAG, "Parsing returned null on attempt $attempt") + } + } catch (e: Exception) { + Log.w(TAG, "Read attempt $attempt failed: ${e.message}") + } + delay(200) + } + + if (parsedSettings != null) { + Log.d(TAG, "Initial transparency settings: $parsedSettings") + // Populate UI states from device values without triggering a send (initialReadSucceeded is set below) + enabled.value = parsedSettings.enabled + amplificationSliderValue.floatValue = parsedSettings.netAmplification + balanceSliderValue.floatValue = parsedSettings.balance + toneSliderValue.floatValue = parsedSettings.leftTone + ambientNoiseReductionSliderValue.floatValue = parsedSettings.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsedSettings.leftConversationBoost + eq.value = parsedSettings.leftEQ.copyOf() + initialReadSucceeded.value = true + } else { + Log.d(TAG, "Failed to read/parse initial transparency settings after ${initialReadAttempts.value} attempts") + } + } catch (e: IOException) { + e.printStackTrace() + } finally { + // mark load complete (UI may be editable), but writes remain blocked until a successful read + initialLoadComplete.value = true } } - // Single Mode Switch - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) + AccessibilityToggle( + text = "Transparency Mode", + mutableState = enabled, + independent = true + ) + Text( + text = stringResource(R.string.customize_transparency_mode_description), + style = TextStyle( + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ), + modifier = Modifier + .padding(horizontal = 2.dp) + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Customize Transparency Mode".uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(8.dp) + ) { + AccessibilitySlider( + label = "Amplification", + valueRange = 0f..1f, + value = amplificationSliderValue.floatValue, + onValueChange = { + amplificationSliderValue.floatValue = it + }, + ) + AccessibilitySlider( + label = "Balance", + valueRange = 0f..1f, + value = balanceSliderValue.floatValue, + onValueChange = { + balanceSliderValue.floatValue = it + }, + ) + AccessibilitySlider( + label = "Tone", + valueRange = 0f..1f, + value = toneSliderValue.floatValue, + onValueChange = { + toneSliderValue.floatValue = it + }, + ) + AccessibilitySlider( + label = "Ambient Noise Reduction", + valueRange = 0f..1f, + value = ambientNoiseReductionSliderValue.floatValue, + onValueChange = { + ambientNoiseReductionSliderValue.floatValue = it + }, + ) + AccessibilityToggle( + text = "Conversation Boost", + mutableState = conversationBoostEnabled + ) + } + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = "AUDIO", + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(top = 2.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween ) { - Row( + Text( + text = "Tone Volume", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Light, + color = textColor + ), modifier = Modifier + .padding(horizontal = 8.dp, vertical = 4.dp) .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - "Single Mode", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, + ) + ToneVolumeSlider() + SinglePodANCSwitch() + VolumeControlSwitch() + LoudSoundReductionSwitch(attManager) + } + Spacer(modifier = Modifier.height(2.dp)) + + Text( + text = "Equalizer".uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + for (i in 0 until 8) { + val eqValue = remember(eq.value[i]) { mutableFloatStateOf(eq.value[i]) } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(32.dp) + ) { + Text( + text = String.format("%.2f", eqValue.floatValue), + fontSize = 12.sp, color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + modifier = Modifier.padding(bottom = 4.dp) ) - ) - StyledSwitch( - checked = singleMode.value, - onCheckedChange = { - singleMode.value = it - if (it) { - // When switching to single mode, set amplification and balance - val avg = (leftAmplification.value + rightAmplification.value) / 2 - amplification.value = avg.coerceIn(0f, 1f) - val diff = rightAmplification.value - leftAmplification.value - balance.value = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) - // Update left and right - val amp = amplification.value - val bal = balance.value - leftAmplification.value = amp * (1 + bal) - rightAmplification.value = amp * (2 - bal) - } - } - ) + + Slider( + value = eqValue.floatValue, + onValueChange = { newVal -> + eqValue.floatValue = newVal + val newEQ = eq.value.copyOf() + newEQ[i] = eqValue.floatValue + eq.value = newEQ + }, + valueRange = 0f..1f, + modifier = Modifier + .fillMaxWidth(0.9f) + ) + + Text( + text = "Band ${i + 1}", + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(top = 4.dp) + ) + } } } + Spacer(modifier = Modifier.height(16.dp)) + } + } +} - if (isConnected) { - if (singleMode.value) { - // Balance Slider for Amplification - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - AccessibilitySlider( - label = "Amplification", - value = amplification.value, - onValueChange = { - amplification.value = it - val amp = it - val bal = balance.value - leftAmplification.value = amp * (1 + bal) - rightAmplification.value = amp * (2 - bal) - onSettingsChanged() - }, - valueRange = 0f..1f - ) - AccessibilitySlider( - label = "Balance", - value = balance.value, - onValueChange = { - balance.value = it - val amp = amplification.value - val bal = it - leftAmplification.value = amp * (1 + bal) - rightAmplification.value = amp * (2 - bal) - onSettingsChanged() - }, - valueRange = 0f..1f - ) - } +@Composable +fun AccessibilityToggle(text: String, mutableState: MutableState, independent: Boolean = false) { + val isDarkTheme = isSystemInDarkTheme() + var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) + val textColor = if (isDarkTheme) Color.White else Color.Black + val boxPaddings = if (independent) 2.dp else 4.dp + val cornerShape = if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) + Box ( + modifier = Modifier + .padding(vertical = boxPaddings) + .background(animatedBackgroundColor, cornerShape) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + mutableState.value = !mutableState.value } + ) + }, + ) + { + val rowHeight = if (independent) 55.dp else 50.dp + val rowPadding = if (independent) 12.dp else 4.dp + Row( + modifier = Modifier + .fillMaxWidth() + .height(rowHeight) + .padding(horizontal = rowPadding), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + modifier = Modifier.weight(1f), + fontSize = 16.sp, + color = textColor + ) + StyledSwitch( + checked = mutableState.value, + onCheckedChange = { + mutableState.value = it + }, + ) + } + } +} - // Single Bud Settings Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Bud Settings", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) +data class TransparencySettings ( + val enabled: Boolean, + val leftEQ: FloatArray, + val rightEQ: FloatArray, + val leftAmplification: Float, + val rightAmplification: Float, + val leftTone: Float, + val rightTone: Float, + val leftConversationBoost: Boolean, + val rightConversationBoost: Boolean, + val leftAmbientNoiseReduction: Float, + val rightAmbientNoiseReduction: Float, + val netAmplification: Float, + val balance: Float +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - AccessibilitySlider( - label = "Tone", - value = leftTone.value, - onValueChange = { - leftTone.value = it - rightTone.value = it - onSettingsChanged() - }, - valueRange = 0f..2f - ) + other as TransparencySettings - AccessibilitySlider( - label = "Ambient Noise Reduction", - value = leftAmbientNoiseReduction.value, - onValueChange = { - leftAmbientNoiseReduction.value = it - rightAmbientNoiseReduction.value = it - onSettingsChanged() - }, - valueRange = 0f..1f - ) + if (enabled != other.enabled) return false + if (leftAmplification != other.leftAmplification) return false + if (rightAmplification != other.rightAmplification) return false + if (leftTone != other.leftTone) return false + if (rightTone != other.rightTone) return false + if (leftConversationBoost != other.leftConversationBoost) return false + if (rightConversationBoost != other.rightConversationBoost) return false + if (leftAmbientNoiseReduction != other.leftAmbientNoiseReduction) return false + if (rightAmbientNoiseReduction != other.rightAmbientNoiseReduction) return false + if (!leftEQ.contentEquals(other.leftEQ)) return false + if (!rightEQ.contentEquals(other.rightEQ)) return false - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - "Conversation Boost", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - StyledSwitch( - checked = leftConversationBoost.value, - onCheckedChange = { - leftConversationBoost.value = it - rightConversationBoost.value = it - onSettingsChanged() - } - ) - } + return true + } - NavigationButton( - to = "eq", - name = "Equalizer Settings", - navController = navController - ) - } - } - } else { - // Left Bud Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Left Bud", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) + override fun hashCode(): Int { + var result = enabled.hashCode() + result = 31 * result + leftAmplification.hashCode() + result = 31 * result + rightAmplification.hashCode() + result = 31 * result + leftTone.hashCode() + result = 31 * result + rightTone.hashCode() + result = 31 * result + leftConversationBoost.hashCode() + result = 31 * result + rightConversationBoost.hashCode() + result = 31 * result + leftAmbientNoiseReduction.hashCode() + result = 31 * result + rightAmbientNoiseReduction.hashCode() + result = 31 * result + leftEQ.contentHashCode() + result = 31 * result + rightEQ.contentHashCode() + return result + } +} - AccessibilitySlider( - label = "Amplification", - value = leftAmplification.value, - onValueChange = { - leftAmplification.value = it - onSettingsChanged() - }, - valueRange = 0f..2f - ) +private fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings? { + val settingsData = data.copyOfRange(1, data.size) + val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) - AccessibilitySlider( - label = "Tone", - value = leftTone.value, - onValueChange = { - leftTone.value = it - onSettingsChanged() - }, - valueRange = 0f..2f - ) + val enabled = buffer.float + Log.d(TAG, "Parsed enabled: $enabled") - AccessibilitySlider( - label = "Ambient Noise Reduction", - value = leftAmbientNoiseReduction.value, - onValueChange = { - leftAmbientNoiseReduction.value = it - onSettingsChanged() - }, - valueRange = 0f..1f - ) + // Left bud + val leftEQ = FloatArray(8) + for (i in 0..7) { + leftEQ[i] = buffer.float + Log.d(TAG, "Parsed left EQ${i+1}: ${leftEQ[i]}") + } + val leftAmplification = buffer.float + Log.d(TAG, "Parsed left amplification: $leftAmplification") + val leftTone = buffer.float + Log.d(TAG, "Parsed left tone: $leftTone") + val leftConvFloat = buffer.float + val leftConversationBoost = leftConvFloat > 0.5f + Log.d(TAG, "Parsed left conversation boost: $leftConvFloat ($leftConversationBoost)") + val leftAmbientNoiseReduction = buffer.float + Log.d(TAG, "Parsed left ambient noise reduction: $leftAmbientNoiseReduction") - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - "Conversation Boost", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - StyledSwitch( - checked = leftConversationBoost.value, - onCheckedChange = { - leftConversationBoost.value = it - onSettingsChanged() - } - ) - } + val rightEQ = FloatArray(8) + for (i in 0..7) { + rightEQ[i] = buffer.float + Log.d(TAG, "Parsed right EQ${i+1}: ${rightEQ[i]}") + } - NavigationButton( - to = "eq", - name = "Equalizer Settings", - navController = navController - ) - } - } + val rightAmplification = buffer.float + Log.d(TAG, "Parsed right amplification: $rightAmplification") + val rightTone = buffer.float + Log.d(TAG, "Parsed right tone: $rightTone") + val rightConvFloat = buffer.float + val rightConversationBoost = rightConvFloat > 0.5f + Log.d(TAG, "Parsed right conversation boost: $rightConvFloat ($rightConversationBoost)") + val rightAmbientNoiseReduction = buffer.float + Log.d(TAG, "Parsed right ambient noise reduction: $rightAmbientNoiseReduction") - // Right Bud Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Right Bud", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) + Log.d(TAG, "Settings parsed successfully") - AccessibilitySlider( - label = "Amplification", - value = rightAmplification.value, - onValueChange = { - rightAmplification.value = it - onSettingsChanged() - }, - valueRange = 0f..2f - ) + val avg = (leftAmplification + rightAmplification) / 2 + val amplification = avg.coerceIn(0f, 1f) + val diff = rightAmplification - leftAmplification + val balance = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) - AccessibilitySlider( - label = "Tone", - value = rightTone.value, - onValueChange = { - rightTone.value = it - onSettingsChanged() - }, - valueRange = 0f..2f - ) + return TransparencySettings( + enabled = enabled > 0.5f, + leftEQ = leftEQ, + rightEQ = rightEQ, + leftAmplification = leftAmplification, + rightAmplification = rightAmplification, + leftTone = leftTone, + rightTone = rightTone, + leftConversationBoost = leftConversationBoost, + rightConversationBoost = rightConversationBoost, + leftAmbientNoiseReduction = leftAmbientNoiseReduction, + rightAmbientNoiseReduction = rightAmbientNoiseReduction, + netAmplification = amplification, + balance = balance + ) +} - AccessibilitySlider( - label = "Ambient Noise Reduction", - value = rightAmbientNoiseReduction.value, - onValueChange = { - rightAmbientNoiseReduction.value = it - onSettingsChanged() - }, - valueRange = 0f..1f - ) +private fun sendTransparencySettings( + attManager: ATTManager, + transparencySettings: TransparencySettings +) { + debounceJob?.cancel() + debounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) // 100 data bytes - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - "Conversation Boost", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - StyledSwitch( - checked = rightConversationBoost.value, - onCheckedChange = { - rightConversationBoost.value = it - onSettingsChanged() - } - ) - } + Log.d(TAG, + "Sending settings: $transparencySettings" + ) - NavigationButton( - to = "eq", - name = "Equalizer Settings", - navController = navController - ) - } - } - } + buffer.putFloat(if (transparencySettings.enabled) 1.0f else 0.0f) + + for (eq in transparencySettings.leftEQ) { + buffer.putFloat(eq) } + buffer.putFloat(transparencySettings.leftAmplification) + buffer.putFloat(transparencySettings.leftTone) + buffer.putFloat(if (transparencySettings.leftConversationBoost) 1.0f else 0.0f) + buffer.putFloat(transparencySettings.leftAmbientNoiseReduction) - Spacer(modifier = Modifier.height(16.dp)) + for (eq in transparencySettings.rightEQ) { + buffer.putFloat(eq) + } + buffer.putFloat(transparencySettings.rightAmplification) + buffer.putFloat(transparencySettings.rightTone) + buffer.putFloat(if (transparencySettings.rightConversationBoost) 1.0f else 0.0f) + buffer.putFloat(transparencySettings.rightAmbientNoiseReduction) + + val data = buffer.array() + attManager.write( + 0x18, + value = data + ) + } catch (e: IOException) { + e.printStackTrace() } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index c6e68ec8..99df81fb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -91,6 +91,7 @@ import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.CustomDevice import me.kavishdevar.librepods.composables.AccessibilitySettings import me.kavishdevar.librepods.composables.AudioSettings import me.kavishdevar.librepods.composables.BatteryView @@ -401,7 +402,10 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, // Only show debug when not in BLE-only mode if (!bleOnlyMode) { Spacer(modifier = Modifier.height(16.dp)) - AccessibilitySettings() + NavigationButton(to = "", "Accessibility", navController = navController, onClick = { + val intent = Intent(context, CustomDevice::class.java) + context.startActivity(intent) + }) Spacer(modifier = Modifier.height(16.dp)) NavigationButton("debug", "Debug", navController) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt deleted file mode 100644 index 5b77ebb9..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/EqualizerSettingsScreen.kt +++ /dev/null @@ -1,300 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.screens - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -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.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavController -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.AccessibilitySlider -import me.kavishdevar.librepods.services.ServiceManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun EqualizerSettingsScreen( - navController: NavController, - leftEQ: MutableState, - rightEQ: MutableState, - singleMode: MutableState, - onEQChanged: () -> Unit, - phoneMediaEQ: MutableState -) { - val isDarkTheme = isSystemInDarkTheme() - val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) - val cardBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - val textColor = if (isDarkTheme) Color.White else Color.Black - - val aacpManager = ServiceManager.getService()!!.aacpManager - - val debounceJob = remember { mutableStateOf(null) } - - Scaffold( - topBar = { - TopAppBar( - title = { - Text( - "Equalizer Settings", - style = TextStyle( - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.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( - "Accessibility", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5), - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ), - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent - ) - ) - }, - containerColor = backgroundColor - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - if (singleMode.value) { - // Single Bud EQ Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Equalizer", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - - for (i in 0..7) { - AccessibilitySlider( - label = "EQ${i + 1}", - value = leftEQ.value[i], - onValueChange = { - leftEQ.value = leftEQ.value.copyOf().apply { this[i] = it } - rightEQ.value = rightEQ.value.copyOf().apply { this[i] = it } // Sync to right - onEQChanged() - }, - valueRange = 0f..100f - ) - } - } - } - } else { - // Left Bud EQ Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Left Bud Equalizer", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - - for (i in 0..7) { - AccessibilitySlider( - label = "EQ${i + 1}", - value = leftEQ.value[i], - onValueChange = { - leftEQ.value = leftEQ.value.copyOf().apply { this[i] = it } - onEQChanged() - }, - valueRange = 0f..100f - ) - } - } - } - - // Right Bud EQ Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Right Bud Equalizer", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - - for (i in 0..7) { - AccessibilitySlider( - label = "EQ${i + 1}", - value = rightEQ.value[i], - onValueChange = { - rightEQ.value = rightEQ.value.copyOf().apply { this[i] = it } - onEQChanged() - }, - valueRange = 0f..100f - ) - } - } - } - } - - // Phone and Media EQ Card - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = cardBackgroundColor), - shape = RoundedCornerShape(14.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - Text( - "Phone and Media Equalizer", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - color = textColor, - fontFamily = androidx.compose.ui.text.font.FontFamily( - Font(R.font.sf_pro) - ) - ) - ) - - for (i in 0..7) { - AccessibilitySlider( - label = "EQ${i + 1}", - value = phoneMediaEQ.value[i], - onValueChange = { - phoneMediaEQ.value = phoneMediaEQ.value.copyOf().apply { this[i] = it } - debounceJob.value?.cancel() - debounceJob.value = CoroutineScope(Dispatchers.IO).launch { - delay(100) - aacpManager.sendPhoneMediaEQ(phoneMediaEQ.value) - } - }, - valueRange = 0f..100f - ) - } - } - } - } - } -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 71374d60..b11d1dcd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -2318,6 +2318,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList delay(200) aacpManager.sendNotificationRequest() delay(200) + aacpManager.sendSomePacketIDontKnowWhatItIs() + delay(200) aacpManager.sendRequestProximityKeys((AACPManager.Companion.ProximityKeyType.IRK.value+AACPManager.Companion.ProximityKeyType.ENC_KEY.value).toByte()) if (!handleIncomingCallOnceConnected) startHeadTracking() else handleIncomingCall() Handler(Looper.getMainLooper()).postDelayed({ diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 342db7ae..9141285e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -1107,6 +1107,19 @@ class AACPManager { return devices } + fun sendSomePacketIDontKnowWhatItIs() { + // 2900 00ff ffff ffff ffff + sendDataPacket( + byteArrayOf( + 0x29, 0x00, + 0x00, 0xFF.toByte(), + 0xFF.toByte(), 0xFF.toByte(), + 0xFF.toByte(), 0xFF.toByte(), + 0xFF.toByte(), 0xFF.toByte(), + ) + ) + } + fun disconnected() { Log.d(TAG, "Disconnected, clearing state") controlCommandStatusList.clear() diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt new file mode 100644 index 00000000..2939c333 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -0,0 +1,112 @@ +package me.kavishdevar.librepods.utils + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothSocket +import android.os.ParcelUuid +import android.util.Log +import org.lsposed.hiddenapibypass.HiddenApiBypass +import java.io.InputStream +import java.io.OutputStream + +class ATTManager(private val device: BluetoothDevice) { + companion object { + private const val TAG = "ATTManager" + + private const val OPCODE_READ_REQUEST: Byte = 0x0A + private const val OPCODE_WRITE_REQUEST: Byte = 0x12 + } + + var socket: BluetoothSocket? = null + private var input: InputStream? = null + private var output: OutputStream? = null + + @SuppressLint("MissingPermission") + fun connect() { + HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") + val uuid = ParcelUuid.fromString("00000000-0000-0000-0000-000000000000") + + socket = createBluetoothSocket(device, uuid) + socket!!.connect() + input = socket!!.inputStream + output = socket!!.outputStream + Log.d(TAG, "Connected to ATT") + } + + fun disconnect() { + try { + socket?.close() + } catch (e: Exception) { + Log.w(TAG, "Error closing socket: ${e.message}") + } + } + + fun read(handle: Int): ByteArray { + val lsb = (handle and 0xFF).toByte() + val msb = ((handle shr 8) and 0xFF).toByte() + val pdu = byteArrayOf(OPCODE_READ_REQUEST, lsb, msb) + writeRaw(pdu) + return readRaw() + } + + fun write(handle: Int, value: ByteArray) { + val lsb = (handle and 0xFF).toByte() + val msb = ((handle shr 8) and 0xFF).toByte() + val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value + writeRaw(pdu) + readRaw() // usually a Write Response (0x13) + } + + private fun writeRaw(pdu: ByteArray) { + output?.write(pdu) + output?.flush() + Log.d(TAG, "writeRaw: ${pdu.joinToString(" ") { String.format("%02X", it) }}") + } + + private fun readRaw(): ByteArray { + val inp = input ?: throw IllegalStateException("Not connected") + val buffer = ByteArray(512) + val len = inp.read(buffer) + if (len <= 0) throw IllegalStateException("No data read from ATT socket") + val data = buffer.copyOfRange(0, len) + Log.wtf(TAG, "Read ${data.size} bytes from ATT") + Log.d(TAG, "readRaw: ${data.joinToString(" ") { String.format("%02X", it) }}") + return data + } + + private fun createBluetoothSocket(device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket { + val type = 3 // L2CAP + val constructorSpecs = listOf( + arrayOf(device, type, true, true, 31, uuid), + arrayOf(device, type, 1, true, true, 31, uuid), + arrayOf(type, 1, true, true, device, 31, uuid), + arrayOf(type, true, true, device, 31, uuid) + ) + + val constructors = BluetoothSocket::class.java.declaredConstructors + Log.d("ATTManager", "BluetoothSocket has ${constructors.size} constructors:") + + constructors.forEachIndexed { index, constructor -> + val params = constructor.parameterTypes.joinToString(", ") { it.simpleName } + Log.d("ATTManager", "Constructor $index: ($params)") + } + + var lastException: Exception? = null + var attemptedConstructors = 0 + + for ((index, params) in constructorSpecs.withIndex()) { + try { + Log.d("ATTManager", "Trying constructor signature #${index + 1}") + attemptedConstructors++ + return HiddenApiBypass.newInstance(BluetoothSocket::class.java, *params) as BluetoothSocket + } catch (e: Exception) { + Log.e("ATTManager", "Constructor signature #${index + 1} failed: ${e.message}") + lastException = e + } + } + + val errorMessage = "Failed to create BluetoothSocket after trying $attemptedConstructors constructor signatures" + Log.e("ATTManager", errorMessage) + throw lastException ?: IllegalStateException(errorMessage) + } +} diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 25458ed6..a520e9de 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + LibrePods Liberate your AirPods from Apple\'s ecosystem. GATT Testing @@ -82,4 +82,7 @@ Starting media playback Your phone starts playing media Undo + You can customize Transparency mode for your AirPods Pro to help you hear what\'s around you. + AirPods Pro automatically reduce your exposure to loud environmental noises when in Transparency and Adaptive mode. + Loud Sound Reduction From 9e6d97198b5c9ab8678a06ca113405d79ff13e89 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 15 Sep 2025 11:49:00 +0530 Subject: [PATCH 09/72] android: add EQ settings for phone and media --- .../composables/LoudSoundReductionSwitch.kt | 23 +- .../screens/AccessibilitySettingsScreen.kt | 248 +++++++++++++++++- .../librepods/utils/AACPManager.kt | 63 ++++- 3 files changed, 319 insertions(+), 15 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt index 1eecf4dd..c1cec371 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures @@ -62,7 +63,27 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) { while (attManager.socket?.isConnected != true) { delay(100) } - attManager.read(0x1b) + + var parsed = false + for (attempt in 1..3) { + try { + val data = attManager.read(0x1b) + if (data.size == 2) { + loudSoundReductionEnabled = data[1].toInt() != 0 + Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}") + parsed = true + break + } else { + Log.d("LoudSoundReduction", "Read attempt $attempt returned empty data") + } + } catch (e: Exception) { + Log.w("LoudSoundReduction", "Read attempt $attempt failed: ${e.message}") + } + delay(200) + } + if (!parsed) { + Log.d("LoudSoundReduction", "Failed to read loud sound reduction state after 3 attempts") + } } LaunchedEffect(loudSoundReductionEnabled) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index fb94720e..438f5a9f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -40,7 +40,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SnackbarHost @@ -60,6 +63,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput @@ -95,6 +99,7 @@ import java.nio.ByteOrder import kotlin.io.encoding.ExperimentalEncodingApi var debounceJob: Job? = null +var phoneMediaDebounceJob: Job? = null const val TAG = "AccessibilitySettings" @SuppressLint("DefaultLocale") @@ -108,6 +113,9 @@ fun AccessibilitySettingsScreen() { val hazeState = remember { HazeState() } val snackbarHostState = remember { SnackbarHostState() } val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected")) + // get the AACP manager if available (used for EQ read/write) + val aacpManager = remember { ServiceManager.getService()?.aacpManager } + DisposableEffect(attManager) { onDispose { Log.d(TAG, "Disconnecting from ATT...") @@ -187,15 +195,15 @@ fun AccessibilitySettingsScreen() { val conversationBoostEnabled = remember { mutableStateOf(false) } val eq = remember { mutableStateOf(FloatArray(8)) } - // Flag to prevent sending default settings to device while we are loading device state + val phoneMediaEQ = remember { mutableStateOf(FloatArray(8) { 0.5f }) } + val phoneEQEnabled = remember { mutableStateOf(false) } + val mediaEQEnabled = remember { mutableStateOf(false) } + val initialLoadComplete = remember { mutableStateOf(false) } - // Ensure we actually read device properties before allowing writes. - // Try up to 3 times silently; mark success only if parse succeeds. val initialReadSucceeded = remember { mutableStateOf(false) } val initialReadAttempts = remember { mutableStateOf(0) } - // Populate a single stored representation for convenience (kept for debug/logging) val transparencySettings = remember { mutableStateOf( TransparencySettings( @@ -217,13 +225,11 @@ fun AccessibilitySettingsScreen() { } LaunchedEffect(enabled.value, amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, eq.value, initialLoadComplete.value, initialReadSucceeded.value) { - // Do not send updates until we have populated UI from the device if (!initialLoadComplete.value) { Log.d(TAG, "Initial device load not complete - skipping send") return@LaunchedEffect } - // Do not send until we've successfully read the device properties at least once. if (!initialReadSucceeded.value) { Log.d(TAG, "Initial device read not successful yet - skipping send until read succeeds") return@LaunchedEffect @@ -248,7 +254,6 @@ fun AccessibilitySettingsScreen() { sendTransparencySettings(attManager, transparencySettings.value) } - // Move initial connect / read here so we can populate the UI state variables above. LaunchedEffect(Unit) { Log.d(TAG, "Connecting to ATT...") try { @@ -256,9 +261,28 @@ fun AccessibilitySettingsScreen() { while (attManager.socket?.isConnected != true) { delay(100) } + // If we have an AACP manager, prefer its EQ data to populate EQ controls first + try { + if (aacpManager != null) { + Log.d(TAG, "Found AACPManager, reading cached EQ data") + val aacpEQ = aacpManager.eqData + if (aacpEQ.isNotEmpty()) { + eq.value = aacpEQ.copyOf() + phoneMediaEQ.value = aacpEQ.copyOf() + phoneEQEnabled.value = aacpManager.eqOnPhone + mediaEQEnabled.value = aacpManager.eqOnMedia + Log.d(TAG, "Populated EQ from AACPManager: ${aacpEQ.toList()}") + } else { + Log.d(TAG, "AACPManager EQ data empty") + } + } else { + Log.d(TAG, "No AACPManager available") + } + } catch (e: Exception) { + Log.w(TAG, "Error reading EQ from AACPManager: ${e.message}") + } var parsedSettings: TransparencySettings? = null - // Try up to 3 read attempts silently for (attempt in 1..3) { initialReadAttempts.value = attempt try { @@ -278,7 +302,6 @@ fun AccessibilitySettingsScreen() { if (parsedSettings != null) { Log.d(TAG, "Initial transparency settings: $parsedSettings") - // Populate UI states from device values without triggering a send (initialReadSucceeded is set below) enabled.value = parsedSettings.enabled amplificationSliderValue.floatValue = parsedSettings.netAmplification balanceSliderValue.floatValue = parsedSettings.balance @@ -293,11 +316,31 @@ fun AccessibilitySettingsScreen() { } catch (e: IOException) { e.printStackTrace() } finally { - // mark load complete (UI may be editable), but writes remain blocked until a successful read initialLoadComplete.value = true } } + // Debounced write for phone/media EQ using AACP manager when values/toggles change + LaunchedEffect(phoneMediaEQ.value, phoneEQEnabled.value, mediaEQEnabled.value) { + phoneMediaDebounceJob?.cancel() + phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(150) + val manager = ServiceManager.getService()?.aacpManager + if (manager == null) { + Log.w(TAG, "Cannot write EQ: AACPManager not available") + return@launch + } + try { + val phoneByte = if (phoneEQEnabled.value) 0x01.toByte() else 0x02.toByte() + val mediaByte = if (mediaEQEnabled.value) 0x01.toByte() else 0x02.toByte() + Log.d(TAG, "Sending phone/media EQ (phoneEnabled=${phoneEQEnabled.value}, mediaEnabled=${mediaEQEnabled.value})") + manager.sendPhoneMediaEQ(phoneMediaEQ.value, phoneByte, mediaByte) + } catch (e: Exception) { + Log.w(TAG, "Error sending phone/media EQ: ${e.message}") + } + } + } + AccessibilityToggle( text = "Transparency Mode", mutableState = enabled, @@ -448,7 +491,168 @@ fun AccessibilitySettingsScreen() { newEQ[i] = eqValue.floatValue eq.value = newEQ }, - valueRange = 0f..1f, + valueRange = 0f..100f, + modifier = Modifier + .fillMaxWidth(0.9f) + ) + + Text( + text = "Band ${i + 1}", + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Apply EQ to".uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(top = 0.dp, bottom = 12.dp) + ) { + val darkModeLocal = isSystemInDarkTheme() + + val phoneShape = RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) + var phoneBackgroundColor by remember { mutableStateOf(if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val phoneAnimatedBackgroundColor by animateColorAsState(targetValue = phoneBackgroundColor, animationSpec = tween(durationMillis = 500)) + + Row( + modifier = Modifier + .height(48.dp) + .fillMaxWidth() + .background(phoneAnimatedBackgroundColor, phoneShape) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + phoneBackgroundColor = if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + phoneBackgroundColor = if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + phoneEQEnabled.value = !phoneEQEnabled.value + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Phone", + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + modifier = Modifier.weight(1f) + ) + Checkbox( + checked = phoneEQEnabled.value, + onCheckedChange = { phoneEQEnabled.value = it }, + colors = CheckboxDefaults.colors().copy( + checkedCheckmarkColor = Color(0xFF007AFF), + uncheckedCheckmarkColor = Color.Transparent, + checkedBoxColor = Color.Transparent, + uncheckedBoxColor = Color.Transparent, + checkedBorderColor = Color.Transparent, + uncheckedBorderColor = Color.Transparent + ), + modifier = Modifier + .height(24.dp) + .scale(1.5f) + ) + } + + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888) + ) + + val mediaShape = RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + var mediaBackgroundColor by remember { mutableStateOf(if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val mediaAnimatedBackgroundColor by animateColorAsState(targetValue = mediaBackgroundColor, animationSpec = tween(durationMillis = 500)) + + Row( + modifier = Modifier + .height(48.dp) + .fillMaxWidth() + .background(mediaAnimatedBackgroundColor, mediaShape) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + mediaBackgroundColor = if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + mediaBackgroundColor = if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + mediaEQEnabled.value = !mediaEQEnabled.value + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Media", + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + modifier = Modifier.weight(1f) + ) + Checkbox( + checked = mediaEQEnabled.value, + onCheckedChange = { mediaEQEnabled.value = it }, + colors = CheckboxDefaults.colors().copy( + checkedCheckmarkColor = Color(0xFF007AFF), + uncheckedCheckmarkColor = Color.Transparent, + checkedBoxColor = Color.Transparent, + uncheckedBoxColor = Color.Transparent, + checkedBorderColor = Color.Transparent, + uncheckedBorderColor = Color.Transparent + ), + modifier = Modifier + .height(24.dp) + .scale(1.5f) + ) + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + for (i in 0 until 8) { + val eqPhoneValue = remember(phoneMediaEQ.value[i]) { mutableFloatStateOf(phoneMediaEQ.value[i]) } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(32.dp) + ) { + Text( + text = String.format("%.2f", eqPhoneValue.floatValue), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + + Slider( + value = eqPhoneValue.floatValue, + onValueChange = { newVal -> + eqPhoneValue.floatValue = newVal + val newEQ = phoneMediaEQ.value.copyOf() + newEQ[i] = eqPhoneValue.floatValue + phoneMediaEQ.value = newEQ + }, + valueRange = 0f..100f, modifier = Modifier .fillMaxWidth(0.9f) ) @@ -578,7 +782,6 @@ private fun parseTransparencySettingsResponse(data: ByteArray): TransparencySett val enabled = buffer.float Log.d(TAG, "Parsed enabled: $enabled") - // Left bud val leftEQ = FloatArray(8) for (i in 0..7) { leftEQ[i] = buffer.float @@ -642,7 +845,7 @@ private fun sendTransparencySettings( debounceJob = CoroutineScope(Dispatchers.IO).launch { delay(100) try { - val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) // 100 data bytes + val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) Log.d(TAG, "Sending settings: $transparencySettings" @@ -676,3 +879,22 @@ private fun sendTransparencySettings( } } } + +// Debounced send helper for phone/media EQ (if needed elsewhere) +private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPManager?, eq: FloatArray, phoneEnabled: Boolean, mediaEnabled: Boolean) { + phoneMediaDebounceJob?.cancel() + phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + if (aacpManager == null) { + Log.w(TAG, "AACPManger is null; cannot send phone/media EQ") + return@launch + } + val phoneByte = if (phoneEnabled) 0x01.toByte() else 0x02.toByte() + val mediaByte = if (mediaEnabled) 0x01.toByte() else 0x02.toByte() + aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) + } catch (e: Exception) { + Log.w(TAG, "Error in sendPhoneMediaEQ: ${e.message}") + } + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 9141285e..764e368f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -195,6 +195,15 @@ class AACPManager { var audioSource: AudioSource? = null private set + var eqData = FloatArray(8) { 0.0f } + private set + + var eqOnPhone: Boolean = false + private set + + var eqOnMedia: Boolean = false + private set + fun getControlCommandStatus(identifier: ControlCommandIdentifiers): ControlCommandStatus? { return controlCommandStatusList.find { it.identifier == identifier } } @@ -513,12 +522,60 @@ class AACPManager { } } + Opcodes.EQ_DATA -> { + if (packet.size != 140) { + Log.w( + TAG, + "Received EQ_DATA packet of unexpected size: ${packet.size}, expected 140" + ) + return + } + if (packet[6] != 0x84.toByte()) { + Log.w( + TAG, + "Received EQ_DATA packet with unexpected identifier: ${packet[6].toHexString()}, expected 0x84" + ) + return + } + // first 4 bytes AACP header, next two bytes opcode, next to bytes identifer + eqOnMedia = (packet[10] == 0x01.toByte()) + eqOnPhone = (packet[11] == 0x01.toByte()) + // there are 4 eqs. i am not sure what those are for, maybe all 4 listening modes, or maybe phone+media left+right, but then there shouldn't be another flag for phone/media enabled. just directly the EQ... weird. + // the EQs are little endian floats + val eq1 = ByteBuffer.wrap(packet, 12, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() + val eq2 = ByteBuffer.wrap(packet, 44, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() + val eq3 = ByteBuffer.wrap(packet, 76, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() + val eq4 = ByteBuffer.wrap(packet, 108, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() + + // for now, just take the first EQ + eqData = FloatArray(8) { i -> eq1.get(i) } + Log.d(TAG, "EQ Data set to: ${eqData.toList()}, eqOnPhone: $eqOnPhone, eqOnMedia: $eqOnMedia") + } + else -> { callback?.onUnknownPacketReceived(packet) } } } + fun sendEqualizerData(eqData: FloatArray, eqOnPhone: Boolean, eqOnMedia: Boolean): Boolean { + if (eqData.size != 8) { + throw IllegalArgumentException("EQ data must be 8 floats") + } + return sendDataPacket(createEqualizerDataPacket(eqData, eqOnPhone, eqOnMedia)) + } + + fun createEqualizerDataPacket(eqData: FloatArray, eqOnPhone: Boolean, eqOnMedia: Boolean): ByteArray { + val opcode = byteArrayOf(Opcodes.EQ_DATA, 0x00) + val identifier = byteArrayOf(0x84.toByte(), 0x00) + val something = byteArrayOf(0x02, 0x02) + val phoneFlag = if (eqOnPhone) 0x01.toByte() else 0x00.toByte() + val mediaFlag = if (eqOnMedia) 0x01.toByte() else 0x00.toByte() + val buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN) + eqData.forEach { buffer.putFloat(it) } + return opcode + identifier + something + byteArrayOf(phoneFlag, mediaFlag) + buffer.array() + buffer.array() + buffer.array() + buffer.array() + } + fun sendNotificationRequest(): Boolean { return sendDataPacket(createRequestNotificationPacket()) } @@ -777,6 +834,10 @@ class AACPManager { } Log.d(TAG, "SELFMAC: $selfMacAddress") val targetMac = connectedDevices.find { it.mac != selfMacAddress }?.mac + if (targetMac == null) { + Log.w(TAG, "Cannot send Media Information packet: No connected device found") + return false + } Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}") return sendDataPacket( createMediaInformationPacket( @@ -1108,7 +1169,7 @@ class AACPManager { } fun sendSomePacketIDontKnowWhatItIs() { - // 2900 00ff ffff ffff ffff + // 2900 00ff ffff ffff ffff -- enables setting EQ sendDataPacket( byteArrayOf( 0x29, 0x00, From 5bef8c384e4e48cf1800d139e891f58e401976c1 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 15 Sep 2025 19:59:43 +0530 Subject: [PATCH 10/72] android: add toggle for DID hook --- .../librepods/screens/AppSettingsScreen.kt | 100 +++++++++++++++++- .../librepods/utils/RadareOffsetFinder.kt | 93 +++++++++++++++- 2 files changed, 191 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index d49021ea..bbe332f5 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.screens import android.content.Context import android.widget.Toast +import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -99,6 +100,9 @@ import me.kavishdevar.librepods.utils.RadareOffsetFinder import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.roundToInt +import androidx.compose.runtime.rememberCoroutineScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class, ExperimentalEncodingApi::class) @Composable @@ -107,6 +111,7 @@ fun AppSettingsScreen(navController: NavController) { val name = remember { mutableStateOf(sharedPreferences.getString("name", "") ?: "") } val isDarkTheme = isSystemInDarkTheme() val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() val scrollState = rememberScrollState() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() val hazeState = remember { HazeState() } @@ -200,6 +205,11 @@ fun AppSettingsScreen(navController: NavController) { return hexPattern.matches(input) } + var isProcessingSdp by remember { mutableStateOf(false) } + var actAsAppleDevice by remember { mutableStateOf(false) } + + BackHandler(enabled = isProcessingSdp) {} + Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -233,8 +243,11 @@ fun AppSettingsScreen(navController: NavController) { navigationIcon = { TextButton( onClick = { - navController.popBackStack() + if (!isProcessingSdp) { + navController.popBackStack() + } }, + enabled = !isProcessingSdp, shape = RoundedCornerShape(8.dp), modifier = Modifier.width(180.dp) ) { @@ -1189,6 +1202,91 @@ fun AppSettingsScreen(navController: NavController) { ) } } + + LaunchedEffect(Unit) { + actAsAppleDevice = RadareOffsetFinder.isSdpOffsetAvailable() + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable( + enabled = !isProcessingSdp, + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + if (!isProcessingSdp) { + val newValue = !actAsAppleDevice + actAsAppleDevice = newValue + isProcessingSdp = true + coroutineScope.launch { + if (newValue) { + val radareOffsetFinder = RadareOffsetFinder(context) + val success = radareOffsetFinder.findSdpOffset() ?: false + if (success) { + Toast.makeText(context, "Found offset please restart the Bluetooth process", Toast.LENGTH_LONG).show() + } + } else { + RadareOffsetFinder.clearSdpOffset() + } + isProcessingSdp = false + } + } + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(vertical = 8.dp) + .padding(end = 4.dp) + ) { + Text( + text = "Act as an Apple device", + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Enables multi-device connectivity and Accessibility features like customizing transparency mode (amplification, tone, ambient noise reduction, conversation boost, and EQ)", + fontSize = 14.sp, + color = textColor.copy(0.6f), + lineHeight = 16.sp, + ) + if (actAsAppleDevice) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Might be unstable!! A maximum of two devices can be connected to your AirPods. If you are using with an Apple device like an iPad or Mac, then please connect that device first and then your Android.", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.error, + lineHeight = 14.sp, + ) + } + } + + StyledSwitch( + checked = actAsAppleDevice, + onCheckedChange = { + if (!isProcessingSdp) { + actAsAppleDevice = it + isProcessingSdp = true + coroutineScope.launch { + if (it) { + val radareOffsetFinder = RadareOffsetFinder(context) + val success = radareOffsetFinder.findSdpOffset() ?: false + if (success) { + Toast.makeText(context, "Found offset please restart the Bluetooth process", Toast.LENGTH_LONG).show() + } + } else { + RadareOffsetFinder.clearSdpOffset() + } + isProcessingSdp = false + } + } + }, + enabled = !isProcessingSdp + ) + } } Spacer(modifier = Modifier.height(16.dp)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt index e9a8c0f9..6cc06b52 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt @@ -78,7 +78,7 @@ class RadareOffsetFinder(context: Context) { "setprop $HOOK_OFFSET_PROP '' && " + "setprop $CFG_REQ_OFFSET_PROP '' && " + "setprop $CSM_CONFIG_OFFSET_PROP '' && " + - "setprop $PEER_INFO_REQ_OFFSET_PROP ''" + + "setprop $PEER_INFO_REQ_OFFSET_PROP '' &&" + "setprop $SDP_OFFSET_PROP ''" )) val exitCode = process.waitFor() @@ -94,6 +94,44 @@ class RadareOffsetFinder(context: Context) { } return false } + + fun clearSdpOffset(): Boolean { + try { + val process = Runtime.getRuntime().exec(arrayOf( + "su", "-c", "setprop $SDP_OFFSET_PROP ''" + )) + val exitCode = process.waitFor() + + if (exitCode == 0) { + Log.d(TAG, "Successfully cleared SDP offset property") + return true + } else { + Log.e(TAG, "Failed to clear SDP offset property, exit code: $exitCode") + } + } catch (e: Exception) { + Log.e(TAG, "Error clearing SDP offset property", e) + } + return false + } + + fun isSdpOffsetAvailable(): Boolean { + try { + val process = Runtime.getRuntime().exec(arrayOf("getprop", SDP_OFFSET_PROP)) + val reader = BufferedReader(InputStreamReader(process.inputStream)) + val propValue = reader.readLine() + process.waitFor() + + if (propValue != null && propValue.isNotEmpty()) { + Log.d(TAG, "SDP offset property exists: $propValue") + return true + } + } catch (e: Exception) { + Log.e(TAG, "Error checking if SDP offset property exists", e) + } + + Log.d(TAG, "No SDP offset available") + return false + } } private val radare2TarballFile = File(context.cacheDir, "radare2.tar.gz") @@ -661,4 +699,57 @@ class RadareOffsetFinder(context: Context) { Log.e(TAG, "Failed to cleanup extracted files", e) } } + + suspend fun findSdpOffset(): Boolean = withContext(Dispatchers.IO) { + try { + _progressState.value = ProgressState.Downloading + if (!downloadRadare2TarballIfNeeded()) { + _progressState.value = ProgressState.Error("Failed to download radare2 tarball") + Log.e(TAG, "Failed to download radare2 tarball") + return@withContext false + } + + _progressState.value = ProgressState.Extracting + if (!extractRadare2Tarball()) { + _progressState.value = ProgressState.Error("Failed to extract radare2 tarball") + Log.e(TAG, "Failed to extract radare2 tarball") + return@withContext false + } + + _progressState.value = ProgressState.MakingExecutable + if (!makeExecutable()) { + _progressState.value = ProgressState.Error("Failed to make binaries executable") + Log.e(TAG, "Failed to make binaries executable") + return@withContext false + } + + _progressState.value = ProgressState.FindingOffset + val libraryPath = findBluetoothLibraryPath() + if (libraryPath == null) { + _progressState.value = ProgressState.Error("Failed to find Bluetooth library") + Log.e(TAG, "Failed to find Bluetooth library") + return@withContext false + } + + @Suppress("LocalVariableName") val currentLD_LIBRARY_PATH = ProcessBuilder().command("su", "-c", "printenv LD_LIBRARY_PATH").start().inputStream.bufferedReader().readText().trim() + val currentPATH = ProcessBuilder().command("su", "-c", "printenv PATH").start().inputStream.bufferedReader().readText().trim() + val envSetup = """ + export LD_LIBRARY_PATH="$RADARE2_LIB_PATH:$currentLD_LIBRARY_PATH" + export PATH="$BUSYBOX_PATH:$RADARE2_BIN_PATH:$currentPATH" + """.trimIndent() + + findAndSaveSdpOffset(libraryPath, envSetup) + + _progressState.value = ProgressState.Cleaning + cleanupExtractedFiles() + + _progressState.value = ProgressState.Success(0L) + return@withContext true + + } catch (e: Exception) { + _progressState.value = ProgressState.Error("Error: ${e.message}") + Log.e(TAG, "Error in findSdpOffset", e) + return@withContext false + } + } } From 792629acb9e722574d66db02f542f3d2e97f7715 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 15 Sep 2025 20:01:46 +0530 Subject: [PATCH 11/72] docs: add 'has ownership' control cmd --- docs/control_commands.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/control_commands.md b/docs/control_commands.md index bd5bc49b..32482229 100644 --- a/docs/control_commands.md +++ b/docs/control_commands.md @@ -21,6 +21,7 @@ These commands |--------------|---------------------|--------| | 0x01 | Mic Mode | Single value (1 byte) | | 0x05 | Button Send Mode | Single value (1 byte) | +| 0x06 | Has ownership | Single value (1 byte): `0x01` = own, `0x00` = doesn't own | | 0x12 | VoiceTrigger for Siri | Single Value (1 byte): `0x01` = enabled, `0x01` = disabled | | 0x14 | SingleClickMode | Single value (1 byte) | | 0x15 | DoubleClickMode | Single value (1 byte) | From 93328d281ee0e852e6bb6877d75a92d93bd9fcf0 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Thu, 18 Sep 2025 13:56:06 +0530 Subject: [PATCH 12/72] android: fix balance NaN error when amplification L/R is both zero --- .../librepods/screens/AccessibilitySettingsScreen.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 438f5a9f..4b65d7bd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -818,7 +818,11 @@ private fun parseTransparencySettingsResponse(data: ByteArray): TransparencySett val avg = (leftAmplification + rightAmplification) / 2 val amplification = avg.coerceIn(0f, 1f) val diff = rightAmplification - leftAmplification - val balance = (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) + val balance = if (avg == 0f) { + 0.5f + } else { + (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) + } return TransparencySettings( enabled = enabled > 0.5f, From 65d074efe01b155a8d14e831564c453bcd7cd3d6 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 13:10:59 +0530 Subject: [PATCH 13/72] android: bring back some accessiblity settings and add listeners for all config --- .../composables/AccessibilitySettings.kt | 218 ---------------- .../composables/AdaptiveStrengthSlider.kt | 35 ++- .../ConversationalAwarenessSwitch.kt | 26 ++ .../composables/IndependentToggle.kt | 23 ++ .../composables/LoudSoundReductionSwitch.kt | 25 ++ .../composables/SinglePodANCSwitch.kt | 18 ++ .../librepods/composables/ToneVolumeSlider.kt | 31 ++- .../composables/VolumeControlSwitch.kt | 18 ++ .../screens/AccessibilitySettingsScreen.kt | 236 ++++++++++++++++-- .../screens/AirPodsSettingsScreen.kt | 1 - .../librepods/utils/AACPManager.kt | 17 +- .../kavishdevar/librepods/utils/ATTManager.kt | 81 +++++- 12 files changed, 476 insertions(+), 253 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt index ac870f23..e69de29b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt @@ -1,218 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -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.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -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 me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun AccessibilitySettings() { - val isDarkTheme = isSystemInDarkTheme() - val textColor = if (isDarkTheme) Color.White else Color.Black - val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - val service = ServiceManager.getService()!! - Text( - text = stringResource(R.string.accessibility).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(top = 2.dp) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - ) { - Text( - text = stringResource(R.string.tone_volume), - modifier = Modifier - .padding(end = 8.dp, bottom = 2.dp, start = 2.dp) - .fillMaxWidth(), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor - ) - ) - - ToneVolumeSlider() - } - - val pressSpeedOptions = mapOf( - 0.toByte() to "Default", - 1.toByte() to "Slower", - 2.toByte() to "Slowest" - ) - val selectedPressSpeedValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var selectedPressSpeed by remember { mutableStateOf(pressSpeedOptions[selectedPressSpeedValue] ?: pressSpeedOptions[0]) } - DropdownMenuComponent( - label = "Press Speed", - options = pressSpeedOptions.values.toList(), - selectedOption = selectedPressSpeed.toString(), - onOptionSelected = { newValue -> - selectedPressSpeed = newValue - service.aacpManager.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value, - value = pressSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 0.toByte() - ) - }, - textColor = textColor - ) - - val pressAndHoldDurationOptions = mapOf( - 0.toByte() to "Default", - 1.toByte() to "Slower", - 2.toByte() to "Slowest" - ) - - val selectedPressAndHoldDurationValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var selectedPressAndHoldDuration by remember { mutableStateOf(pressAndHoldDurationOptions[selectedPressAndHoldDurationValue] ?: pressAndHoldDurationOptions[0]) } - DropdownMenuComponent( - label = "Press and Hold Duration", - options = pressAndHoldDurationOptions.values.toList(), - selectedOption = selectedPressAndHoldDuration.toString(), - onOptionSelected = { newValue -> - selectedPressAndHoldDuration = newValue - service.aacpManager.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value, - value = pressAndHoldDurationOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 0.toByte() - ) - }, - textColor = textColor - ) - - val volumeSwipeSpeedOptions = mapOf( - 1.toByte() to "Default", - 2.toByte() to "Longer", - 3.toByte() to "Longest" - ) - val selectedVolumeSwipeSpeedValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var selectedVolumeSwipeSpeed by remember { mutableStateOf(volumeSwipeSpeedOptions[selectedVolumeSwipeSpeedValue] ?: volumeSwipeSpeedOptions[1]) } - DropdownMenuComponent( - label = "Volume Swipe Speed", - options = volumeSwipeSpeedOptions.values.toList(), - selectedOption = selectedVolumeSwipeSpeed.toString(), - onOptionSelected = { newValue -> - selectedVolumeSwipeSpeed = newValue - service.aacpManager.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value, - value = volumeSwipeSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 1.toByte() - ) - }, - textColor = textColor - ) - } -} - -@Composable -fun DropdownMenuComponent( - label: String, - options: List, - selectedOption: String, - onOptionSelected: (String) -> Unit, - textColor: Color -) { - var expanded by remember { mutableStateOf(false) } - - Column ( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp) - ) { - Text( - text = label, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor - ) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .clickable { expanded = true } - .padding(8.dp) - ) { - Text( - text = selectedOption, - modifier = Modifier.padding(16.dp), - color = textColor - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - options.forEach { option -> - DropdownMenuItem( - onClick = { - onOptionSelected(option) - expanded = false - }, - text = { Text(text = option) } - ) - } - } - } -} - -@Preview -@Composable -fun AccessibilitySettingsPreview() { - AccessibilitySettings() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt index ad6dc8d3..e60bea9e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt @@ -38,6 +38,7 @@ import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -66,6 +67,31 @@ fun AdaptiveStrengthSlider() { sliderValueFromAACP?.toFloat()?.let { sliderValue.floatValue = (100 - it) } } + val listener = remember { + object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH.value) { + controlCommand.value.takeIf { it.isNotEmpty() }?.get(0)?.toFloat()?.let { + sliderValue.floatValue = (100 - it) + } + } + } + } + } + + DisposableEffect(Unit) { + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH, + listener + ) + onDispose { + service.aacpManager.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH, + listener + ) + } + } + val isDarkTheme = isSystemInDarkTheme() val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFFD9D9D9) @@ -81,11 +107,11 @@ fun AdaptiveStrengthSlider() { Slider( value = sliderValue.floatValue, onValueChange = { - sliderValue.floatValue = it + sliderValue.floatValue = snapIfClose(it, listOf(0f, 50f, 100f)) }, valueRange = 0f..100f, onValueChangeFinished = { - sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat() + sliderValue.floatValue = snapIfClose(sliderValue.floatValue.roundToInt().toFloat(), listOf(0f, 50f, 100f)) service.aacpManager.sendControlCommand( identifier = AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH.value, value = (100 - sliderValue.floatValue).toInt() @@ -156,3 +182,8 @@ fun AdaptiveStrengthSlider() { fun AdaptiveStrengthSliderPreview() { AdaptiveStrengthSlider() } + +private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { + val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value + return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt index 668bdad7..46fa6f61 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt @@ -34,7 +34,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -71,6 +73,30 @@ fun ConversationalAwarenessSwitch() { ) } + val conversationalAwarenessListener = object: AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + conversationalAwarenessEnabled = newValue == 1.toByte() + } + } + } + + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG, + conversationalAwarenessListener + ) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG, + conversationalAwarenessListener + ) + } + } + val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt index 2cb6e460..f40364bd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt @@ -34,6 +34,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -51,6 +52,7 @@ import me.kavishdevar.librepods.services.AirPodsService import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi import androidx.core.content.edit +import android.util.Log @Composable fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null) { @@ -86,6 +88,27 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam LaunchedEffect(sharedPreferences) { checked = sharedPreferences.getBoolean(snakeCasedName, true) } + + if (controlCommandIdentifier != null) { + val listener = remember { + object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == controlCommandIdentifier.value) { + Log.d("IndependentToggle", "Received control command for $name: ${controlCommand.value}") + checked = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) == 1.toByte() + } + } + } + } + LaunchedEffect(Unit) { + service?.aacpManager?.registerControlCommandListener(controlCommandIdentifier, listener) + } + DisposableEffect(Unit) { + onDispose { + service?.aacpManager?.unregisterControlCommandListener(controlCommandIdentifier, listener) + } + } + } Box ( modifier = Modifier .padding(vertical = 8.dp) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt index c1cec371..1d0ad9d6 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -63,6 +64,7 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) { while (attManager.socket?.isConnected != true) { delay(100) } + attManager.enableNotifications(0x1b) var parsed = false for (attempt in 1..3) { @@ -91,6 +93,29 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) { attManager.write(0x1b, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0)) } + val loudSoundListener = remember { + object : (ByteArray) -> Unit { + override fun invoke(value: ByteArray) { + if (value.isNotEmpty()) { + loudSoundReductionEnabled = value[0].toInt() != 0 + Log.d("LoudSoundReduction", "Updated from notification: enabled=$loudSoundReductionEnabled") + } else { + Log.w("LoudSoundReduction", "Empty value in notification") + } + } + } + } + + LaunchedEffect(Unit) { + attManager.registerListener(0x1b, loudSoundListener) + } + + DisposableEffect(Unit) { + onDispose { + attManager.unregisterListener(0x1b, loudSoundListener) + } + } + val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt index 370be0db..4818bf8c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt @@ -34,6 +34,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -60,6 +62,22 @@ fun SinglePodANCSwitch() { singleANCEnabledValue == 1.toByte() ) } + val listener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + singleANCEnabled = newValue == 1.toByte() + } + } + } + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, listener) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, listener) + } + } fun updateSingleEnabled(enabled: Boolean) { singleANCEnabled = enabled diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt index c9db3614..07546ab2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt @@ -37,6 +37,8 @@ import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -66,6 +68,24 @@ fun ToneVolumeSlider() { val sliderValue = remember { mutableFloatStateOf( sliderValueFromAACP?.toFloat() ?: -1f ) } + val listener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0)?.toFloat() + if (newValue != null) { + sliderValue.floatValue = newValue + } + } + } + } + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME, listener) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME, listener) + } + } Log.d("ToneVolumeSlider", "Slider value: ${sliderValue.floatValue}") val isDarkTheme = isSystemInDarkTheme() @@ -94,11 +114,11 @@ fun ToneVolumeSlider() { Slider( value = sliderValue.floatValue, onValueChange = { - sliderValue.floatValue = it + sliderValue.floatValue = snapIfClose(it, listOf(100f)) }, - valueRange = 0f..100f, + valueRange = 0f..125f, onValueChangeFinished = { - sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat() + sliderValue.floatValue = snapIfClose(sliderValue.floatValue.roundToInt().toFloat(), listOf(100f)) service.aacpManager.sendControlCommand( identifier = AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME.value, value = byteArrayOf(sliderValue.floatValue.toInt().toByte(), @@ -163,3 +183,8 @@ fun ToneVolumeSlider() { fun ToneVolumeSliderPreview() { ToneVolumeSlider() } + +private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { + val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value + return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt index 41bc9cc5..1c8b6229 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt @@ -34,6 +34,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -60,6 +62,22 @@ fun VolumeControlSwitch() { volumeControlEnabledValue == 1.toByte() ) } + val listener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + volumeControlEnabled = newValue == 1.toByte() + } + } + } + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE, listener) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE, listener) + } + } fun updateVolumeControlEnabled(enabled: Boolean) { volumeControlEnabled = enabled service.aacpManager.sendControlCommand( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 4b65d7bd..73693f4f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -23,6 +23,7 @@ import android.util.Log import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -42,6 +43,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold @@ -67,6 +70,7 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font @@ -93,6 +97,7 @@ import me.kavishdevar.librepods.composables.ToneVolumeSlider import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.ATTManager +import me.kavishdevar.librepods.utils.AACPManager import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -224,6 +229,98 @@ fun AccessibilitySettingsScreen() { ) } + val transparencyListener = remember { + object : (ByteArray) -> Unit { + override fun invoke(value: ByteArray) { + val parsed = parseTransparencySettingsResponse(value) + if (parsed != null) { + enabled.value = parsed.enabled + amplificationSliderValue.floatValue = parsed.netAmplification + balanceSliderValue.floatValue = parsed.balance + toneSliderValue.floatValue = parsed.leftTone + ambientNoiseReductionSliderValue.floatValue = parsed.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsed.leftConversationBoost + eq.value = parsed.leftEQ.copyOf() + Log.d(TAG, "Updated transparency settings from notification") + } else { + Log.w(TAG, "Failed to parse transparency settings from notification") + } + } + } + } + + val pressSpeedOptions = mapOf( + 0.toByte() to "Default", + 1.toByte() to "Slower", + 2.toByte() to "Slowest" + ) + val selectedPressSpeedValue = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) + var selectedPressSpeed by remember { mutableStateOf(pressSpeedOptions[selectedPressSpeedValue] ?: pressSpeedOptions[0]) } + val selectedPressSpeedListener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + selectedPressSpeed = pressSpeedOptions[newValue] ?: pressSpeedOptions[0] + } + } + } + LaunchedEffect(Unit) { + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL, selectedPressSpeedListener) + } + DisposableEffect(Unit) { + onDispose { + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL, selectedPressSpeedListener) + } + } + + val pressAndHoldDurationOptions = mapOf( + 0.toByte() to "Default", + 1.toByte() to "Slower", + 2.toByte() to "Slowest" + ) + val selectedPressAndHoldDurationValue = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) + var selectedPressAndHoldDuration by remember { mutableStateOf(pressAndHoldDurationOptions[selectedPressAndHoldDurationValue] ?: pressAndHoldDurationOptions[0]) } + val selectedPressAndHoldDurationListener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + selectedPressAndHoldDuration = pressAndHoldDurationOptions[newValue] ?: pressAndHoldDurationOptions[0] + } + } + } + LaunchedEffect(Unit) { + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL, selectedPressAndHoldDurationListener) + } + DisposableEffect(Unit) { + onDispose { + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL, selectedPressAndHoldDurationListener) + } + } + + val volumeSwipeSpeedOptions = mapOf( + 1.toByte() to "Default", + 2.toByte() to "Longer", + 3.toByte() to "Longest" + ) + val selectedVolumeSwipeSpeedValue = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) + var selectedVolumeSwipeSpeed by remember { mutableStateOf(volumeSwipeSpeedOptions[selectedVolumeSwipeSpeedValue] ?: volumeSwipeSpeedOptions[1]) } + val selectedVolumeSwipeSpeedListener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + selectedVolumeSwipeSpeed = volumeSwipeSpeedOptions[newValue] ?: volumeSwipeSpeedOptions[1] + } + } + } + LaunchedEffect(Unit) { + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL, selectedVolumeSwipeSpeedListener) + } + DisposableEffect(Unit) { + onDispose { + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL, selectedVolumeSwipeSpeedListener) + } + } + LaunchedEffect(enabled.value, amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, eq.value, initialLoadComplete.value, initialReadSucceeded.value) { if (!initialLoadComplete.value) { Log.d(TAG, "Initial device load not complete - skipping send") @@ -239,8 +336,8 @@ fun AccessibilitySettingsScreen() { enabled = enabled.value, leftEQ = eq.value, rightEQ = eq.value, - leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, - rightAmplification = amplificationSliderValue.floatValue + (balanceSliderValue.floatValue - 0.5f) * amplificationSliderValue.floatValue * 2, + leftAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue < 0) -balanceSliderValue.floatValue else 0f, + rightAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue > 0) balanceSliderValue.floatValue else 0f, leftTone = toneSliderValue.floatValue, rightTone = toneSliderValue.floatValue, leftConversationBoost = conversationBoostEnabled.value, @@ -254,6 +351,12 @@ fun AccessibilitySettingsScreen() { sendTransparencySettings(attManager, transparencySettings.value) } + DisposableEffect(Unit) { + onDispose { + attManager.unregisterListener(0x18, transparencyListener) + } + } + LaunchedEffect(Unit) { Log.d(TAG, "Connecting to ATT...") try { @@ -261,6 +364,10 @@ fun AccessibilitySettingsScreen() { while (attManager.socket?.isConnected != true) { delay(100) } + + attManager.enableNotifications(0x18) + attManager.registerListener(0x18, transparencyListener) + // If we have an AACP manager, prefer its EQ data to populate EQ controls first try { if (aacpManager != null) { @@ -375,26 +482,26 @@ fun AccessibilitySettingsScreen() { ) { AccessibilitySlider( label = "Amplification", - valueRange = 0f..1f, + valueRange = -1f..1f, value = amplificationSliderValue.floatValue, onValueChange = { - amplificationSliderValue.floatValue = it + amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) }, ) AccessibilitySlider( label = "Balance", - valueRange = 0f..1f, + valueRange = -1f..1f, value = balanceSliderValue.floatValue, onValueChange = { - balanceSliderValue.floatValue = it + balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) }, ) AccessibilitySlider( label = "Tone", - valueRange = 0f..1f, + valueRange = -1f..1f, value = toneSliderValue.floatValue, onValueChange = { - toneSliderValue.floatValue = it + toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) }, ) AccessibilitySlider( @@ -402,7 +509,7 @@ fun AccessibilitySettingsScreen() { valueRange = 0f..1f, value = ambientNoiseReductionSliderValue.floatValue, onValueChange = { - ambientNoiseReductionSliderValue.floatValue = it + ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) }, ) AccessibilityToggle( @@ -445,6 +552,46 @@ fun AccessibilitySettingsScreen() { SinglePodANCSwitch() VolumeControlSwitch() LoudSoundReductionSwitch(attManager) + + DropdownMenuComponent( + label = "Press Speed", + options = pressSpeedOptions.values.toList(), + selectedOption = selectedPressSpeed.toString(), + onOptionSelected = { newValue -> + selectedPressSpeed = newValue + aacpManager?.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value, + value = pressSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 0.toByte() + ) + }, + textColor = textColor + ) + DropdownMenuComponent( + label = "Press and Hold Duration", + options = pressAndHoldDurationOptions.values.toList(), + selectedOption = selectedPressAndHoldDuration.toString(), + onOptionSelected = { newValue -> + selectedPressAndHoldDuration = newValue + aacpManager?.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value, + value = pressAndHoldDurationOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 0.toByte() + ) + }, + textColor = textColor + ) + DropdownMenuComponent( + label = "Volume Swipe Speed", + options = volumeSwipeSpeedOptions.values.toList(), + selectedOption = selectedVolumeSwipeSpeed.toString(), + onOptionSelected = { newValue -> + selectedVolumeSwipeSpeed = newValue + aacpManager?.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value, + value = volumeSwipeSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 1.toByte() + ) + }, + textColor = textColor + ) } Spacer(modifier = Modifier.height(2.dp)) @@ -515,13 +662,13 @@ fun AccessibilitySettingsScreen() { color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(8.dp, bottom = 0.dp) ) Column( modifier = Modifier .fillMaxWidth() .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(top = 0.dp, bottom = 12.dp) + .padding(vertical = 0.dp) ) { val darkModeLocal = isSystemInDarkTheme() @@ -666,7 +813,6 @@ fun AccessibilitySettingsScreen() { } } } - Spacer(modifier = Modifier.height(16.dp)) } } } @@ -816,13 +962,9 @@ private fun parseTransparencySettingsResponse(data: ByteArray): TransparencySett Log.d(TAG, "Settings parsed successfully") val avg = (leftAmplification + rightAmplification) / 2 - val amplification = avg.coerceIn(0f, 1f) + val amplification = avg.coerceIn(-1f, 1f) val diff = rightAmplification - leftAmplification - val balance = if (avg == 0f) { - 0.5f - } else { - (0.5f + diff / (2 * avg)).coerceIn(0f, 1f) - } + val balance = diff.coerceIn(-1f, 1f) return TransparencySettings( enabled = enabled > 0.5f, @@ -902,3 +1044,61 @@ private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPMan } } } + +private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { + val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value + return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value +} + +@Composable +fun DropdownMenuComponent( + label: String, + options: List, + selectedOption: String, + onOptionSelected: (String) -> Unit, + textColor: Color +) { + var expanded by remember { mutableStateOf(false) } + + Column ( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + ) { + Text( + text = label, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = textColor + ) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .clickable { expanded = true } + .padding(8.dp) + ) { + Text( + text = selectedOption, + modifier = Modifier.padding(16.dp), + ) + } + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + options.forEach { option -> + DropdownMenuItem( + onClick = { + onOptionSelected(option) + expanded = false + }, + text = { Text(text = option) } + ) + } + } + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 99df81fb..545f6fb0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -92,7 +92,6 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.CustomDevice -import me.kavishdevar.librepods.composables.AccessibilitySettings import me.kavishdevar.librepods.composables.AudioSettings import me.kavishdevar.librepods.composables.BatteryView import me.kavishdevar.librepods.composables.IndependentToggle diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 764e368f..f704c9a9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -272,6 +272,13 @@ class AACPManager { controlCommandListeners.getOrPut(identifier) { mutableListOf() }.add(callback) } + fun unregisterControlCommandListener( + identifier: ControlCommandIdentifiers, + callback: ControlCommandListener + ) { + controlCommandListeners[identifier]?.remove(callback) + } + private var callback: PacketCallback? = null fun setPacketCallback(callback: PacketCallback) { @@ -558,13 +565,6 @@ class AACPManager { } } - fun sendEqualizerData(eqData: FloatArray, eqOnPhone: Boolean, eqOnMedia: Boolean): Boolean { - if (eqData.size != 8) { - throw IllegalArgumentException("EQ data must be 8 floats") - } - return sendDataPacket(createEqualizerDataPacket(eqData, eqOnPhone, eqOnMedia)) - } - fun createEqualizerDataPacket(eqData: FloatArray, eqOnPhone: Boolean, eqOnMedia: Boolean): ByteArray { val opcode = byteArrayOf(Opcodes.EQ_DATA, 0x00) val identifier = byteArrayOf(0x84.toByte(), 0x00) @@ -1120,6 +1120,9 @@ class AACPManager { val payload = buffer.array() val packet = header + payload sendPacket(packet) + this.eqData = eq.copyOf() + this.eqOnPhone = phone == 0x01.toByte() + this.eqOnMedia = media == 0x01.toByte() } fun parseAudioSourceResponse(data: ByteArray): Pair { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index 2939c333..72857bbc 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -5,9 +5,14 @@ import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothSocket import android.os.ParcelUuid import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.lsposed.hiddenapibypass.HiddenApiBypass import java.io.InputStream import java.io.OutputStream +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit class ATTManager(private val device: BluetoothDevice) { companion object { @@ -15,11 +20,17 @@ class ATTManager(private val device: BluetoothDevice) { private const val OPCODE_READ_REQUEST: Byte = 0x0A private const val OPCODE_WRITE_REQUEST: Byte = 0x12 + private const val OPCODE_HANDLE_VALUE_NTF: Byte = 0x1B } var socket: BluetoothSocket? = null private var input: InputStream? = null private var output: OutputStream? = null + private val listeners = mutableMapOf Unit>>() + private var notificationJob: kotlinx.coroutines.Job? = null + + // queue for non-notification PDUs (responses to requests) + private val responses = LinkedBlockingQueue() @SuppressLint("MissingPermission") fun connect() { @@ -31,22 +42,63 @@ class ATTManager(private val device: BluetoothDevice) { input = socket!!.inputStream output = socket!!.outputStream Log.d(TAG, "Connected to ATT") + + notificationJob = CoroutineScope(Dispatchers.IO).launch { + while (socket?.isConnected == true) { + try { + val pdu = readPDU() + if (pdu.isNotEmpty() && pdu[0] == OPCODE_HANDLE_VALUE_NTF) { + // notification -> dispatch to listeners + val handle = (pdu[1].toInt() and 0xFF) or ((pdu[2].toInt() and 0xFF) shl 8) + val value = pdu.copyOfRange(2, pdu.size) + listeners[handle]?.forEach { listener -> + try { + listener(value) + Log.d(TAG, "Dispatched notification for handle $handle to listener, with value ${value.joinToString(" ") { String.format("%02X", it) }}") + } catch (e: Exception) { + Log.w(TAG, "Error in listener for handle $handle: ${e.message}") + } + } + } else { + // not a notification -> treat as a response for pending request(s) + responses.put(pdu) + } + } catch (e: Exception) { + Log.w(TAG, "Error reading notification/response: ${e.message}") + if (socket?.isConnected != true) break + } + } + } } fun disconnect() { try { + notificationJob?.cancel() socket?.close() } catch (e: Exception) { Log.w(TAG, "Error closing socket: ${e.message}") } } + fun registerListener(handle: Int, listener: (ByteArray) -> Unit) { + listeners.getOrPut(handle) { mutableListOf() }.add(listener) + } + + fun unregisterListener(handle: Int, listener: (ByteArray) -> Unit) { + listeners[handle]?.remove(listener) + } + + fun enableNotifications(handle: Int) { + write(handle + 1, byteArrayOf(0x01, 0x00)) + } + fun read(handle: Int): ByteArray { val lsb = (handle and 0xFF).toByte() val msb = ((handle shr 8) and 0xFF).toByte() val pdu = byteArrayOf(OPCODE_READ_REQUEST, lsb, msb) writeRaw(pdu) - return readRaw() + // wait for response placed into responses queue by the reader coroutine + return readResponse() } fun write(handle: Int, value: ByteArray) { @@ -54,7 +106,12 @@ class ATTManager(private val device: BluetoothDevice) { val msb = ((handle shr 8) and 0xFF).toByte() val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value writeRaw(pdu) - readRaw() // usually a Write Response (0x13) + // usually a Write Response (0x13) will arrive; wait for it (but discard return) + try { + readResponse() + } catch (e: Exception) { + Log.w(TAG, "No write response received: ${e.message}") + } } private fun writeRaw(pdu: ByteArray) { @@ -63,17 +120,33 @@ class ATTManager(private val device: BluetoothDevice) { Log.d(TAG, "writeRaw: ${pdu.joinToString(" ") { String.format("%02X", it) }}") } - private fun readRaw(): ByteArray { + // rename / specialize: read raw PDU directly from input stream (blocking) + private fun readPDU(): ByteArray { val inp = input ?: throw IllegalStateException("Not connected") val buffer = ByteArray(512) val len = inp.read(buffer) if (len <= 0) throw IllegalStateException("No data read from ATT socket") val data = buffer.copyOfRange(0, len) Log.wtf(TAG, "Read ${data.size} bytes from ATT") - Log.d(TAG, "readRaw: ${data.joinToString(" ") { String.format("%02X", it) }}") + Log.d(TAG, "readPDU: ${data.joinToString(" ") { String.format("%02X", it) }}") return data } + // wait for a response PDU produced by the background reader + private fun readResponse(timeoutMs: Long = 2000): ByteArray { + try { + val resp = responses.poll(timeoutMs, TimeUnit.MILLISECONDS) + if (resp == null) { + throw IllegalStateException("No response read from ATT socket within $timeoutMs ms") + } + Log.d(TAG, "readResponse: ${resp.joinToString(" ") { String.format("%02X", it) }}") + return resp + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + throw IllegalStateException("Interrupted while waiting for ATT response", e) + } + } + private fun createBluetoothSocket(device: BluetoothDevice, uuid: ParcelUuid): BluetoothSocket { val type = 3 // L2CAP val constructorSpecs = listOf( From 5c9beeb26d13c69054bc5445b5adc251d066545f Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 14:29:55 +0530 Subject: [PATCH 14/72] android: add header to ATTManager --- .../kavishdevar/librepods/utils/ATTManager.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index 72857bbc..f3709576 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -1,3 +1,26 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + /* This is a very basic ATT (Attribute Protocol) implementation. I have only implemented + * what is necessary for LibrePods to function, i.e. reading and writing characteristics, + * and receiving notifications. It is not a complete implementation of the ATT protocol. + */ + package me.kavishdevar.librepods.utils import android.annotation.SuppressLint From 032b94e3ae59f53b340294e722c0a96bfe02c847 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 16:27:32 +0530 Subject: [PATCH 15/72] android: use device name sent by the connected device in island --- .../librepods/services/AirPodsService.kt | 33 +++++++---- .../librepods/utils/AACPManager.kt | 57 +++++++++++-------- .../librepods/utils/IslandWindow.kt | 15 +++-- .../librepods/utils/MediaController.kt | 22 +++++-- android/app/src/main/res/values/strings.xml | 2 +- 5 files changed, 80 insertions(+), 49 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index b11d1dcd..f32fee19 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -872,16 +872,21 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList override fun onOwnershipChangeReceived(owns: Boolean) { if (!owns) { + MediaController.recentlyLostOwnership = true + Handler(Looper.getMainLooper()).postDelayed({ + MediaController.recentlyLostOwnership = false + }, 3000) Log.d("AirPodsService", "ownership lost") MediaController.sendPause() MediaController.pausedForOtherDevice = true } } - override fun onOwnershipToFalseRequest(reasonReverseTapped: Boolean) { + override fun onOwnershipToFalseRequest(sender: String, reasonReverseTapped: Boolean) { // TODO: Show a reverse button, but that's a lot of effort -- i'd have to change the UI too, which i hate doing, and handle other device's reverses too, and disconnect audio etc... so for now, just pause the audio and show the island without asking to reverse. // handling reverse is a problem because we'd have to disconnect the audio, but there's no option connect audio again natively, so notification would have to be changed. I wish there was a way to just "change the audio output device". // (20 minutes later) i've done it nonetheless :] + val senderName = aacpManager.connectedDevices.find { it.mac == sender }?.type ?: "Other device" Log.d("AirPodsService", "other device has hijacked the connection, reasonReverseTapped: $reasonReverseTapped") aacpManager.sendControlCommand( AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION.value, @@ -895,7 +900,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList this@AirPodsService, (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), IslandType.MOVED_TO_OTHER_DEVICE, - reversed = true + reversed = true, + otherDeviceName = senderName ) } if (!aacpManager.owns) { @@ -903,19 +909,22 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList this@AirPodsService, (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), IslandType.MOVED_TO_OTHER_DEVICE, - reversed = reasonReverseTapped + reversed = reasonReverseTapped, + otherDeviceName = senderName ) } MediaController.sendPause() } - override fun onShowNearbyUI() { - showIsland( - this@AirPodsService, - (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), - IslandType.MOVED_TO_OTHER_DEVICE, - reversed = false - ) + override fun onShowNearbyUI(sender: String) { + val senderName = aacpManager.connectedDevices.find { it.mac == sender }?.type ?: "Other device" + showIsland( + this@AirPodsService, + (batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT}?.level?: 0).coerceAtMost(batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT}?.level?: 0), + IslandType.MOVED_TO_OTHER_DEVICE, + reversed = false, + otherDeviceName = senderName + ) } override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) { @@ -1316,7 +1325,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList var islandOpen = false var islandWindow: IslandWindow? = null @SuppressLint("MissingPermission") - fun showIsland(service: Service, batteryPercentage: Int, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false) { + fun showIsland(service: Service, batteryPercentage: Int, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false, otherDeviceName: String? = null) { Log.d("AirPodsService", "Showing island window") if (!Settings.canDrawOverlays(service)) { Log.d("AirPodsService", "No permission for SYSTEM_ALERT_WINDOW") @@ -1324,7 +1333,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } CoroutineScope(Dispatchers.Main).launch { islandWindow = IslandWindow(service.applicationContext) - islandWindow!!.show(sharedPreferences.getString("name", "AirPods Pro").toString(), batteryPercentage, this@AirPodsService, type, reversed) + islandWindow!!.show(sharedPreferences.getString("name", "AirPods Pro").toString(), batteryPercentage, this@AirPodsService, type, reversed, otherDeviceName) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index f704c9a9..02e9993b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -174,7 +174,8 @@ class AACPManager { data class ConnectedDevice( val mac: String, val info1: Byte, - val info2: Byte + val info2: Byte, + var type: String? ) } @@ -242,8 +243,8 @@ class AACPManager { fun onAudioSourceReceived(audioSource: ByteArray) fun onOwnershipChangeReceived(owns: Boolean) fun onConnectedDevicesReceived(connectedDevices: List) - fun onOwnershipToFalseRequest(reasonReverseTapped: Boolean) - fun onShowNearbyUI() + fun onOwnershipToFalseRequest(sender: String, reasonReverseTapped: Boolean) + fun onShowNearbyUI(sender: String) } fun parseStemPressResponse(data: ByteArray): Pair { @@ -521,11 +522,21 @@ class AACPManager { Opcodes.SMART_ROUTING_RESP -> { val packetString = packet.decodeToString() + val sender = packet.sliceArray(6..11).reversedArray().joinToString(":") { "%02X".format(it) } + + if (connectedDevices.find { it.mac == sender }?.type == null && packetString.contains("btName")) { + val nameStartIndex = packetString.indexOf("btName") + 7 + val nameEndIndex = if (packetString.contains("other")) (packetString.indexOf("otherDevice") - 2) else (packetString.indexOf("nearbyAudio") - 2) + val name = packet.sliceArray(nameStartIndex..nameEndIndex).decodeToString() + connectedDevices.find { it.mac == sender }?.type = name + Log.d(TAG, "Device $sender is named $name") + } + Log.d(TAG, "Smart Routing Response from $sender: $packetString, type: ${connectedDevices.find { it.mac == sender }?.type}") if (packetString.contains("SetOwnershipToFalse")) { - callback?.onOwnershipToFalseRequest(packetString.contains("ReverseBannerTapped")) + callback?.onOwnershipToFalseRequest(sender, packetString.contains("ReverseBannerTapped")) } if (packetString.contains("ShowNearbyUI")) { - callback?.onShowNearbyUI() + callback?.onShowNearbyUI(sender) } } @@ -544,7 +555,7 @@ class AACPManager { ) return } - // first 4 bytes AACP header, next two bytes opcode, next to bytes identifer + eqOnMedia = (packet[10] == 0x01.toByte()) eqOnPhone = (packet[11] == 0x01.toByte()) // there are 4 eqs. i am not sure what those are for, maybe all 4 listening modes, or maybe phone+media left+right, but then there shouldn't be another flag for phone/media enabled. just directly the EQ... weird. @@ -554,7 +565,7 @@ class AACPManager { val eq3 = ByteBuffer.wrap(packet, 76, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() val eq4 = ByteBuffer.wrap(packet, 108, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() - // for now, just take the first EQ + // for now, taking just the first EQ eqData = FloatArray(8) { i -> eq1.get(i) } Log.d(TAG, "EQ Data set to: ${eqData.toList()}, eqOnPhone: $eqOnPhone, eqOnMedia: $eqOnMedia") } @@ -756,11 +767,11 @@ class AACPManager { fun createMediaInformationNewDevicePacket(selfMacAddress: String, targetMacAddress: String): ByteArray { val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) - val buffer = ByteBuffer.allocate(112) + val buffer = ByteBuffer.allocate(116) buffer.put( targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() ) - buffer.put(byteArrayOf(0x68, 0x00)) + buffer.put(byteArrayOf(0x6C, 0x00)) buffer.put(byteArrayOf(0x01, 0xE5.toByte(), 0x4A)) buffer.put("playingApp".toByteArray()) buffer.put(0x42) @@ -775,8 +786,8 @@ class AACPManager { buffer.put(selfMacAddress.toByteArray()) buffer.put(0x46) buffer.put("btName".toByteArray()) - buffer.put(0x43) - buffer.put("And".toByteArray()) + buffer.put(0x47) + buffer.put("Android".toByteArray()) buffer.put(0x58) buffer.put("otherDevice".toByteArray()) buffer.put("AudioCategory".toByteArray()) @@ -805,8 +816,6 @@ class AACPManager { buffer.put( targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() ) - // 620001E54A6C6F63616C73636F7265306446726561736F6E4848696A61636B763251617564696F526F7574696E6753636F7265312D015F617564696F526F7574696E675365744F776E657273686970546F46616C7365014B72656D6F746573636F7265A5 - buffer.put(byteArrayOf(0x62, 0x00)) buffer.put(byteArrayOf(0x01, 0xE5.toByte())) buffer.put(0x4A) @@ -854,16 +863,16 @@ class AACPManager { streamingState: Boolean = true ): ByteArray { val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) - val buffer = ByteBuffer.allocate(134) + val buffer = ByteBuffer.allocate(138) buffer.put( targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() ) buffer.put( byteArrayOf( - 0x7E, + 0x82.toByte(), // related to the length 0x00 ) - ) // something to do with the length, can't confirm, but changing causes airpods to soft reset + ) buffer.put(byteArrayOf(0x01, 0xE5.toByte(), 0x4A)) // unknown, constant buffer.put("PlayingApp".toByteArray()) buffer.put(byteArrayOf(0x56)) // 'V', seems like a identifier or a separator @@ -877,8 +886,8 @@ class AACPManager { buffer.put(0x51) // 'Q' buffer.put(selfMacAddress.toByteArray()) // self MAC buffer.put("btName".toByteArray()) // self name - buffer.put(0x44) // 'D' - buffer.put("iPho".toByteArray()) // if set to iPad, shows "Moved to iPad, but most likely we're running on a phone. setting to anything else of the same length will show iPhone instead. + buffer.put(0x47) // 'D' + buffer.put("Android".toByteArray()) // if set to iPad, shows "Moved to iPad", but most likely we're running on a phone. setting to anything else of the same length will show iPhone instead. buffer.put(0x58) // 'X' buffer.put("otherDevice".toByteArray()) buffer.put("AudioCategory".toByteArray()) @@ -973,11 +982,11 @@ class AACPManager { fun createAddTiPiDevicePacket(selfMacAddress: String, targetMacAddress: String): ByteArray { val opcode = byteArrayOf(Opcodes.SMART_ROUTING, 0x00) - val buffer = ByteBuffer.allocate(86) + val buffer = ByteBuffer.allocate(90) buffer.put( targetMacAddress.split(":").map { it.toInt(16).toByte() }.toByteArray().reversedArray() ) - buffer.put(byteArrayOf(0x4E, 0x00)) + buffer.put(byteArrayOf(0x52, 0x00)) buffer.put(byteArrayOf(0x01, 0xE5.toByte())) buffer.put(0x48) // 'H' buffer.put("idleTime".toByteArray()) @@ -989,8 +998,8 @@ class AACPManager { buffer.put(selfMacAddress.toByteArray()) buffer.put(0x46) buffer.put("btName".toByteArray()) - buffer.put(0x43) - buffer.put("And".toByteArray()) + buffer.put(0x47) + buffer.put("Android".toByteArray()) buffer.put(0x50) buffer.put("nearbyAudioScore".toByteArray()) buffer.put(byteArrayOf(0x0E)) @@ -1164,13 +1173,13 @@ class AACPManager { val mac = macBytes.joinToString(":") { "%02X".format(it) } val info1 = data[offset + 6] val info2 = data[offset + 7] - devices.add(ConnectedDevice(mac, info1, info2)) + val existingDevice = devices.find { it.mac == mac } + devices.add(ConnectedDevice(mac, info1, info2, existingDevice?.type)) offset += 8 } return devices } - fun sendSomePacketIDontKnowWhatItIs() { // 2900 00ff ffff ffff ffff -- enables setting EQ sendDataPacket( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt index d8ef502a..0e57c4b1 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt @@ -165,7 +165,7 @@ class IslandWindow(private val context: Context) { @SuppressLint("SetTextI18s", "ClickableViewAccessibility", "UnspecifiedRegisterReceiverFlag", "SetTextI18n" ) - fun show(name: String, batteryPercentage: Int, context: Context, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false) { + fun show(name: String, batteryPercentage: Int, context: Context, type: IslandType = IslandType.CONNECTED, reversed: Boolean = false, otherDeviceName: String? = null) { if (ServiceManager.getService()?.islandOpen == true) return else ServiceManager.getService()?.islandOpen = true @@ -352,19 +352,22 @@ class IslandWindow(private val context: Context) { when (type) { IslandType.CONNECTED -> { - islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_connected_text) + islandView.findViewById(R.id.island_connected_text).text = context.getString(R.string.island_connected_text) } IslandType.TAKING_OVER -> { - islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_taking_over_text) + islandView.findViewById(R.id.island_connected_text).text = context.getString(R.string.island_taking_over_text) } IslandType.MOVED_TO_REMOTE -> { - islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_moved_to_remote_text) + islandView.findViewById(R.id.island_connected_text).text = context.getString(R.string.island_moved_to_remote_text) } IslandType.MOVED_TO_OTHER_DEVICE -> { + if (otherDeviceName == null || otherDeviceName.isEmpty()) { + e("IslandWindow", "Other device name is null or empty for MOVED_TO_OTHER_DEVICE type") + } if (reversed) { - islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_moved_to_other_device_reversed_text) + islandView.findViewById(R.id.island_connected_text).text = context.getString(R.string.island_moved_to_other_device_reversed_text) } else { - islandView.findViewById(R.id.island_connected_text).text = getString(context, R.string.island_moved_to_other_device_text) + islandView.findViewById(R.id.island_connected_text).text = context.getString(R.string.island_moved_to_other_device_text, otherDeviceName) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt index 631673bb..5d9e07e3 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/MediaController.kt @@ -61,6 +61,8 @@ object MediaController { private var conversationalAwarenessVolume: Int = 2 private var conversationalAwarenessPauseMusic: Boolean = false + var recentlyLostOwnership: Boolean = false + fun initialize(audioManager: AudioManager, sharedPreferences: SharedPreferences) { if (this::audioManager.isInitialized) { return @@ -118,10 +120,14 @@ object MediaController { if (isActive) { Log.d("MediaController", "Detected play while pausedForOtherDevice; attempting to take over") - pausedForOtherDevice = false - userPlayedTheMedia = true - if (!pausedWhileTakingOver) { - ServiceManager.getService()?.takeOver("music") + if (!recentlyLostOwnership) { + pausedForOtherDevice = false + userPlayedTheMedia = true + if (!pausedWhileTakingOver) { + ServiceManager.getService()?.takeOver("music") + } + } else { + Log.d("MediaController", "Skipping take-over due to recent ownership loss") } } else { Log.d("MediaController", "Still not active while pausedForOtherDevice; will clear state after timeout") @@ -148,8 +154,12 @@ object MediaController { Log.d("MediaController", "pausedWhileTakingOver: $pausedWhileTakingOver") if (!pausedWhileTakingOver && isActive) { if (lastKnownIsMusicActive != true) { - Log.d("MediaController", "Music is active and not pausedWhileTakingOver; requesting takeOver") - ServiceManager.getService()?.takeOver("music") + if (!recentlyLostOwnership) { + Log.d("MediaController", "Music is active and not pausedWhileTakingOver; requesting takeOver") + ServiceManager.getService()?.takeOver("music") + } else { + Log.d("MediaController", "Skipping take-over due to recent ownership loss") + } } } diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index a520e9de..3d84861c 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -47,7 +47,7 @@ Connected to Linux Connected Moved to Linux - Moved to other device + Moved to %1$s Reconnect from notification Head Tracking Nod to answer calls, and shake your head to decline. From 3699ee6beee797462e182cbeac0ac557796fabcc Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 18:08:31 +0530 Subject: [PATCH 16/72] android: fix track color in tone volume --- .../me/kavishdevar/librepods/composables/ToneVolumeSlider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt index 07546ab2..98ef02ad 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt @@ -158,7 +158,7 @@ fun ToneVolumeSlider() { ) Box( modifier = Modifier - .fillMaxWidth(sliderValue.floatValue / 100) + .fillMaxWidth(sliderValue.floatValue / 125) .height(4.dp) .background(activeTrackColor, RoundedCornerShape(4.dp)) ) From b5103a28e7653e038c176120c59c394aa1cf84dd Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 18:10:00 +0530 Subject: [PATCH 17/72] android: remove unused composable --- .../me/kavishdevar/librepods/composables/AccessibilitySettings.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySettings.kt deleted file mode 100644 index e69de29b..00000000 From 5eff5b9d77d89c79f7713ad2b71fc2ce567fed83 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 18:12:56 +0530 Subject: [PATCH 18/72] android: update eq sliders style --- .../screens/AccessibilitySettingsScreen.kt | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 73693f4f..bc66c191 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -36,7 +36,9 @@ 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.layout.width +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -49,6 +51,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -67,6 +70,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput @@ -121,6 +125,11 @@ fun AccessibilitySettingsScreen() { // get the AACP manager if available (used for EQ read/write) val aacpManager = remember { ServiceManager.getService()?.aacpManager } + 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 + DisposableEffect(attManager) { onDispose { Log.d(TAG, "Disconnecting from ATT...") @@ -621,7 +630,7 @@ fun AccessibilitySettingsScreen() { verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .height(32.dp) + .height(38.dp) ) { Text( text = String.format("%.2f", eqValue.floatValue), @@ -641,6 +650,42 @@ fun AccessibilitySettingsScreen() { valueRange = 0f..100f, modifier = Modifier .fillMaxWidth(0.9f) + .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(eqValue.floatValue / 100f) + .height(4.dp) + .background(activeTrackColor, RoundedCornerShape(4.dp)) + ) + } + } ) Text( @@ -782,7 +827,7 @@ fun AccessibilitySettingsScreen() { verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .height(32.dp) + .height(38.dp) ) { Text( text = String.format("%.2f", eqPhoneValue.floatValue), @@ -802,6 +847,42 @@ fun AccessibilitySettingsScreen() { valueRange = 0f..100f, modifier = Modifier .fillMaxWidth(0.9f) + .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(eqPhoneValue.floatValue / 100f) + .height(4.dp) + .background(activeTrackColor, RoundedCornerShape(4.dp)) + ) + } + } ) Text( From 63baa153da540e9d463cc3d5febe34e09af95ca0 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 18:16:02 +0530 Subject: [PATCH 19/72] android: fix text color in selectors --- .../librepods/screens/AccessibilitySettingsScreen.kt | 2 ++ .../kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt | 1 + 2 files changed, 3 insertions(+) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index bc66c191..9e587e3a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -742,6 +742,7 @@ fun AccessibilitySettingsScreen() { Text( "Phone", fontSize = 16.sp, + color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)), modifier = Modifier.weight(1f) ) @@ -792,6 +793,7 @@ fun AccessibilitySettingsScreen() { Text( "Media", fontSize = 16.sp, + color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)), modifier = Modifier.weight(1f) ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index eb884c94..457a5963 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -403,6 +403,7 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF Text( name, fontSize = 16.sp, + color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)), ) Text ( From 71a1f834cb80a3de7f05214537cf92bad0c79ef1 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 19 Sep 2025 23:38:38 +0530 Subject: [PATCH 20/72] android: add delay before starting head tracking again --- .../kavishdevar/librepods/services/AirPodsService.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index f32fee19..49ca9a86 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -2145,11 +2145,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList MediaController.sendPlay() } else if (startHeadTrackingAgain) { Log.d("AirPodsService", "Starting head tracking again after taking control") - if (sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false)) { - aacpManager.sendDataPacket(aacpManager.createAlternateStartHeadTrackingPacket()) - } else { - aacpManager.sendStartHeadTracking() - } + Handler(Looper.getMainLooper()).postDelayed({ + startHeadTracking() + }, 500) } } } else { @@ -2602,7 +2600,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList fun startHeadTracking() { isHeadTrackingActive = true val useAlternatePackets = sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && aacpManager.getControlCommandStatus(AACPManager.Companion.ControlCommandIdentifiers.OWNS_CONNECTION)?.value?.get(0)?.toInt() != 1) { takeOver("call", startHeadTrackingAgain = true) Log.d("AirPodsService", "Taking over for head tracking") } else { From bb69a74a8ecaea6158c0d3018deae89351368913 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sat, 20 Sep 2025 01:43:24 +0530 Subject: [PATCH 21/72] android: add a few options ik not the right branch/pr but, eh, i am not merging this hook until i test further, and if i don't merge, conflicts, a lot of 'em --- .../librepods/composables/AudioSettings.kt | 47 ++- .../composables/AutomaticConnectionSwitch.kt | 182 +++++++++++ .../composables/CallControlSettings.kt | 287 ++++++++++++++++ .../composables/ConnectionSettings.kt | 76 +++++ .../ConversationalAwarenessSwitch.kt | 2 +- .../librepods/composables/CustomDropdown.kt | 182 ----------- .../composables/EarDetectionSwitch.kt | 176 ++++++++++ .../composables/PersonalizedVolumeSwitch.kt | 163 +++++++++ .../screens/AccessibilitySettingsScreen.kt | 309 +++++++++--------- .../screens/AirPodsSettingsScreen.kt | 67 ++-- .../librepods/utils/AACPManager.kt | 6 + .../src/main/res/values-zh-rCN/strings.xml | 1 - android/app/src/main/res/values/strings.xml | 7 +- 13 files changed, 1147 insertions(+), 358 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/CustomDropdown.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt index 5e7cf637..25a0c4eb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column @@ -27,7 +28,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text +import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource @@ -37,12 +41,32 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.ATTManager import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun AudioSettings() { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black + val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected")) + DisposableEffect(attManager) { + onDispose { + try { + attManager.disconnect() + } catch (e: Exception) { + Log.w("AirPodsAudioSettings", "Error while disconnecting ATTManager: ${e.message}") + } + } + } + LaunchedEffect(Unit) { + Log.d("AirPodsAudioSettings", "Connecting to ATT...") + try { + attManager.connect() + } catch (e: Exception) { + Log.w("AirPodsAudioSettings", "Error while connecting ATTManager: ${e.message}") + } + } Text( text = stringResource(R.string.audio).uppercase(), @@ -63,7 +87,29 @@ fun AudioSettings() { .padding(top = 2.dp) ) { + PersonalizedVolumeSwitch() + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + ConversationalAwarenessSwitch() + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + + LoudSoundReductionSwitch(attManager) + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) Column( modifier = Modifier @@ -91,7 +137,6 @@ fun AudioSettings() { color = textColor.copy(alpha = 0.6f) ) ) - AdaptiveStrengthSlider() } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt new file mode 100644 index 00000000..994da455 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt @@ -0,0 +1,182 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +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.isSystemInDarkTheme +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import android.content.Context.MODE_PRIVATE +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun AutomaticConnectionSwitch() { + val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) + val service = ServiceManager.getService()!! + + val shared_preference_key = "automatic_connection_ctrl_cmd" + + val automaticConnectionEnabledValue = service.aacpManager.controlCommandStatusList.find { + it.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG + }?.value?.takeIf { it.isNotEmpty() }?.get(0) + + var automaticConnectionEnabled by remember { + mutableStateOf( + if (automaticConnectionEnabledValue != null) { + automaticConnectionEnabledValue == 1.toByte() + } else { + sharedPreferences.getBoolean(shared_preference_key, false) + } + ) + } + + fun updateAutomaticConnection(enabled: Boolean) { + automaticConnectionEnabled = enabled + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG.value, + enabled + ) + // todo: send other connected devices smartAudioRoutingDisabled or something, check packets again. + + sharedPreferences.edit() + .putBoolean(shared_preference_key, enabled) + .apply() + } + + val automaticConnectionListener = object: AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + val enabled = newValue == 1.toByte() + automaticConnectionEnabled = enabled + + sharedPreferences.edit() + .putBoolean(shared_preference_key, enabled) + .apply() + } + } + } + + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG, + automaticConnectionListener + ) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG, + automaticConnectionListener + ) + } + } + + val isDarkTheme = isSystemInDarkTheme() + 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() } + ) { + updateAutomaticConnection(!automaticConnectionEnabled) + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = stringResource(R.string.automatically_connect), + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.automatically_connect_description), + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ) + } + StyledSwitch( + checked = automaticConnectionEnabled, + onCheckedChange = { + updateAutomaticConnection(it) + }, + ) + } +} + +@Preview +@Composable +fun AutomaticConnectionSwitchPreview() { + AutomaticConnectionSwitch() +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt new file mode 100644 index 00000000..56bd43f2 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -0,0 +1,287 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +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 me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun CallControlSettings() { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + Text( + text = stringResource(R.string.call_controls).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(top = 2.dp) + ) { + val service = ServiceManager.getService()!! + val callControlEnabledValue = service.aacpManager.controlCommandStatusList.find { + it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG + }?.value ?: byteArrayOf(0x00, 0x03) + + var flipped by remember { mutableStateOf(callControlEnabledValue.contentEquals(byteArrayOf(0x00, 0x02))) } + var singlePressAction by remember { mutableStateOf(if (flipped) "Double Press" else "Single Press") } + var doublePressAction by remember { mutableStateOf(if (flipped) "Single Press" else "Double Press") } + var showSinglePressDropdown by remember { mutableStateOf(false) } + var showDoublePressDropdown by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + val listener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) == + AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG) { + val newFlipped = controlCommand.value.contentEquals(byteArrayOf(0x00, 0x02)) + flipped = newFlipped + singlePressAction = if (newFlipped) "Double Press" else "Single Press" + doublePressAction = if (newFlipped) "Single Press" else "Double Press" + Log.d("CallControlSettings", "Control command received, flipped: $newFlipped") + } + } + } + + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG, + listener + ) + } + + DisposableEffect(Unit) { + onDispose { + service.aacpManager.controlCommandListeners[AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG]?.clear() + } + } + + LaunchedEffect(flipped) { + Log.d("CallControlSettings", "Call control flipped: $flipped") + } + + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp) + .height(55.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Answer call", + fontSize = 18.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + Text( + text = "Single Press", + fontSize = 18.sp, + color = textColor.copy(alpha = 0.6f) + ) + } + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp) + .height(55.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Mute/Unmute", + fontSize = 18.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + Box { + Row( + modifier = Modifier.clickable { showSinglePressDropdown = true }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = singlePressAction, + fontSize = 18.sp, + color = textColor.copy(alpha = 0.8f) + ) + Icon( + Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = textColor.copy(alpha = 0.6f) + ) + } + DropdownMenu( + expanded = showSinglePressDropdown, + onDismissRequest = { showSinglePressDropdown = false } + ) { + DropdownMenuItem( + text = { Text("Single Press") }, + onClick = { + singlePressAction = "Single Press" + doublePressAction = "Double Press" + showSinglePressDropdown = false + service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03)) + } + ) + DropdownMenuItem( + text = { Text("Double Press") }, + onClick = { + singlePressAction = "Double Press" + doublePressAction = "Single Press" + showSinglePressDropdown = false + service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02)) + } + ) + } + } + } + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp) + .height(55.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Hang Up", + fontSize = 18.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + Box { + Row( + modifier = Modifier.clickable { showDoublePressDropdown = true }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = doublePressAction, + fontSize = 18.sp, + color = textColor.copy(alpha = 0.8f) + ) + Icon( + Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = textColor.copy(alpha = 0.6f) + ) + } + DropdownMenu( + expanded = showDoublePressDropdown, + onDismissRequest = { showDoublePressDropdown = false } + ) { + DropdownMenuItem( + text = { Text("Single Press") }, + onClick = { + doublePressAction = "Single Press" + singlePressAction = "Double Press" + showDoublePressDropdown = false + service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02)) + } + ) + DropdownMenuItem( + text = { Text("Double Press") }, + onClick = { + doublePressAction = "Double Press" + singlePressAction = "Single Press" + showDoublePressDropdown = false + service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03)) + } + ) + } + } + } + } + } +} + +@Preview +@Composable +fun CallControlSettingsPreview() { + CallControlSettings() +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt new file mode 100644 index 00000000..58ffa147 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt @@ -0,0 +1,76 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.material3.HorizontalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +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 me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.ATTManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun ConnectionSettings() { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(top = 2.dp) + ) { + EarDetectionSwitch() + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + + AutomaticConnectionSwitch() + } +} + +@Preview +@Composable +fun ConnectionSettingsPreview() { + ConnectionSettings() +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt index 46fa6f61..7492a629 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt @@ -133,7 +133,7 @@ fun ConversationalAwarenessSwitch() { .padding(end = 4.dp) ) { Text( - text = "Conversational Awareness", + text = stringResource(R.string.conversational_awareness), fontSize = 16.sp, color = textColor ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CustomDropdown.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CustomDropdown.kt deleted file mode 100644 index a4d37b6b..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CustomDropdown.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package me.kavishdevar.librepods.composables - -import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.spring -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.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.layout.widthIn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Popup -import androidx.compose.ui.window.PopupProperties -import me.kavishdevar.librepods.R - -class DropdownItem(val name: String, val onSelect: () -> Unit) { - fun select() { - onSelect() - } -} - -@Composable -fun CustomDropdown(name: String, description: String = "", items: List) { - val isDarkTheme = isSystemInDarkTheme() - val textColor = if (isDarkTheme) Color.White else Color.Black - val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - var expanded by remember { mutableStateOf(false) } - var offset by remember { mutableStateOf(IntOffset.Zero) } - var popupHeight by remember { mutableStateOf(0.dp) } - - val animatedHeight by animateDpAsState( - targetValue = if (expanded) popupHeight else 0.dp, - animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow) - ) - val animatedScale by animateFloatAsState( - targetValue = if (expanded) 1f else 0f, - animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .background( - shape = RoundedCornerShape(14.dp), - color = Color.Transparent - ) - .padding(horizontal = 12.dp, vertical = 12.dp) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - expanded = true - } - .onGloballyPositioned { coordinates -> - val windowPosition = coordinates.localToWindow(Offset.Zero) - offset = IntOffset(windowPosition.x.toInt(), windowPosition.y.toInt() + coordinates.size.height) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(end = 4.dp) - ) { - Text( - text = name, - fontSize = 16.sp, - color = textColor, - maxLines = 1 - ) - if (description.isNotEmpty()) { - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = description, - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, - maxLines = 1 - ) - } - } - Text( - text = "\uDBC0\uDD8F", - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor - ) - } - - if (expanded) { - Popup( - alignment = Alignment.TopStart, - offset = offset , - properties = PopupProperties(focusable = true), - onDismissRequest = { expanded = false } - ) { - val density = LocalDensity.current - Column( - modifier = Modifier - .background(backgroundColor, RoundedCornerShape(8.dp)) - .padding(8.dp) - .widthIn(max = 50.dp) - .height(animatedHeight) - .scale(animatedScale) - .onGloballyPositioned { coordinates -> - popupHeight = with(density) { coordinates.size.height.toDp() } - } - ) { - items.forEach { item -> - Text( - text = item.name, - modifier = Modifier - .fillMaxWidth() - .clickable { - item.select() - expanded = false - } - .padding(8.dp), - color = textColor - ) - } - } - } - } -} - -@Preview -@Composable -fun CustomDropdownPreview() { - CustomDropdown( - name = "Volume Swipe Speed", - items = listOf( - DropdownItem("Always On") { }, - DropdownItem("Off") { }, - DropdownItem("Only when speaking") { } - ) - ) -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt new file mode 100644 index 00000000..37ef7c83 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt @@ -0,0 +1,176 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +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.isSystemInDarkTheme +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import android.content.Context.MODE_PRIVATE +import android.content.SharedPreferences +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun EarDetectionSwitch() { + val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) + val service = ServiceManager.getService()!! + + val shared_preference_key = "automatic_ear_detection" + + val earDetectionEnabledValue = service.aacpManager.controlCommandStatusList.find { + it.identifier == AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG + }?.value?.takeIf { it.isNotEmpty() }?.get(0) + + var earDetectionEnabled by remember { + mutableStateOf( + if (earDetectionEnabledValue != null) { + earDetectionEnabledValue == 1.toByte() + } else { + sharedPreferences.getBoolean(shared_preference_key, false) + } + ) + } + + fun updateEarDetection(enabled: Boolean) { + earDetectionEnabled = enabled + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG.value, + enabled + ) + service.setEarDetection(enabled) + + sharedPreferences.edit() + .putBoolean(shared_preference_key, enabled) + .apply() + } + + val earDetectionListener = object: AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + val enabled = newValue == 1.toByte() + earDetectionEnabled = enabled + + sharedPreferences.edit() + .putBoolean(shared_preference_key, enabled) + .apply() + } + } + } + + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG, + earDetectionListener + ) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG, + earDetectionListener + ) + } + } + + val isDarkTheme = isSystemInDarkTheme() + 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() } + ) { + updateEarDetection(!earDetectionEnabled) + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = stringResource(R.string.ear_detection), + fontSize = 16.sp, + color = textColor + ) + } + StyledSwitch( + checked = earDetectionEnabled, + onCheckedChange = { + updateEarDetection(it) + } + ) + } +} + +@Preview +@Composable +fun EarDetectionSwitchPreview() { + EarDetectionSwitch() +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt new file mode 100644 index 00000000..14bb876f --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt @@ -0,0 +1,163 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +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.isSystemInDarkTheme +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.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun PersonalizedVolumeSwitch() { + val service = ServiceManager.getService()!! + + val adaptiveVolumeEnabledValue = service.aacpManager.controlCommandStatusList.find { + it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG + }?.value?.takeIf { it.isNotEmpty() }?.get(0) + + var adaptiveVolumeEnabled by remember { + mutableStateOf( + adaptiveVolumeEnabledValue == 1.toByte() + ) + } + + fun updatePersonalizedVolume(enabled: Boolean) { + adaptiveVolumeEnabled = enabled + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG.value, + enabled + ) + } + + val adaptiveVolumeListener = object: AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + adaptiveVolumeEnabled = newValue == 1.toByte() + } + } + } + + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, + adaptiveVolumeListener + ) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, + adaptiveVolumeListener + ) + } + } + + val isDarkTheme = isSystemInDarkTheme() + 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() } + ) { + updatePersonalizedVolume(!adaptiveVolumeEnabled) + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = stringResource(R.string.personalized_volume), + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.personalized_volume_description), + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ) + } + StyledSwitch( + checked = adaptiveVolumeEnabled, + onCheckedChange = { + updatePersonalizedVolume(it) + }, + ) + } +} + +@Preview +@Composable +fun PersonalizedVolumeSwitchPreview() { + PersonalizedVolumeSwitch() +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 9e587e3a..d9204612 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -75,6 +75,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.PointerEventPass +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 @@ -102,6 +103,7 @@ import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.RadareOffsetFinder import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -124,6 +126,9 @@ fun AccessibilitySettingsScreen() { val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected")) // get the AACP manager if available (used for EQ read/write) val aacpManager = remember { ServiceManager.getService()?.aacpManager } + val context = LocalContext.current + val radareOffsetFinder = remember { RadareOffsetFinder(context) } + val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491) val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) @@ -457,76 +462,80 @@ fun AccessibilitySettingsScreen() { } } - AccessibilityToggle( - text = "Transparency Mode", - mutableState = enabled, - independent = true - ) - Text( - text = stringResource(R.string.customize_transparency_mode_description), - style = TextStyle( - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, - ), - modifier = Modifier - .padding(horizontal = 2.dp) - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "Customize Transparency Mode".uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) - ) - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(8.dp) - ) { - AccessibilitySlider( - label = "Amplification", - valueRange = -1f..1f, - value = amplificationSliderValue.floatValue, - onValueChange = { - amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) - }, - ) - AccessibilitySlider( - label = "Balance", - valueRange = -1f..1f, - value = balanceSliderValue.floatValue, - onValueChange = { - balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) - AccessibilitySlider( - label = "Tone", - valueRange = -1f..1f, - value = toneSliderValue.floatValue, - onValueChange = { - toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, + // Only show transparency mode section if SDP offset is available + if (isSdpOffsetAvailable.value) { + AccessibilityToggle( + text = "Transparency Mode", + mutableState = enabled, + independent = true ) - AccessibilitySlider( - label = "Ambient Noise Reduction", - valueRange = 0f..1f, - value = ambientNoiseReductionSliderValue.floatValue, - onValueChange = { - ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) - }, + Text( + text = stringResource(R.string.customize_transparency_mode_description), + style = TextStyle( + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ), + modifier = Modifier + .padding(horizontal = 2.dp) ) - AccessibilityToggle( - text = "Conversation Boost", - mutableState = conversationBoostEnabled + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Customize Transparency Mode".uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) ) + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(8.dp) + ) { + AccessibilitySlider( + label = "Amplification", + valueRange = -1f..1f, + value = amplificationSliderValue.floatValue, + onValueChange = { + amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) + }, + ) + AccessibilitySlider( + label = "Balance", + valueRange = -1f..1f, + value = balanceSliderValue.floatValue, + onValueChange = { + balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) + }, + ) + AccessibilitySlider( + label = "Tone", + valueRange = -1f..1f, + value = toneSliderValue.floatValue, + onValueChange = { + toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) + }, + ) + AccessibilitySlider( + label = "Ambient Noise Reduction", + valueRange = 0f..1f, + value = ambientNoiseReductionSliderValue.floatValue, + onValueChange = { + ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) + }, + ) + AccessibilityToggle( + text = "Conversation Boost", + mutableState = conversationBoostEnabled + ) + } + Spacer(modifier = Modifier.height(2.dp)) } - Spacer(modifier = Modifier.height(2.dp)) + Text( text = "AUDIO", style = TextStyle( @@ -604,101 +613,105 @@ fun AccessibilitySettingsScreen() { } Spacer(modifier = Modifier.height(2.dp)) - Text( - text = "Equalizer".uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween - ) { - for (i in 0 until 8) { - val eqValue = remember(eq.value[i]) { mutableFloatStateOf(eq.value[i]) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(38.dp) - ) { - Text( - text = String.format("%.2f", eqValue.floatValue), - fontSize = 12.sp, - color = textColor, - modifier = Modifier.padding(bottom = 4.dp) - ) + // Only show transparency mode EQ section if SDP offset is available + if (isSdpOffsetAvailable.value) { + Text( + text = "Equalizer".uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) - Slider( - value = eqValue.floatValue, - onValueChange = { newVal -> - eqValue.floatValue = newVal - val newEQ = eq.value.copyOf() - newEQ[i] = eqValue.floatValue - eq.value = newEQ - }, - valueRange = 0f..100f, + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + for (i in 0 until 8) { + val eqValue = remember(eq.value[i]) { mutableFloatStateOf(eq.value[i]) } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .fillMaxWidth(0.9f) - .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 - ) - { + .fillMaxWidth() + .height(38.dp) + ) { + Text( + text = String.format("%.2f", eqValue.floatValue), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + + Slider( + value = eqValue.floatValue, + onValueChange = { newVal -> + eqValue.floatValue = newVal + val newEQ = eq.value.copyOf() + newEQ[i] = eqValue.floatValue + eq.value = newEQ + }, + valueRange = 0f..100f, + modifier = Modifier + .fillMaxWidth(0.9f) + .height(36.dp), + colors = SliderDefaults.colors( + thumbColor = thumbColor, + activeTrackColor = activeTrackColor, + inactiveTrackColor = trackColor + ), + thumb = { Box( modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .background(trackColor, RoundedCornerShape(4.dp)) + .size(24.dp) + .shadow(4.dp, CircleShape) + .background(thumbColor, CircleShape) ) - Box( + }, + track = { + Box ( modifier = Modifier - .fillMaxWidth(eqValue.floatValue / 100f) - .height(4.dp) - .background(activeTrackColor, RoundedCornerShape(4.dp)) + .fillMaxWidth() + .height(12.dp), + contentAlignment = Alignment.CenterStart ) + { + Box( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .background(trackColor, RoundedCornerShape(4.dp)) + ) + Box( + modifier = Modifier + .fillMaxWidth(eqValue.floatValue / 100f) + .height(4.dp) + .background(activeTrackColor, RoundedCornerShape(4.dp)) + ) + } } - } - ) + ) - Text( - text = "Band ${i + 1}", - fontSize = 12.sp, - color = textColor, - modifier = Modifier.padding(top = 4.dp) - ) + Text( + text = "Band ${i + 1}", + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(top = 4.dp) + ) + } } } + + Spacer(modifier = Modifier.height(16.dp)) } - Spacer(modifier = Modifier.height(16.dp)) Text( text = "Apply EQ to".uppercase(), style = TextStyle( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 545f6fb0..e4c1e650 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -94,6 +94,8 @@ import me.kavishdevar.librepods.R import me.kavishdevar.librepods.CustomDevice import me.kavishdevar.librepods.composables.AudioSettings import me.kavishdevar.librepods.composables.BatteryView +import me.kavishdevar.librepods.composables.CallControlSettings +import me.kavishdevar.librepods.composables.ConnectionSettings import me.kavishdevar.librepods.composables.IndependentToggle import me.kavishdevar.librepods.composables.NameField import me.kavishdevar.librepods.composables.NavigationButton @@ -353,11 +355,35 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, ) } - // Only show L2CAP-dependent features when not in BLE-only mode if (!bleOnlyMode) { Spacer(modifier = Modifier.height(32.dp)) NoiseControlSettings(service = service) + Spacer(modifier = Modifier.height(16.dp)) + CallControlSettings() + + // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events + + Spacer(modifier = Modifier.height(16.dp)) + PressAndHoldSettings(navController = navController) + + Spacer(modifier = Modifier.height(16.dp)) + AudioSettings() + + Spacer(modifier = Modifier.height(16.dp)) + ConnectionSettings() + + // microphone settings + + Spacer(modifier = Modifier.height(16.dp)) + IndependentToggle( + name = stringResource(R.string.sleep_detection), + service = service, + sharedPreferences = sharedPreferences, + default = false, + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG + ) + Spacer(modifier = Modifier.height(16.dp)) Text( text = stringResource(R.string.head_gestures).uppercase(), @@ -369,42 +395,35 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, ), modifier = Modifier.padding(8.dp, bottom = 2.dp) ) - Spacer(modifier = Modifier.height(2.dp)) NavigationButton(to = "head_tracking", "Head Tracking", navController) Spacer(modifier = Modifier.height(16.dp)) - PressAndHoldSettings(navController = navController) - - Spacer(modifier = Modifier.height(16.dp)) - AudioSettings() + NavigationButton(to = "", "Accessibility", navController = navController, onClick = { + val intent = Intent(context, CustomDevice::class.java) + context.startActivity(intent) + }) Spacer(modifier = Modifier.height(16.dp)) IndependentToggle( - name = "Off Listening Mode", + name = stringResource(R.string.off_listening_mode), service = service, sharedPreferences = sharedPreferences, default = false, controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION ) - } - - Spacer(modifier = Modifier.height(16.dp)) - IndependentToggle( - name = "Automatic Ear Detection", - service = service, - functionName = "setEarDetection", - sharedPreferences = sharedPreferences, - default = true, - ) + Text( + text = stringResource(R.string.off_listening_mode_description), + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, top = 0.dp) + ) - // Only show debug when not in BLE-only mode - if (!bleOnlyMode) { - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "", "Accessibility", navController = navController, onClick = { - val intent = Intent(context, CustomDevice::class.java) - context.startActivity(intent) - }) + // an about card- everything but the version number is unknown - will add later if i find out Spacer(modifier = Modifier.height(16.dp)) NavigationButton("debug", "Debug", navController) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 02e9993b..6200348d 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -114,6 +114,10 @@ class AACPManager { HEARING_ASSIST_CONFIG(0x33), ALLOW_OFF_OPTION(0x34), STEM_CONFIG(0x39), + SLEEP_DETECTION_CONFIG(0x35), + ALLOW_AUTO_CONNECT(0x36), // not sure what this does, AUTOMATIC_CONNECTION is the only one used, but this is newer... so ¯\_(ツ)_/¯ + EAR_DETECTION_CONFIG(0x0A), + AUTOMATIC_CONNECTION_CONFIG(0x20), OWNS_CONNECTION(0x06); companion object { @@ -594,6 +598,8 @@ class AACPManager { fun createRequestNotificationPacket(): ByteArray { val opcode = byteArrayOf(Opcodes.REQUEST_NOTIFICATIONS, 0x00) val data = byteArrayOf(0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte()) + // note to self #1: third byte is 0xfd when ear detection is disabled + // note to self #2: this can be sent any time, not just at the start of the aacp connection return opcode + data } diff --git a/android/app/src/main/res/values-zh-rCN/strings.xml b/android/app/src/main/res/values-zh-rCN/strings.xml index 86747678..8ebffb3f 100644 --- a/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/android/app/src/main/res/values-zh-rCN/strings.xml @@ -21,7 +21,6 @@ 头部手势 左耳 右耳 - 根据环境调整媒体音量 对话感知 当你开始与他人交谈时,会降低媒体音量并减少背景噪音。 个性化音量 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 3d84861c..fc1043de 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -21,7 +21,6 @@ Head Gestures Left Right - Adjusts the volume of media in response to your environment Conversational Awareness Lowers media volume and reduces background noise when you start speaking to other people. Personalized Volume @@ -85,4 +84,10 @@ You can customize Transparency mode for your AirPods Pro to help you hear what\'s around you. AirPods Pro automatically reduce your exposure to loud environmental noises when in Transparency and Adaptive mode. Loud Sound Reduction + Call Controls + Connect to this device automatically + When enabled, AirPods will try to connect to this device automatically. Else, they will try to autoconnect only when last connected. + Pause media when falling asleep + Off Listening Mode + When this is on, AirPods listening modes will include an Off option. Loud sound levels are not reduced when the listening mode is set to Off. From 6fd3cc1eb0b05835d61bb12bd92b00641bc810a7 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sat, 20 Sep 2025 01:44:36 +0530 Subject: [PATCH 22/72] android: a small ui fix --- .../librepods/screens/AirPodsSettingsScreen.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index e4c1e650..4c1042cb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -385,18 +385,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, ) Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(R.string.head_gestures).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) - ) - Spacer(modifier = Modifier.height(2.dp)) - NavigationButton(to = "head_tracking", "Head Tracking", navController) + NavigationButton(to = "head_tracking", stringResource(R.string.head_gestures), navController) Spacer(modifier = Modifier.height(16.dp)) NavigationButton(to = "", "Accessibility", navController = navController, onClick = { From 3cca786cf97bd4460b4775e086914adf8d36674b Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sat, 20 Sep 2025 01:45:06 +0530 Subject: [PATCH 23/72] docs: a few more control cmds --- docs/control_commands.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/control_commands.md b/docs/control_commands.md index 32482229..7996f7de 100644 --- a/docs/control_commands.md +++ b/docs/control_commands.md @@ -19,9 +19,10 @@ These commands | Command identifier | Description | Format | |--------------|---------------------|--------| -| 0x01 | Mic Mode | Single value (1 byte) | +| 0x01 | Mic Mode | Single value (1 byte): `0x00` = Automatic, `0x01` = Right, `0x02` = Left | | 0x05 | Button Send Mode | Single value (1 byte) | | 0x06 | Has ownership | Single value (1 byte): `0x01` = own, `0x00` = doesn't own | +| 0x0A | Ear Detection | Single value (1 byte): `0x01` = enabled, `0x02` = disabled | | 0x12 | VoiceTrigger for Siri | Single Value (1 byte): `0x01` = enabled, `0x01` = disabled | | 0x14 | SingleClickMode | Single value (1 byte) | | 0x15 | DoubleClickMode | Single value (1 byte) | @@ -34,6 +35,7 @@ These commands | 0x0D | ListeningMode | Single value (1 byte): 1 = Off, 2 = noise cancellation, 3 = transparency, 4 = adaptive | | 0x1E | AutoAnswerMode | Single value (1 byte) | | 0x1F | Chime Volume | Single value (1 byte): 0 to 100| +| 0x20 | Connect Automatically | Single value (1 byte): `0x01` = enabled, `0x02` = disabled | | 0x23 | VolumeSwipeInterval | Single value (1 byte): 0x00 = Default, `0x01` = Longer, `0x02` = Longest | | 0x24 | Call Management Config | Single value (1 byte) | | 0x25 | VolumeSwipeMode | Single value (1 byte): `0x01` = enabled, `0x02` = disabled | From 5aeb47b835dd7e3a0bfa4fa98bc2969812abbfa2 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sat, 20 Sep 2025 22:55:35 +0530 Subject: [PATCH 24/72] android: add microphone setting also, un-hardcoded strings, and updated text sizes --- .../composables/CallControlSettings.kt | 62 +++--- .../composables/MicrophoneSettings.kt | 204 ++++++++++++++++++ .../composables/PressAndHoldSettings.kt | 12 +- .../screens/AirPodsSettingsScreen.kt | 4 +- android/app/src/main/res/values/strings.xml | 10 + 5 files changed, 254 insertions(+), 38 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt index 56bd43f2..9e966538 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -91,8 +91,8 @@ fun CallControlSettings() { }?.value ?: byteArrayOf(0x00, 0x03) var flipped by remember { mutableStateOf(callControlEnabledValue.contentEquals(byteArrayOf(0x00, 0x02))) } - var singlePressAction by remember { mutableStateOf(if (flipped) "Double Press" else "Single Press") } - var doublePressAction by remember { mutableStateOf(if (flipped) "Single Press" else "Double Press") } + var singlePressAction by remember { mutableStateOf(if (flipped) "Press Twice" else "Press Once") } + var doublePressAction by remember { mutableStateOf(if (flipped) "Press Once" else "Press Twice") } var showSinglePressDropdown by remember { mutableStateOf(false) } var showDoublePressDropdown by remember { mutableStateOf(false) } @@ -103,8 +103,8 @@ fun CallControlSettings() { AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG) { val newFlipped = controlCommand.value.contentEquals(byteArrayOf(0x00, 0x02)) flipped = newFlipped - singlePressAction = if (newFlipped) "Double Press" else "Single Press" - doublePressAction = if (newFlipped) "Single Press" else "Double Press" + singlePressAction = if (newFlipped) "Press Twice" else "Press Once" + doublePressAction = if (newFlipped) "Press Once" else "Press Twice" Log.d("CallControlSettings", "Control command received, flipped: $newFlipped") } } @@ -134,19 +134,19 @@ fun CallControlSettings() { modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(55.dp), + .height(50.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Answer call", - fontSize = 18.sp, + text = stringResource(R.string.answer_call), + fontSize = 16.sp, color = textColor, modifier = Modifier.padding(bottom = 4.dp) ) Text( - text = "Single Press", - fontSize = 18.sp, + text = stringResource(R.string.press_once), + fontSize = 16.sp, color = textColor.copy(alpha = 0.6f) ) } @@ -161,13 +161,13 @@ fun CallControlSettings() { modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(55.dp), + .height(50.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Mute/Unmute", - fontSize = 18.sp, + text = stringResource(R.string.mute_unmute), + fontSize = 16.sp, color = textColor, modifier = Modifier.padding(bottom = 4.dp) ) @@ -177,8 +177,8 @@ fun CallControlSettings() { verticalAlignment = Alignment.CenterVertically ) { Text( - text = singlePressAction, - fontSize = 18.sp, + text = if (singlePressAction == "Press Once") stringResource(R.string.press_once) else stringResource(R.string.press_twice), + fontSize = 16.sp, color = textColor.copy(alpha = 0.8f) ) Icon( @@ -193,19 +193,19 @@ fun CallControlSettings() { onDismissRequest = { showSinglePressDropdown = false } ) { DropdownMenuItem( - text = { Text("Single Press") }, + text = { Text(stringResource(R.string.press_once)) }, onClick = { - singlePressAction = "Single Press" - doublePressAction = "Double Press" + singlePressAction = "Press Once" + doublePressAction = "Press Twice" showSinglePressDropdown = false service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03)) } ) DropdownMenuItem( - text = { Text("Double Press") }, + text = { Text(stringResource(R.string.press_twice)) }, onClick = { - singlePressAction = "Double Press" - doublePressAction = "Single Press" + singlePressAction = "Press Twice" + doublePressAction = "Press Once" showSinglePressDropdown = false service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02)) } @@ -224,13 +224,13 @@ fun CallControlSettings() { modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(55.dp), + .height(50.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Hang Up", - fontSize = 18.sp, + text = stringResource(R.string.hang_up), + fontSize = 16.sp, color = textColor, modifier = Modifier.padding(bottom = 4.dp) ) @@ -240,8 +240,8 @@ fun CallControlSettings() { verticalAlignment = Alignment.CenterVertically ) { Text( - text = doublePressAction, - fontSize = 18.sp, + text = if (doublePressAction == "Press Once") stringResource(R.string.press_once) else stringResource(R.string.press_twice), + fontSize = 16.sp, color = textColor.copy(alpha = 0.8f) ) Icon( @@ -256,19 +256,19 @@ fun CallControlSettings() { onDismissRequest = { showDoublePressDropdown = false } ) { DropdownMenuItem( - text = { Text("Single Press") }, + text = { Text(stringResource(R.string.press_once)) }, onClick = { - doublePressAction = "Single Press" - singlePressAction = "Double Press" + doublePressAction = "Press Once" + singlePressAction = "Press Twice" showDoublePressDropdown = false service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02)) } ) DropdownMenuItem( - text = { Text("Double Press") }, + text = { Text(stringResource(R.string.press_twice)) }, onClick = { - doublePressAction = "Double Press" - singlePressAction = "Single Press" + doublePressAction = "Press Twice" + singlePressAction = "Press Once" showDoublePressDropdown = false service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03)) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt new file mode 100644 index 00000000..88996b14 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -0,0 +1,204 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +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 me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun MicrophoneSettings() { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(top = 2.dp) + ) { + val service = ServiceManager.getService()!! + val micModeValue = service.aacpManager.controlCommandStatusList.find { + it.identifier == AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE + }?.value?.get(0) ?: 0x00.toByte() + + var selectedMode by remember { + mutableStateOf( + when (micModeValue) { + 0x00.toByte() -> "Automatic" + 0x01.toByte() -> "Always Right" + 0x02.toByte() -> "Always Left" + else -> "Automatic" + } + ) + } + var showDropdown by remember { mutableStateOf(false) } + + val listener = object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) == + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE) { + selectedMode = when (controlCommand.value.get(0)) { + 0x00.toByte() -> "Automatic" + 0x01.toByte() -> "Always Right" + 0x02.toByte() -> "Always Left" + else -> "Automatic" + } + Log.d("MicrophoneSettings", "Microphone mode received: $selectedMode") + } + } + } + + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE, + listener + ) + } + + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE, + listener + ) + } + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp) + .height(55.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.microphone_mode), + fontSize = 16.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + Box { + Row( + modifier = Modifier.clickable { showDropdown = true }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedMode, + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f) + ) + Icon( + Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = textColor.copy(alpha = 0.6f) + ) + } + DropdownMenu( + expanded = showDropdown, + onDismissRequest = { showDropdown = false } + ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.microphone_automatic)) }, + onClick = { + selectedMode = "Automatic" + showDropdown = false + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, + byteArrayOf(0x00) + ) + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.microphone_always_right)) }, + onClick = { + selectedMode = "Always Right" + showDropdown = false + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, + byteArrayOf(0x01) + ) + } + ) + DropdownMenuItem( + text = { Text(stringResource(R.string.microphone_always_left)) }, + onClick = { + selectedMode = "Always Left" + showDropdown = false + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, + byteArrayOf(0x02) + ) + } + ) + } + } + } + } +} + +@Preview +@Composable +fun MicrophoneSettingsPreview() { + MicrophoneSettings() +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt index eb83542e..53af7195 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt @@ -111,7 +111,7 @@ fun PressAndHoldSettings(navController: NavController) { Box( modifier = Modifier .fillMaxWidth() - .height(55.dp) + .height(50.dp) .background(animatedLeftBackgroundColor, RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp)) .pointerInput(Unit) { detectTapGestures( @@ -135,7 +135,7 @@ fun PressAndHoldSettings(navController: NavController) { Text( text = stringResource(R.string.left), style = TextStyle( - fontSize = 18.sp, + fontSize = 16.sp, color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), @@ -144,7 +144,7 @@ fun PressAndHoldSettings(navController: NavController) { Text( text = leftActionText, style = TextStyle( - fontSize = 18.sp, + fontSize = 16.sp, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), @@ -171,7 +171,7 @@ fun PressAndHoldSettings(navController: NavController) { Box( modifier = Modifier .fillMaxWidth() - .height(55.dp) + .height(50.dp) .background(animatedRightBackgroundColor, RoundedCornerShape(bottomEnd = 14.dp, bottomStart = 14.dp)) .pointerInput(Unit) { detectTapGestures( @@ -195,7 +195,7 @@ fun PressAndHoldSettings(navController: NavController) { Text( text = stringResource(R.string.right), style = TextStyle( - fontSize = 18.sp, + fontSize = 16.sp, color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), @@ -204,7 +204,7 @@ fun PressAndHoldSettings(navController: NavController) { Text( text = rightActionText, style = TextStyle( - fontSize = 18.sp, + fontSize = 16.sp, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 4c1042cb..0f449a43 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -97,6 +97,7 @@ import me.kavishdevar.librepods.composables.BatteryView import me.kavishdevar.librepods.composables.CallControlSettings import me.kavishdevar.librepods.composables.ConnectionSettings import me.kavishdevar.librepods.composables.IndependentToggle +import me.kavishdevar.librepods.composables.MicrophoneSettings import me.kavishdevar.librepods.composables.NameField import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.NoiseControlSettings @@ -373,7 +374,8 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, Spacer(modifier = Modifier.height(16.dp)) ConnectionSettings() - // microphone settings + Spacer(modifier = Modifier.height(16.dp)) + MicrophoneSettings() Spacer(modifier = Modifier.height(16.dp)) IndependentToggle( diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index fc1043de..e5699339 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -90,4 +90,14 @@ Pause media when falling asleep Off Listening Mode When this is on, AirPods listening modes will include an Off option. Loud sound levels are not reduced when the listening mode is set to Off. + Microphone + Microphone Mode + Automatic + Always Right + Always Left + Answer call + Mute/Unmute + Hang Up + Press Once + Press Twice From ecfdc05dbfbbbe2b5d0c971a6da41471f6f3c4bb Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 21 Sep 2025 01:34:42 +0530 Subject: [PATCH 25/72] android: improve dropdowns ai generated --- .../composables/CallControlSettings.kt | 301 ++++++++++---- .../composables/MicrophoneSettings.kt | 368 +++++++++++++++--- 2 files changed, 539 insertions(+), 130 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt index 9e966538..76d60701 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -22,36 +22,39 @@ package me.kavishdevar.librepods.composables import android.util.Log import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.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.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf 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.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight @@ -89,27 +92,55 @@ fun CallControlSettings() { val callControlEnabledValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG }?.value ?: byteArrayOf(0x00, 0x03) - - var flipped by remember { mutableStateOf(callControlEnabledValue.contentEquals(byteArrayOf(0x00, 0x02))) } - var singlePressAction by remember { mutableStateOf(if (flipped) "Press Twice" else "Press Once") } - var doublePressAction by remember { mutableStateOf(if (flipped) "Press Once" else "Press Twice") } + + val pressOnceText = stringResource(R.string.press_once) + val pressTwiceText = stringResource(R.string.press_twice) + + var flipped by remember { + mutableStateOf( + callControlEnabledValue.contentEquals( + byteArrayOf( + 0x00, + 0x02 + ) + ) + ) + } + var singlePressAction by remember { mutableStateOf(if (flipped) pressTwiceText else pressOnceText) } + var doublePressAction by remember { mutableStateOf(if (flipped) pressOnceText else pressTwiceText) } + var showSinglePressDropdown by remember { mutableStateOf(false) } + var touchOffsetSingle by remember { mutableStateOf(null) } + var boxPositionSingle by remember { mutableStateOf(Offset.Zero) } + var lastDismissTimeSingle by remember { mutableLongStateOf(0L) } + var parentHoveredIndexSingle by remember { mutableStateOf(null) } + var parentDragActiveSingle by remember { mutableStateOf(false) } + var showDoublePressDropdown by remember { mutableStateOf(false) } + var touchOffsetDouble by remember { mutableStateOf(null) } + var boxPositionDouble by remember { mutableStateOf(Offset.Zero) } + var lastDismissTimeDouble by remember { mutableLongStateOf(0L) } + var parentHoveredIndexDouble by remember { mutableStateOf(null) } + var parentDragActiveDouble by remember { mutableStateOf(false) } LaunchedEffect(Unit) { val listener = object : AACPManager.ControlCommandListener { override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) == - AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG) { + if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) == + AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG + ) { val newFlipped = controlCommand.value.contentEquals(byteArrayOf(0x00, 0x02)) flipped = newFlipped - singlePressAction = if (newFlipped) "Press Twice" else "Press Once" - doublePressAction = if (newFlipped) "Press Once" else "Press Twice" - Log.d("CallControlSettings", "Control command received, flipped: $newFlipped") + singlePressAction = if (newFlipped) pressTwiceText else pressOnceText + doublePressAction = if (newFlipped) pressOnceText else pressTwiceText + Log.d( + "CallControlSettings", + "Control command received, flipped: $newFlipped" + ) } } } - + service.aacpManager.registerControlCommandListener( AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG, listener @@ -121,11 +152,13 @@ fun CallControlSettings() { service.aacpManager.controlCommandListeners[AACPManager.Companion.ControlCommandIdentifiers.CALL_MANAGEMENT_CONFIG]?.clear() } } - LaunchedEffect(flipped) { Log.d("CallControlSettings", "Call control flipped: $flipped") } + val density = LocalDensity.current + val itemHeightPx = with(density) { 48.dp.toPx() } + Column( modifier = Modifier .fillMaxWidth() @@ -161,7 +194,66 @@ fun CallControlSettings() { modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(50.dp), + .height(50.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val now = System.currentTimeMillis() + if (showSinglePressDropdown) { + showSinglePressDropdown = false + lastDismissTimeSingle = now + } else { + if (now - lastDismissTimeSingle > 250L) { + touchOffsetSingle = offset + showSinglePressDropdown = true + } + } + } + } + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + val now = System.currentTimeMillis() + touchOffsetSingle = offset + if (!showSinglePressDropdown && now - lastDismissTimeSingle > 250L) { + showSinglePressDropdown = true + } + lastDismissTimeSingle = now + parentDragActiveSingle = true + parentHoveredIndexSingle = 0 + }, + onDrag = { change, _ -> + val current = change.position + val touch = touchOffsetSingle ?: current + val posInPopupY = current.y - touch.y + val idx = (posInPopupY / itemHeightPx).toInt() + parentHoveredIndexSingle = idx + }, + onDragEnd = { + parentDragActiveSingle = false + parentHoveredIndexSingle?.let { idx -> + val options = listOf(pressOnceText, pressTwiceText) + if (idx in options.indices) { + val option = options[idx] + singlePressAction = option + doublePressAction = + if (option == pressOnceText) pressTwiceText else pressOnceText + showSinglePressDropdown = false + lastDismissTimeSingle = System.currentTimeMillis() + val bytes = if (option == pressOnceText) byteArrayOf( + 0x00, + 0x03 + ) else byteArrayOf(0x00, 0x02) + service.aacpManager.sendControlCommand(0x24, bytes) + } + } + parentHoveredIndexSingle = null + }, + onDragCancel = { + parentDragActiveSingle = false + parentHoveredIndexSingle = null + } + ) + }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -171,13 +263,16 @@ fun CallControlSettings() { color = textColor, modifier = Modifier.padding(bottom = 4.dp) ) - Box { + Box( + modifier = Modifier.onGloballyPositioned { coordinates -> + boxPositionSingle = coordinates.positionInParent() + } + ) { Row( - modifier = Modifier.clickable { showSinglePressDropdown = true }, verticalAlignment = Alignment.CenterVertically ) { Text( - text = if (singlePressAction == "Press Once") stringResource(R.string.press_once) else stringResource(R.string.press_twice), + text = singlePressAction, fontSize = 16.sp, color = textColor.copy(alpha = 0.8f) ) @@ -188,29 +283,31 @@ fun CallControlSettings() { tint = textColor.copy(alpha = 0.6f) ) } - DropdownMenu( + + DragSelectableDropdown( expanded = showSinglePressDropdown, - onDismissRequest = { showSinglePressDropdown = false } - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.press_once)) }, - onClick = { - singlePressAction = "Press Once" - doublePressAction = "Press Twice" - showSinglePressDropdown = false - service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03)) - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.press_twice)) }, - onClick = { - singlePressAction = "Press Twice" - doublePressAction = "Press Once" - showSinglePressDropdown = false - service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02)) - } - ) - } + onDismissRequest = { + showSinglePressDropdown = false + lastDismissTimeSingle = System.currentTimeMillis() + }, + options = listOf(pressOnceText, pressTwiceText), + selectedOption = singlePressAction, + touchOffset = touchOffsetSingle, + boxPosition = boxPositionSingle, + externalHoveredIndex = parentHoveredIndexSingle, + externalDragActive = parentDragActiveSingle, + onOptionSelected = { option -> + singlePressAction = option + doublePressAction = + if (option == pressOnceText) pressTwiceText else pressOnceText + showSinglePressDropdown = false + val bytes = if (option == pressOnceText) byteArrayOf( + 0x00, + 0x03 + ) else byteArrayOf(0x00, 0x02) + service.aacpManager.sendControlCommand(0x24, bytes) + } + ) } } HorizontalDivider( @@ -224,7 +321,66 @@ fun CallControlSettings() { modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(50.dp), + .height(50.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val now = System.currentTimeMillis() + if (showDoublePressDropdown) { + showDoublePressDropdown = false + lastDismissTimeDouble = now + } else { + if (now - lastDismissTimeDouble > 250L) { + touchOffsetDouble = offset + showDoublePressDropdown = true + } + } + } + } + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + val now = System.currentTimeMillis() + touchOffsetDouble = offset + if (!showDoublePressDropdown && now - lastDismissTimeDouble > 250L) { + showDoublePressDropdown = true + } + lastDismissTimeDouble = now + parentDragActiveDouble = true + parentHoveredIndexDouble = 0 + }, + onDrag = { change, _ -> + val current = change.position + val touch = touchOffsetDouble ?: current + val posInPopupY = current.y - touch.y + val idx = (posInPopupY / itemHeightPx).toInt() + parentHoveredIndexDouble = idx + }, + onDragEnd = { + parentDragActiveDouble = false + parentHoveredIndexDouble?.let { idx -> + val options = listOf(pressOnceText, pressTwiceText) + if (idx in options.indices) { + val option = options[idx] + doublePressAction = option + singlePressAction = + if (option == pressOnceText) pressTwiceText else pressOnceText + showDoublePressDropdown = false + lastDismissTimeDouble = System.currentTimeMillis() + val bytes = if (option == pressOnceText) byteArrayOf( + 0x00, + 0x02 + ) else byteArrayOf(0x00, 0x03) + service.aacpManager.sendControlCommand(0x24, bytes) + } + } + parentHoveredIndexDouble = null + }, + onDragCancel = { + parentDragActiveDouble = false + parentHoveredIndexDouble = null + } + ) + }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -234,13 +390,16 @@ fun CallControlSettings() { color = textColor, modifier = Modifier.padding(bottom = 4.dp) ) - Box { + Box( + modifier = Modifier.onGloballyPositioned { coordinates -> + boxPositionDouble = coordinates.positionInParent() + } + ) { Row( - modifier = Modifier.clickable { showDoublePressDropdown = true }, verticalAlignment = Alignment.CenterVertically ) { Text( - text = if (doublePressAction == "Press Once") stringResource(R.string.press_once) else stringResource(R.string.press_twice), + text = doublePressAction, fontSize = 16.sp, color = textColor.copy(alpha = 0.8f) ) @@ -251,29 +410,31 @@ fun CallControlSettings() { tint = textColor.copy(alpha = 0.6f) ) } - DropdownMenu( + + DragSelectableDropdown( expanded = showDoublePressDropdown, - onDismissRequest = { showDoublePressDropdown = false } - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.press_once)) }, - onClick = { - doublePressAction = "Press Once" - singlePressAction = "Press Twice" - showDoublePressDropdown = false - service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x02)) - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.press_twice)) }, - onClick = { - doublePressAction = "Press Twice" - singlePressAction = "Press Once" - showDoublePressDropdown = false - service.aacpManager.sendControlCommand(0x24, byteArrayOf(0x00, 0x03)) - } - ) - } + onDismissRequest = { + showDoublePressDropdown = false + lastDismissTimeDouble = System.currentTimeMillis() + }, + options = listOf(pressOnceText, pressTwiceText), + selectedOption = doublePressAction, + touchOffset = touchOffsetDouble, + boxPosition = boxPositionDouble, + externalHoveredIndex = parentHoveredIndexDouble, + externalDragActive = parentDragActiveDouble, + onOptionSelected = { option -> + doublePressAction = option + singlePressAction = + if (option == pressOnceText) pressTwiceText else pressOnceText + showDoublePressDropdown = false + val bytes = if (option == pressOnceText) byteArrayOf( + 0x00, + 0x02 + ) else byteArrayOf(0x00, 0x03) + service.aacpManager.sendControlCommand(0x24, bytes) + } + ) } } } @@ -284,4 +445,4 @@ fun CallControlSettings() { @Composable fun CallControlSettingsPreview() { CallControlSettings() -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt index 88996b14..5ff5f384 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -21,14 +21,22 @@ package me.kavishdevar.librepods.composables import android.util.Log +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress +import androidx.compose.foundation.gestures.detectTapGestures +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.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -37,27 +45,37 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager @@ -79,8 +97,8 @@ fun MicrophoneSettings() { val micModeValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE }?.value?.get(0) ?: 0x00.toByte() - - var selectedMode by remember { + + var selectedMode by remember { mutableStateOf( when (micModeValue) { 0x00.toByte() -> "Automatic" @@ -91,22 +109,27 @@ fun MicrophoneSettings() { ) } var showDropdown by remember { mutableStateOf(false) } - + var touchOffset by remember { mutableStateOf(null) } + var boxPosition by remember { mutableStateOf(Offset.Zero) } + var lastDismissTime by remember { mutableLongStateOf(0L) } + val reopenThresholdMs = 250L + val listener = object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) == - AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE) { - selectedMode = when (controlCommand.value.get(0)) { - 0x00.toByte() -> "Automatic" - 0x01.toByte() -> "Always Right" - 0x02.toByte() -> "Always Left" - else -> "Automatic" - } - Log.d("MicrophoneSettings", "Microphone mode received: $selectedMode") + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (AACPManager.Companion.ControlCommandIdentifiers.fromByte(controlCommand.identifier) == + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE + ) { + selectedMode = when (controlCommand.value[0]) { + 0x00.toByte() -> "Automatic" + 0x01.toByte() -> "Always Right" + 0x02.toByte() -> "Always Left" + else -> "Automatic" } + Log.d("MicrophoneSettings", "Microphone mode received: $selectedMode") } } - + } + LaunchedEffect(Unit) { service.aacpManager.registerControlCommandListener( AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE, @@ -123,11 +146,84 @@ fun MicrophoneSettings() { } } + val density = LocalDensity.current + val itemHeightPx = with(density) { 48.dp.toPx() } + var parentHoveredIndex by remember { mutableStateOf(null) } + var parentDragActive by remember { mutableStateOf(false) } + val microphoneAutomaticText = stringResource(R.string.microphone_automatic) + val microphoneAlwaysRightText = stringResource(R.string.microphone_always_right) + val microphoneAlwaysLeftText = stringResource(R.string.microphone_always_left) Row( modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(55.dp), + .height(55.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val now = System.currentTimeMillis() + if (showDropdown) { + showDropdown = false + lastDismissTime = now + } else { + if (now - lastDismissTime > reopenThresholdMs) { + touchOffset = offset + showDropdown = true + } + } + } + } + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + val now = System.currentTimeMillis() + touchOffset = offset + if (!showDropdown && now - lastDismissTime > reopenThresholdMs) { + showDropdown = true + } + lastDismissTime = now + parentDragActive = true + parentHoveredIndex = 0 + }, + onDrag = { change, _ -> + val current = change.position + val touch = touchOffset ?: current + val posInPopupY = current.y - touch.y + val idx = (posInPopupY / itemHeightPx).toInt() + parentHoveredIndex = idx + }, + onDragEnd = { + parentDragActive = false + parentHoveredIndex?.let { idx -> + val options = listOf( + microphoneAutomaticText, + microphoneAlwaysRightText, + microphoneAlwaysLeftText + ) + if (idx in options.indices) { + val option = options[idx] + selectedMode = option + showDropdown = false + lastDismissTime = System.currentTimeMillis() + val byteValue = when (option) { + options[0] -> 0x00 + options[1] -> 0x01 + options[2] -> 0x02 + else -> 0x00 + } + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, + byteArrayOf(byteValue.toByte()) + ) + } + } + parentHoveredIndex = null + }, + onDragCancel = { + parentDragActive = false + parentHoveredIndex = null + } + ) + }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -137,9 +233,12 @@ fun MicrophoneSettings() { color = textColor, modifier = Modifier.padding(bottom = 4.dp) ) - Box { + Box( + modifier = Modifier.onGloballyPositioned { coordinates -> + boxPosition = coordinates.positionInParent() + } + ) { Row( - modifier = Modifier.clickable { showDropdown = true }, verticalAlignment = Alignment.CenterVertically ) { Text( @@ -154,44 +253,42 @@ fun MicrophoneSettings() { tint = textColor.copy(alpha = 0.6f) ) } - DropdownMenu( + + val microphoneAutomaticText = stringResource(R.string.microphone_automatic) + val microphoneAlwaysRightText = stringResource(R.string.microphone_always_right) + val microphoneAlwaysLeftText = stringResource(R.string.microphone_always_left) + + DragSelectableDropdown( expanded = showDropdown, - onDismissRequest = { showDropdown = false } - ) { - DropdownMenuItem( - text = { Text(stringResource(R.string.microphone_automatic)) }, - onClick = { - selectedMode = "Automatic" - showDropdown = false - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, - byteArrayOf(0x00) - ) - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.microphone_always_right)) }, - onClick = { - selectedMode = "Always Right" - showDropdown = false - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, - byteArrayOf(0x01) - ) - } - ) - DropdownMenuItem( - text = { Text(stringResource(R.string.microphone_always_left)) }, - onClick = { - selectedMode = "Always Left" - showDropdown = false - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, - byteArrayOf(0x02) - ) + onDismissRequest = { + showDropdown = false + lastDismissTime = System.currentTimeMillis() + }, + options = listOf( + microphoneAutomaticText, + microphoneAlwaysRightText, + microphoneAlwaysLeftText + ), + selectedOption = selectedMode, + touchOffset = touchOffset, + boxPosition = boxPosition, + externalHoveredIndex = parentHoveredIndex, + externalDragActive = parentDragActive, + onOptionSelected = { option -> + selectedMode = option + showDropdown = false + val byteValue = when (option) { + microphoneAutomaticText -> 0x00 + microphoneAlwaysRightText -> 0x01 + microphoneAlwaysLeftText -> 0x02 + else -> 0x00 } - ) - } + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, + byteArrayOf(byteValue.toByte()) + ) + } + ) } } } @@ -201,4 +298,155 @@ fun MicrophoneSettings() { @Composable fun MicrophoneSettingsPreview() { MicrophoneSettings() -} \ No newline at end of file +} + +@Composable +fun DragSelectableDropdown( + expanded: Boolean, + onDismissRequest: () -> Unit, + options: List, + selectedOption: String, + touchOffset: Offset?, + boxPosition: Offset, + onOptionSelected: (String) -> Unit, + externalHoveredIndex: Int? = null, + externalDragActive: Boolean = false, + modifier: Modifier = Modifier +) { + if (expanded) { + val relativeOffset = touchOffset?.let { it - boxPosition } ?: Offset.Zero + Popup( + offset = IntOffset(relativeOffset.x.toInt(), relativeOffset.y.toInt()), + onDismissRequest = onDismissRequest + ) { + AnimatedVisibility( + visible = true, + enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut() + ) { + Card( + modifier = modifier + .padding(8.dp) + .width(300.dp) + .background( + if (isSystemInDarkTheme()) Color(0xFF2C2C2E) else Color(0xFFFFFFFF) + ) + .clip(RoundedCornerShape(8.dp)), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + var hoveredIndex by remember { mutableStateOf(null) } + val itemHeight = 48.dp + + var popupSize by remember { mutableStateOf(IntSize(0, 0)) } + var lastDragPosition by remember { mutableStateOf(null) } + + LaunchedEffect(externalHoveredIndex, externalDragActive) { + if (externalDragActive) { + hoveredIndex = externalHoveredIndex + } + } + + Column( + modifier = Modifier + .onGloballyPositioned { coordinates -> + popupSize = coordinates.size + } + .pointerInput(popupSize) { + detectDragGestures( + onDragStart = { offset -> + hoveredIndex = (offset.y / itemHeight.toPx()).toInt() + lastDragPosition = offset + }, + onDrag = { change, _ -> + val y = change.position.y + hoveredIndex = (y / itemHeight.toPx()).toInt() + lastDragPosition = change.position + }, + onDragEnd = { + val pos = lastDragPosition + val withinBounds = pos != null && + pos.x >= 0f && pos.y >= 0f && + pos.x <= popupSize.width.toFloat() && pos.y <= popupSize.height.toFloat() + + if (withinBounds) { + hoveredIndex?.let { idx -> + if (idx in options.indices) { + onOptionSelected(options[idx]) + } + } + onDismissRequest() + } else { + hoveredIndex = null + } + } + ) + } + ) { + options.forEachIndexed { index, text -> + val isHovered = + if (externalDragActive && externalHoveredIndex != null) { + index == externalHoveredIndex + } else { + index == hoveredIndex + } + Box( + modifier = Modifier + .fillMaxWidth() + .height(itemHeight) + .background( + if (isHovered) (if (isSystemInDarkTheme()) Color(0xFF3A3A3C) else Color( + 0xFFD1D1D6 + )) else Color.Transparent + ) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + onOptionSelected(text) + onDismissRequest() + } + .padding(horizontal = 12.dp), + contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text, + color = if (isSystemInDarkTheme()) Color.White else Color.Black + ) + Checkbox( + checked = text == selectedOption, + onCheckedChange = { onOptionSelected(text) }, + 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 + ) + ) + } + } + + if (index != options.lastIndex) { + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) + ) + } + } + } + } + } + } + } +} From 3ace0e18317d02ef92c60ad6c76ef5c221c54cbe Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 21 Sep 2025 21:44:54 +0530 Subject: [PATCH 26/72] android: move attmanager to service to avoid trying to connect multiple times --- .../librepods/composables/AudioSettings.kt | 21 +------- .../composables/ConnectionSettings.kt | 1 - .../composables/LoudSoundReductionSwitch.kt | 18 +++---- .../screens/AccessibilitySettingsScreen.kt | 33 ++++--------- .../librepods/services/AirPodsService.kt | 7 +++ .../kavishdevar/librepods/utils/ATTManager.kt | 49 ++++++++++++++----- 6 files changed, 64 insertions(+), 65 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt index 25a0c4eb..059dc103 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt @@ -42,31 +42,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTManager import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun AudioSettings() { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black - val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected")) - DisposableEffect(attManager) { - onDispose { - try { - attManager.disconnect() - } catch (e: Exception) { - Log.w("AirPodsAudioSettings", "Error while disconnecting ATTManager: ${e.message}") - } - } - } - LaunchedEffect(Unit) { - Log.d("AirPodsAudioSettings", "Connecting to ATT...") - try { - attManager.connect() - } catch (e: Exception) { - Log.w("AirPodsAudioSettings", "Error while connecting ATTManager: ${e.message}") - } - } Text( text = stringResource(R.string.audio).uppercase(), @@ -103,7 +84,7 @@ fun AudioSettings() { .padding(start = 12.dp, end = 0.dp) ) - LoudSoundReductionSwitch(attManager) + LoudSoundReductionSwitch() HorizontalDivider( thickness = 1.5.dp, color = Color(0x40888888), diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt index 58ffa147..28f796e6 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt @@ -42,7 +42,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTManager import kotlin.io.encoding.ExperimentalEncodingApi @Composable diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt index 1d0ad9d6..b79bd582 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt @@ -50,26 +50,26 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.delay import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.ATTManager +import me.kavishdevar.librepods.utils.ATTHandles import kotlin.io.encoding.ExperimentalEncodingApi @Composable -fun LoudSoundReductionSwitch(attManager: ATTManager) { +fun LoudSoundReductionSwitch() { var loudSoundReductionEnabled by remember { mutableStateOf( false ) } + val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") LaunchedEffect(Unit) { - while (attManager.socket?.isConnected != true) { - delay(100) - } - attManager.enableNotifications(0x1b) + attManager.enableNotifications(ATTHandles.LOUD_SOUND_REDUCTION) var parsed = false for (attempt in 1..3) { try { - val data = attManager.read(0x1b) + val data = attManager.read(ATTHandles.LOUD_SOUND_REDUCTION) if (data.size == 2) { loudSoundReductionEnabled = data[1].toInt() != 0 Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}") @@ -90,7 +90,7 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) { LaunchedEffect(loudSoundReductionEnabled) { if (attManager.socket?.isConnected != true) return@LaunchedEffect - attManager.write(0x1b, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0)) + attManager.write(ATTHandles.LOUD_SOUND_REDUCTION, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0)) } val loudSoundListener = remember { @@ -107,12 +107,12 @@ fun LoudSoundReductionSwitch(attManager: ATTManager) { } LaunchedEffect(Unit) { - attManager.registerListener(0x1b, loudSoundListener) + attManager.registerListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener) } DisposableEffect(Unit) { onDispose { - attManager.unregisterListener(0x1b, loudSoundListener) + attManager.unregisterListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index d9204612..9878f1f9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -86,6 +86,7 @@ import androidx.compose.ui.unit.sp import dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope @@ -102,6 +103,7 @@ import me.kavishdevar.librepods.composables.ToneVolumeSlider import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.ATTManager +import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.RadareOffsetFinder import java.io.IOException @@ -123,7 +125,7 @@ fun AccessibilitySettingsScreen() { val verticalScrollState = rememberScrollState() val hazeState = remember { HazeState() } val snackbarHostState = remember { SnackbarHostState() } - val attManager = ATTManager(ServiceManager.getService()?.device?: throw IllegalStateException("No device connected")) + val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") // get the AACP manager if available (used for EQ read/write) val aacpManager = remember { ServiceManager.getService()?.aacpManager } val context = LocalContext.current @@ -135,17 +137,6 @@ fun AccessibilitySettingsScreen() { val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) val labelTextColor = if (isDarkTheme) Color.White else Color.Black - DisposableEffect(attManager) { - onDispose { - Log.d(TAG, "Disconnecting from ATT...") - try { - attManager.disconnect() - } catch (e: Exception) { - Log.w(TAG, "Error while disconnecting ATTManager: ${e.message}") - } - } - } - Scaffold( containerColor = if (isSystemInDarkTheme()) Color( 0xFF000000 @@ -198,6 +189,7 @@ fun AccessibilitySettingsScreen() { ) { paddingValues -> Column( modifier = Modifier + .hazeSource(hazeState) .fillMaxSize() .padding(paddingValues) .padding(horizontal = 16.dp) @@ -367,20 +359,15 @@ fun AccessibilitySettingsScreen() { DisposableEffect(Unit) { onDispose { - attManager.unregisterListener(0x18, transparencyListener) + attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener) } } LaunchedEffect(Unit) { Log.d(TAG, "Connecting to ATT...") try { - attManager.connect() - while (attManager.socket?.isConnected != true) { - delay(100) - } - - attManager.enableNotifications(0x18) - attManager.registerListener(0x18, transparencyListener) + attManager.enableNotifications(ATTHandles.TRANSPARENCY) + attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener) // If we have an AACP manager, prefer its EQ data to populate EQ controls first try { @@ -407,7 +394,7 @@ fun AccessibilitySettingsScreen() { for (attempt in 1..3) { initialReadAttempts.value = attempt try { - val data = attManager.read(0x18) + val data = attManager.read(ATTHandles.TRANSPARENCY) parsedSettings = parseTransparencySettingsResponse(data = data) if (parsedSettings != null) { Log.d(TAG, "Parsed settings on attempt $attempt") @@ -569,7 +556,7 @@ fun AccessibilitySettingsScreen() { ToneVolumeSlider() SinglePodANCSwitch() VolumeControlSwitch() - LoudSoundReductionSwitch(attManager) + LoudSoundReductionSwitch() DropdownMenuComponent( label = "Press Speed", @@ -1113,7 +1100,7 @@ private fun sendTransparencySettings( val data = buffer.array() attManager.write( - 0x18, + ATTHandles.TRANSPARENCY, value = data ) } catch (e: IOException) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 49ca9a86..76e31e1a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -88,6 +88,7 @@ import me.kavishdevar.librepods.constants.StemAction import me.kavishdevar.librepods.constants.isHeadTrackingData import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType +import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.BLEManager import me.kavishdevar.librepods.utils.BluetoothConnectionManager import me.kavishdevar.librepods.utils.CrossDevice @@ -148,6 +149,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList var macAddress = "" var localMac = "" lateinit var aacpManager: AACPManager + var attManager: ATTManager? = null var cameraActive = false private var disconnectedBecauseReversed = false data class ServiceConfig( @@ -634,6 +636,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList isConnectedLocally = false popupShown = false updateNotificationContent(false) + attManager?.disconnect() + attManager = null } } } @@ -2294,6 +2298,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList BluetoothConnectionManager.setCurrentConnection(socket, device) + attManager = ATTManager(device) + attManager!!.connect() + updateNotificationContent( true, config.deviceName, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index f3709576..09fceff7 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -37,6 +37,18 @@ import java.io.OutputStream import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit +enum class ATTHandles(val value: Int) { + TRANSPARENCY(0x18), + LOUD_SOUND_REDUCTION(0x1b), + HEARING_AID(0x2a), +} + +enum class ATTCCCDHandles(val value: Int) { + TRANSPARENCY(ATTHandles.TRANSPARENCY.value + 1), + LOUD_SOUND_REDUCTION(ATTHandles.LOUD_SOUND_REDUCTION.value + 1), + HEARING_AID(ATTHandles.HEARING_AID.value + 1), +} + class ATTManager(private val device: BluetoothDevice) { companion object { private const val TAG = "ATTManager" @@ -103,30 +115,43 @@ class ATTManager(private val device: BluetoothDevice) { } } - fun registerListener(handle: Int, listener: (ByteArray) -> Unit) { - listeners.getOrPut(handle) { mutableListOf() }.add(listener) + fun registerListener(handle: ATTHandles, listener: (ByteArray) -> Unit) { + listeners.getOrPut(handle.value) { mutableListOf() }.add(listener) } - fun unregisterListener(handle: Int, listener: (ByteArray) -> Unit) { - listeners[handle]?.remove(listener) + fun unregisterListener(handle: ATTHandles, listener: (ByteArray) -> Unit) { + listeners[handle.value]?.remove(listener) } - fun enableNotifications(handle: Int) { - write(handle + 1, byteArrayOf(0x01, 0x00)) + fun enableNotifications(handle: ATTHandles) { + write(ATTCCCDHandles.valueOf(handle.name), byteArrayOf(0x01, 0x00)) } - fun read(handle: Int): ByteArray { - val lsb = (handle and 0xFF).toByte() - val msb = ((handle shr 8) and 0xFF).toByte() + fun read(handle: ATTHandles): ByteArray { + val lsb = (handle.value and 0xFF).toByte() + val msb = ((handle.value shr 8) and 0xFF).toByte() val pdu = byteArrayOf(OPCODE_READ_REQUEST, lsb, msb) writeRaw(pdu) // wait for response placed into responses queue by the reader coroutine return readResponse() } - fun write(handle: Int, value: ByteArray) { - val lsb = (handle and 0xFF).toByte() - val msb = ((handle shr 8) and 0xFF).toByte() + fun write(handle: ATTHandles, value: ByteArray) { + val lsb = (handle.value and 0xFF).toByte() + val msb = ((handle.value shr 8) and 0xFF).toByte() + val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value + writeRaw(pdu) + // usually a Write Response (0x13) will arrive; wait for it (but discard return) + try { + readResponse() + } catch (e: Exception) { + Log.w(TAG, "No write response received: ${e.message}") + } + } + + fun write(handle: ATTCCCDHandles, value: ByteArray) { + val lsb = (handle.value and 0xFF).toByte() + val msb = ((handle.value shr 8) and 0xFF).toByte() val pdu = byteArrayOf(OPCODE_WRITE_REQUEST, lsb, msb) + value writeRaw(pdu) // usually a Write Response (0x13) will arrive; wait for it (but discard return) From fe69082e1121f81b564d3191fc86855ac809fdb8 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 22 Sep 2025 00:59:39 +0530 Subject: [PATCH 27/72] android: add ui for hearing stuff mostly copied from the transparency settings, which are now updated to match ios <26 ui --- .../me/kavishdevar/librepods/MainActivity.kt | 8 + .../composables/AccessibilitySlider.kt | 25 +- .../composables/IndependentToggle.kt | 88 +- .../screens/AccessibilitySettingsScreen.kt | 381 ++++++--- .../screens/AirPodsSettingsScreen.kt | 16 +- .../screens/HearingAidAdjustmentsScreen.kt | 799 ++++++++++++++++++ .../librepods/screens/HearingAidScreen.kt | 341 ++++++++ android/app/src/main/res/values/strings.xml | 30 + 8 files changed, 1530 insertions(+), 158 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 9dba1467..0395239b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -112,6 +112,8 @@ import me.kavishdevar.librepods.screens.AirPodsSettingsScreen import me.kavishdevar.librepods.screens.AppSettingsScreen import me.kavishdevar.librepods.screens.DebugScreen import me.kavishdevar.librepods.screens.HeadTrackingScreen +import me.kavishdevar.librepods.screens.HearingAidScreen +import me.kavishdevar.librepods.screens.HearingAidAdjustmentsScreen import me.kavishdevar.librepods.screens.LongPress import me.kavishdevar.librepods.screens.Onboarding import me.kavishdevar.librepods.screens.RenameScreen @@ -380,6 +382,12 @@ fun Main() { composable("onboarding") { Onboarding(navController, context) } + composable("hearing_aid") { + HearingAidScreen(navController) + } + composable("hearing_aid_adjustments") { + HearingAidAdjustmentsScreen(navController) + } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt index abd8d146..2141a013 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt @@ -49,10 +49,11 @@ import kotlin.math.roundToInt @OptIn(ExperimentalMaterial3Api::class) @Composable fun AccessibilitySlider( - label: String, + label: String? = null, value: Float, onValueChange: (Float) -> Unit, - valueRange: ClosedFloatingPointRange + valueRange: ClosedFloatingPointRange, + widthFrac: Float = 1f ) { val isDarkTheme = isSystemInDarkTheme() @@ -62,18 +63,20 @@ fun AccessibilitySlider( val labelTextColor = if (isDarkTheme) Color.White else Color.Black Column( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth(widthFrac), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( - text = label, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = labelTextColor, - fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + if (label != null) { + Text( + text = label, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = labelTextColor, + fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) + ) ) - ) + } Slider( value = value, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt index f40364bd..c83cc34a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt @@ -27,7 +27,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Arrangement 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 @@ -45,17 +48,22 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import me.kavishdevar.librepods.services.AirPodsService +import me.kavishdevar.librepods.R import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi import androidx.core.content.edit import android.util.Log @Composable -fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null) { +fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null, description: String? = null) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val snakeCasedName = @@ -109,39 +117,57 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam } } } - Box ( + Column ( modifier = Modifier - .padding(vertical = 8.dp) - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - checked = !checked - cb() - } - ) - }, - ) - { - Row( + .padding(vertical = 8.dp), + ) { + Box ( modifier = Modifier - .fillMaxWidth() - .height(55.dp) - .padding(horizontal = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = name, modifier = Modifier.weight(1f), fontSize = 16.sp, color = textColor) - StyledSwitch( - checked = checked, - onCheckedChange = { - checked = it - cb() + .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + checked = !checked + cb() + } + ) }, + ) + { + Row( + modifier = Modifier + .fillMaxWidth() + .height(55.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = name, modifier = Modifier.weight(1f), fontSize = 16.sp, color = textColor) + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + }, + ) + } + } + if (description != null) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 8.dp) ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 9878f1f9..193d1622 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -111,9 +111,9 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import kotlin.io.encoding.ExperimentalEncodingApi -var debounceJob: Job? = null -var phoneMediaDebounceJob: Job? = null -const val TAG = "AccessibilitySettings" +private var debounceJob: Job? = null +private var phoneMediaDebounceJob: Job? = null +private const val TAG = "AccessibilitySettings" @SuppressLint("DefaultLocale") @ExperimentalHazeMaterialsApi @@ -150,7 +150,7 @@ fun AccessibilitySettingsScreen() { CenterAlignedTopAppBar( title = { Text( - text = "Accessibility Settings", + text = stringResource(R.string.accessibility), style = TextStyle( fontSize = 20.sp, fontWeight = FontWeight.Medium, @@ -452,79 +452,232 @@ fun AccessibilitySettingsScreen() { // Only show transparency mode section if SDP offset is available if (isSdpOffsetAvailable.value) { AccessibilityToggle( - text = "Transparency Mode", + text = stringResource(R.string.transparency_mode), mutableState = enabled, - independent = true + independent = true, + description = stringResource(R.string.customize_transparency_mode_description) ) + Spacer(modifier = Modifier.height(4.dp)) Text( - text = stringResource(R.string.customize_transparency_mode_description), + text = stringResource(R.string.amplification).uppercase(), style = TextStyle( - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, + 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 = 0.dp) + ) + Box( modifier = Modifier - .padding(horizontal = 2.dp) + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + .height(55.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxSize() + ) { + Text( + text = "􀊥", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(start = 4.dp) + ) + AccessibilitySlider( + valueRange = -1f..1f, + value = amplificationSliderValue.floatValue, + onValueChange = { + amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) + }, + widthFrac = 0.90f, + ) + Text( + text = "􀊩", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(end = 4.dp) + ) + } + } + + Text( + text = stringResource(R.string.balance).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) ) - Spacer(modifier = Modifier.height(4.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.left), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = stringResource(R.string.right), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + AccessibilitySlider( + valueRange = -1f..1f, + value = balanceSliderValue.floatValue, + onValueChange = { + balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) + }, + ) + } + } + Text( - text = "Customize Transparency Mode".uppercase(), + text = stringResource(R.string.tone).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(8.dp, bottom = 0.dp) ) - Column( + Box( modifier = Modifier .fillMaxWidth() .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(8.dp) + .padding(horizontal = 8.dp, vertical = 0.dp) ) { - AccessibilitySlider( - label = "Amplification", - valueRange = -1f..1f, - value = amplificationSliderValue.floatValue, - onValueChange = { - amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) - }, - ) - AccessibilitySlider( - label = "Balance", - valueRange = -1f..1f, - value = balanceSliderValue.floatValue, - onValueChange = { - balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) - AccessibilitySlider( - label = "Tone", - valueRange = -1f..1f, - value = toneSliderValue.floatValue, - onValueChange = { - toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) - AccessibilitySlider( - label = "Ambient Noise Reduction", - valueRange = 0f..1f, - value = ambientNoiseReductionSliderValue.floatValue, - onValueChange = { - ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) - }, - ) - AccessibilityToggle( - text = "Conversation Boost", - mutableState = conversationBoostEnabled - ) + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.darker), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = stringResource(R.string.brighter), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + AccessibilitySlider( + valueRange = -1f..1f, + value = toneSliderValue.floatValue, + onValueChange = { + toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) + }, + ) + } } - Spacer(modifier = Modifier.height(2.dp)) + + Text( + text = stringResource(R.string.ambient_noise_reduction).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.less), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = stringResource(R.string.more), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + AccessibilitySlider( + valueRange = 0f..1f, + value = ambientNoiseReductionSliderValue.floatValue, + onValueChange = { + ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) + }, + ) + } + } + + AccessibilityToggle( + text = stringResource(R.string.conversation_boost), + mutableState = conversationBoostEnabled, + independent = true, + description = stringResource(R.string.conversation_boost_description) + ) } Text( - text = "AUDIO", + text = stringResource(R.string.audio).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -542,7 +695,7 @@ fun AccessibilitySettingsScreen() { verticalArrangement = Arrangement.SpaceBetween ) { Text( - text = "Tone Volume", + text = stringResource(R.string.tone_volume), style = TextStyle( fontSize = 16.sp, fontFamily = FontFamily(Font(R.font.sf_pro)), @@ -559,8 +712,8 @@ fun AccessibilitySettingsScreen() { LoudSoundReductionSwitch() DropdownMenuComponent( - label = "Press Speed", - options = pressSpeedOptions.values.toList(), + label = stringResource(R.string.press_speed), + options = listOf(stringResource(R.string.default_option), stringResource(R.string.slower), stringResource(R.string.slowest)), selectedOption = selectedPressSpeed.toString(), onOptionSelected = { newValue -> selectedPressSpeed = newValue @@ -572,8 +725,8 @@ fun AccessibilitySettingsScreen() { textColor = textColor ) DropdownMenuComponent( - label = "Press and Hold Duration", - options = pressAndHoldDurationOptions.values.toList(), + label = stringResource(R.string.press_and_hold_duration), + options = listOf(stringResource(R.string.default_option), stringResource(R.string.slower), stringResource(R.string.slowest)), selectedOption = selectedPressAndHoldDuration.toString(), onOptionSelected = { newValue -> selectedPressAndHoldDuration = newValue @@ -585,8 +738,8 @@ fun AccessibilitySettingsScreen() { textColor = textColor ) DropdownMenuComponent( - label = "Volume Swipe Speed", - options = volumeSwipeSpeedOptions.values.toList(), + label = stringResource(R.string.volume_swipe_speed), + options = listOf(stringResource(R.string.default_option), stringResource(R.string.longer), stringResource(R.string.longest)), selectedOption = selectedVolumeSwipeSpeed.toString(), onOptionSelected = { newValue -> selectedVolumeSwipeSpeed = newValue @@ -603,7 +756,7 @@ fun AccessibilitySettingsScreen() { // Only show transparency mode EQ section if SDP offset is available if (isSdpOffsetAvailable.value) { Text( - text = "Equalizer".uppercase(), + text = stringResource(R.string.equalizer).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -687,7 +840,7 @@ fun AccessibilitySettingsScreen() { ) Text( - text = "Band ${i + 1}", + text = stringResource(R.string.band_label, i + 1), fontSize = 12.sp, color = textColor, modifier = Modifier.padding(top = 4.dp) @@ -700,7 +853,7 @@ fun AccessibilitySettingsScreen() { } Text( - text = "Apply EQ to".uppercase(), + text = stringResource(R.string.apply_eq_to).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -740,7 +893,7 @@ fun AccessibilitySettingsScreen() { verticalAlignment = Alignment.CenterVertically ) { Text( - "Phone", + stringResource(R.string.phone), fontSize = 16.sp, color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)), @@ -791,7 +944,7 @@ fun AccessibilitySettingsScreen() { verticalAlignment = Alignment.CenterVertically ) { Text( - "Media", + stringResource(R.string.media), fontSize = 16.sp, color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)), @@ -888,7 +1041,7 @@ fun AccessibilitySettingsScreen() { ) Text( - text = "Band ${i + 1}", + text = stringResource(R.string.band_label, i + 1), fontSize = 12.sp, color = textColor, modifier = Modifier.padding(top = 4.dp) @@ -902,57 +1055,75 @@ fun AccessibilitySettingsScreen() { @Composable -fun AccessibilityToggle(text: String, mutableState: MutableState, independent: Boolean = false) { +fun AccessibilityToggle(text: String, mutableState: MutableState, independent: Boolean = false, description: String? = null) { val isDarkTheme = isSystemInDarkTheme() var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) val textColor = if (isDarkTheme) Color.White else Color.Black - val boxPaddings = if (independent) 2.dp else 4.dp val cornerShape = if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) - Box ( + + Column( modifier = Modifier - .padding(vertical = boxPaddings) - .background(animatedBackgroundColor, cornerShape) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + .padding(vertical = 8.dp) + ) { + Box ( + modifier = Modifier + .background(animatedBackgroundColor, cornerShape) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + mutableState.value = !mutableState.value + } + ) + }, + ) + { + val rowHeight = if (independent) 55.dp else 50.dp + val rowPadding = if (independent) 12.dp else 4.dp + Row( + modifier = Modifier + .fillMaxWidth() + .height(rowHeight) + .padding(horizontal = rowPadding), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + modifier = Modifier.weight(1f), + fontSize = 16.sp, + color = textColor + ) + StyledSwitch( + checked = mutableState.value, + onCheckedChange = { + mutableState.value = it }, - onTap = { - mutableState.value = !mutableState.value - } ) - }, - ) - { - val rowHeight = if (independent) 55.dp else 50.dp - val rowPadding = if (independent) 12.dp else 4.dp - Row( - modifier = Modifier - .fillMaxWidth() - .height(rowHeight) - .padding(horizontal = rowPadding), - verticalAlignment = Alignment.CenterVertically - ) { + } + } + if (description != null) { + Spacer(modifier = Modifier.height(8.dp)) Text( - text = text, - modifier = Modifier.weight(1f), - fontSize = 16.sp, - color = textColor - ) - StyledSwitch( - checked = mutableState.value, - onCheckedChange = { - mutableState.value = it - }, + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 8.dp) ) } } } -data class TransparencySettings ( +private data class TransparencySettings ( val enabled: Boolean, val leftEQ: FloatArray, val rightEQ: FloatArray, @@ -1134,7 +1305,7 @@ private fun snapIfClose(value: Float, points: List, threshold: Float = 0. } @Composable -fun DropdownMenuComponent( +private fun DropdownMenuComponent( label: String, options: List, selectedOption: String, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 0f449a43..8a1419d0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -358,6 +358,9 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, if (!bleOnlyMode) { Spacer(modifier = Modifier.height(32.dp)) + NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) + + Spacer(modifier = Modifier.height(16.dp)) NoiseControlSettings(service = service) Spacer(modifier = Modifier.height(16.dp)) @@ -401,17 +404,8 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, service = service, sharedPreferences = sharedPreferences, default = false, - controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION - ) - Text( - text = stringResource(R.string.off_listening_mode_description), - style = TextStyle( - fontSize = 12.sp, - fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, top = 0.dp) + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, + description = stringResource(R.string.off_listening_mode_description) ) // an about card- everything but the version number is unknown - will add later if i find out diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt new file mode 100644 index 00000000..287c8a87 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -0,0 +1,799 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.screens + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Log +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.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.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.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.drawBehind +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.PointerEventPass +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.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import dev.chrisbanes.haze.HazeEffectScope +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.composables.IndependentToggle +import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch +import me.kavishdevar.librepods.composables.SinglePodANCSwitch +import me.kavishdevar.librepods.composables.StyledSwitch +import me.kavishdevar.librepods.composables.ToneVolumeSlider +import me.kavishdevar.librepods.composables.VolumeControlSwitch +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.ATTManager +import me.kavishdevar.librepods.utils.ATTHandles +import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.RadareOffsetFinder +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder +import kotlin.io.encoding.ExperimentalEncodingApi + +private var debounceJob: Job? = null +private var phoneMediaDebounceJob: Job? = null +private const val TAG = "AccessibilitySettings" + +@SuppressLint("DefaultLocale") +@ExperimentalHazeMaterialsApi +@OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) +@Composable +fun HearingAidAdjustmentsScreen(navController: NavController) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val verticalScrollState = rememberScrollState() + val hazeState = remember { HazeState() } + val snackbarHostState = remember { SnackbarHostState() } + val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") + + val aacpManager = remember { ServiceManager.getService()?.aacpManager } + val context = LocalContext.current + val radareOffsetFinder = remember { RadareOffsetFinder(context) } + val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } + val service = ServiceManager.getService() + + 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 + + Scaffold( + containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7), + topBar = { + val darkMode = isSystemInDarkTheme() + val mDensity = remember { mutableFloatStateOf(1f) } + + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(R.string.adjustments), + style = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + color = if (darkMode) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + }, + modifier = Modifier + .hazeEffect( + state = hazeState, + style = CupertinoMaterials.thick(), + block = fun HazeEffectScope.() { + alpha = if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f + }) + .drawBehind { + mDensity.floatValue = density + val strokeWidth = 0.7.dp.value * density + val y = size.height - strokeWidth / 2 + if (verticalScrollState.value > 60.dp.value * density) { + drawLine( + if (darkMode) Color.DarkGray else Color.LightGray, + Offset(0f, y), + Offset(size.width, y), + strokeWidth + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent) + ) + }, + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + Column( + modifier = Modifier + .hazeSource(hazeState) + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp) + .verticalScroll(verticalScrollState), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + val enabled = remember { mutableStateOf(false) } + val amplificationSliderValue = remember { mutableFloatStateOf(0.5f) } + val balanceSliderValue = remember { mutableFloatStateOf(0.5f) } + val toneSliderValue = remember { mutableFloatStateOf(0.5f) } + val ambientNoiseReductionSliderValue = remember { mutableFloatStateOf(0.0f) } + val conversationBoostEnabled = remember { mutableStateOf(false) } + val eq = remember { mutableStateOf(FloatArray(8)) } + + val phoneMediaEQ = remember { mutableStateOf(FloatArray(8) { 0.5f }) } + val phoneEQEnabled = remember { mutableStateOf(false) } + val mediaEQEnabled = remember { mutableStateOf(false) } + + val initialLoadComplete = remember { mutableStateOf(false) } + + val initialReadSucceeded = remember { mutableStateOf(false) } + val initialReadAttempts = remember { mutableStateOf(0) } + + val HearingAidSettings = remember { + mutableStateOf( + HearingAidSettings( + enabled = enabled.value, + leftEQ = eq.value, + rightEQ = eq.value, + leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, + rightAmplification = amplificationSliderValue.floatValue + (balanceSliderValue.floatValue - 0.5f) * amplificationSliderValue.floatValue * 2, + leftTone = toneSliderValue.floatValue, + rightTone = toneSliderValue.floatValue, + leftConversationBoost = conversationBoostEnabled.value, + rightConversationBoost = conversationBoostEnabled.value, + leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + netAmplification = amplificationSliderValue.floatValue, + balance = balanceSliderValue.floatValue + ) + ) + } + + val hearingAidEnabled = remember { + val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } + val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } + mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte())) + } + + val hearingAidListener = remember { + object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value || + controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value) { + val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } + val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } + hearingAidEnabled.value = (aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte()) + } + } + } + } + + LaunchedEffect(Unit) { + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + } + + DisposableEffect(Unit) { + onDispose { + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + } + } + + LaunchedEffect(enabled.value, amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, eq.value, initialLoadComplete.value, initialReadSucceeded.value) { + if (!initialLoadComplete.value) { + Log.d(TAG, "Initial device load not complete - skipping send") + return@LaunchedEffect + } + + if (!initialReadSucceeded.value) { + Log.d(TAG, "Initial device read not successful yet - skipping send until read succeeds") + return@LaunchedEffect + } + + HearingAidSettings.value = HearingAidSettings( + enabled = enabled.value, + leftEQ = eq.value, + rightEQ = eq.value, + leftAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue < 0) -balanceSliderValue.floatValue else 0f, + rightAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue > 0) balanceSliderValue.floatValue else 0f, + leftTone = toneSliderValue.floatValue, + rightTone = toneSliderValue.floatValue, + leftConversationBoost = conversationBoostEnabled.value, + rightConversationBoost = conversationBoostEnabled.value, + leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + netAmplification = amplificationSliderValue.floatValue, + balance = balanceSliderValue.floatValue + ) + Log.d("HearingAidSettings", "Updated settings: ${HearingAidSettings.value}") + // sendHearingAidSettings(attManager, HearingAidSettings.value) + } + + DisposableEffect(Unit) { + onDispose { + // attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener) + } + } + + LaunchedEffect(Unit) { + Log.d(TAG, "Connecting to ATT...") + try { + // attManager.enableNotifications(ATTHandles.TRANSPARENCY) + // attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener) + + try { + if (aacpManager != null) { + Log.d(TAG, "Found AACPManager, reading cached EQ data") + val aacpEQ = aacpManager.eqData + if (aacpEQ.isNotEmpty()) { + eq.value = aacpEQ.copyOf() + phoneMediaEQ.value = aacpEQ.copyOf() + phoneEQEnabled.value = aacpManager.eqOnPhone + mediaEQEnabled.value = aacpManager.eqOnMedia + Log.d(TAG, "Populated EQ from AACPManager: ${aacpEQ.toList()}") + } else { + Log.d(TAG, "AACPManager EQ data empty") + } + } else { + Log.d(TAG, "No AACPManager available") + } + } catch (e: Exception) { + Log.w(TAG, "Error reading EQ from AACPManager: ${e.message}") + } + + /* + var parsedSettings: HearingAidSettings? = null + for (attempt in 1..3) { + initialReadAttempts.value = attempt + try { + val data = attManager.read(ATTHandles.TRANSPARENCY) + parsedSettings = parseHearingAidSettingsResponse(data = data) + if (parsedSettings != null) { + Log.d(TAG, "Parsed settings on attempt $attempt") + break + } else { + Log.d(TAG, "Parsing returned null on attempt $attempt") + } + } catch (e: Exception) { + Log.w(TAG, "Read attempt $attempt failed: ${e.message}") + } + delay(200) + } + + if (parsedSettings != null) { + Log.d(TAG, "Initial transparency settings: $parsedSettings") + enabled.value = parsedSettings.enabled + amplificationSliderValue.floatValue = parsedSettings.netAmplification + balanceSliderValue.floatValue = parsedSettings.balance + toneSliderValue.floatValue = parsedSettings.leftTone + ambientNoiseReductionSliderValue.floatValue = parsedSettings.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsedSettings.leftConversationBoost + eq.value = parsedSettings.leftEQ.copyOf() + initialReadSucceeded.value = true + } else { + Log.d(TAG, "Failed to read/parse initial transparency settings after ${initialReadAttempts.value} attempts") + } + */ + } catch (e: IOException) { + e.printStackTrace() + } finally { + initialLoadComplete.value = true + } + } + + LaunchedEffect(phoneMediaEQ.value, phoneEQEnabled.value, mediaEQEnabled.value) { + phoneMediaDebounceJob?.cancel() + phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(150) + val manager = ServiceManager.getService()?.aacpManager + if (manager == null) { + Log.w(TAG, "Cannot write EQ: AACPManager not available") + return@launch + } + try { + val phoneByte = if (phoneEQEnabled.value) 0x01.toByte() else 0x02.toByte() + val mediaByte = if (mediaEQEnabled.value) 0x01.toByte() else 0x02.toByte() + Log.d(TAG, "Sending phone/media EQ (phoneEnabled=${phoneEQEnabled.value}, mediaEnabled=${mediaEQEnabled.value})") + manager.sendPhoneMediaEQ(phoneMediaEQ.value, phoneByte, mediaByte) + } catch (e: Exception) { + Log.w(TAG, "Error sending phone/media EQ: ${e.message}") + } + } + } + + val isDarkThemeLocal = isSystemInDarkTheme() + var backgroundColorHA by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColorHA by animateColorAsState(targetValue = backgroundColorHA, animationSpec = tween(durationMillis = 500)) + + Text( + text = stringResource(R.string.amplification).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + .height(55.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxSize() + ) { + Text( + text = "􀊥", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(start = 4.dp) + ) + AccessibilitySlider( + valueRange = -1f..1f, + value = amplificationSliderValue.floatValue, + onValueChange = { + amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) + }, + widthFrac = 0.90f + ) + Text( + text = "􀊩", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(end = 4.dp) + ) + } + } + + val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) + + IndependentToggle( + name = stringResource(R.string.swipe_to_control_amplification), + service = service, + sharedPreferences = sharedPreferences, + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.HPS_GAIN_SWIPE, + description = stringResource(R.string.swipe_amplification_description) + ) + + Text( + text = stringResource(R.string.balance).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.left), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = stringResource(R.string.right), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + AccessibilitySlider( + valueRange = -1f..1f, + value = balanceSliderValue.floatValue, + onValueChange = { + balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) + }, + ) + } + } + + Text( + text = stringResource(R.string.tone).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.darker), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = stringResource(R.string.brighter), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + AccessibilitySlider( + valueRange = -1f..1f, + value = toneSliderValue.floatValue, + onValueChange = { + toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) + }, + ) + } + } + + Text( + text = stringResource(R.string.ambient_noise_reduction).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + ) { + Column { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = stringResource(R.string.less), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = stringResource(R.string.more), + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + AccessibilitySlider( + valueRange = 0f..1f, + value = ambientNoiseReductionSliderValue.floatValue, + onValueChange = { + ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) + }, + ) + } + } + + AccessibilityToggle( + text = stringResource(R.string.conversation_boost), + mutableState = conversationBoostEnabled, + independent = true, + description = stringResource(R.string.conversation_boost_description) + ) + } + } +} + +private data class HearingAidSettings( + val enabled: Boolean, + val leftEQ: FloatArray, + val rightEQ: FloatArray, + val leftAmplification: Float, + val rightAmplification: Float, + val leftTone: Float, + val rightTone: Float, + val leftConversationBoost: Boolean, + val rightConversationBoost: Boolean, + val leftAmbientNoiseReduction: Float, + val rightAmbientNoiseReduction: Float, + val netAmplification: Float, + val balance: Float +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as HearingAidSettings + + if (enabled != other.enabled) return false + if (leftAmplification != other.leftAmplification) return false + if (rightAmplification != other.rightAmplification) return false + if (leftTone != other.leftTone) return false + if (rightTone != other.rightTone) return false + if (leftConversationBoost != other.leftConversationBoost) return false + if (rightConversationBoost != other.rightConversationBoost) return false + if (leftAmbientNoiseReduction != other.leftAmbientNoiseReduction) return false + if (rightAmbientNoiseReduction != other.rightAmbientNoiseReduction) return false + if (!leftEQ.contentEquals(other.leftEQ)) return false + if (!rightEQ.contentEquals(other.rightEQ)) return false + + return true + } + + override fun hashCode(): Int { + var result = enabled.hashCode() + result = 31 * result + leftAmplification.hashCode() + result = 31 * result + rightAmplification.hashCode() + result = 31 * result + leftTone.hashCode() + result = 31 * result + rightTone.hashCode() + result = 31 * result + leftConversationBoost.hashCode() + result = 31 * result + rightConversationBoost.hashCode() + result = 31 * result + leftAmbientNoiseReduction.hashCode() + result = 31 * result + rightAmbientNoiseReduction.hashCode() + result = 31 * result + leftEQ.contentHashCode() + result = 31 * result + rightEQ.contentHashCode() + return result + } +} + +private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings? { + val settingsData = data.copyOfRange(1, data.size) + val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) + + val enabled = buffer.float + Log.d(TAG, "Parsed enabled: $enabled") + + val leftEQ = FloatArray(8) + for (i in 0..7) { + leftEQ[i] = buffer.float + Log.d(TAG, "Parsed left EQ${i+1}: ${leftEQ[i]}") + } + val leftAmplification = buffer.float + Log.d(TAG, "Parsed left amplification: $leftAmplification") + val leftTone = buffer.float + Log.d(TAG, "Parsed left tone: $leftTone") + val leftConvFloat = buffer.float + val leftConversationBoost = leftConvFloat > 0.5f + Log.d(TAG, "Parsed left conversation boost: $leftConvFloat ($leftConversationBoost)") + val leftAmbientNoiseReduction = buffer.float + Log.d(TAG, "Parsed left ambient noise reduction: $leftAmbientNoiseReduction") + + val rightEQ = FloatArray(8) + for (i in 0..7) { + rightEQ[i] = buffer.float + Log.d(TAG, "Parsed right EQ${i+1}: $rightEQ[i]") + } + + val rightAmplification = buffer.float + Log.d(TAG, "Parsed right amplification: $rightAmplification") + val rightTone = buffer.float + Log.d(TAG, "Parsed right tone: $rightTone") + val rightConvFloat = buffer.float + val rightConversationBoost = rightConvFloat > 0.5f + Log.d(TAG, "Parsed right conversation boost: $rightConvFloat ($rightConversationBoost)") + val rightAmbientNoiseReduction = buffer.float + Log.d(TAG, "Parsed right ambient noise reduction: $rightAmbientNoiseReduction") + + Log.d(TAG, "Settings parsed successfully") + + val avg = (leftAmplification + rightAmplification) / 2 + val amplification = avg.coerceIn(-1f, 1f) + val diff = rightAmplification - leftAmplification + val balance = diff.coerceIn(-1f, 1f) + + return HearingAidSettings( + enabled = enabled > 0.5f, + leftEQ = leftEQ, + rightEQ = rightEQ, + leftAmplification = leftAmplification, + rightAmplification = rightAmplification, + leftTone = leftTone, + rightTone = rightTone, + leftConversationBoost = leftConversationBoost, + rightConversationBoost = rightConversationBoost, + leftAmbientNoiseReduction = leftAmbientNoiseReduction, + rightAmbientNoiseReduction = rightAmbientNoiseReduction, + netAmplification = amplification, + balance = balance + ) +} + +private fun sendHearingAidSettings( + attManager: ATTManager, + HearingAidSettings: HearingAidSettings +) { + debounceJob?.cancel() + debounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) + + Log.d(TAG, + "Sending settings: $HearingAidSettings" + ) + + buffer.putFloat(if (HearingAidSettings.enabled) 1.0f else 0.0f) + + for (eq in HearingAidSettings.leftEQ) { + buffer.putFloat(eq) + } + buffer.putFloat(HearingAidSettings.leftAmplification) + buffer.putFloat(HearingAidSettings.leftTone) + buffer.putFloat(if (HearingAidSettings.leftConversationBoost) 1.0f else 0.0f) + buffer.putFloat(HearingAidSettings.leftAmbientNoiseReduction) + + for (eq in HearingAidSettings.rightEQ) { + buffer.putFloat(eq) + } + buffer.putFloat(HearingAidSettings.rightAmplification) + buffer.putFloat(HearingAidSettings.rightTone) + buffer.putFloat(if (HearingAidSettings.rightConversationBoost) 1.0f else 0.0f) + buffer.putFloat(HearingAidSettings.rightAmbientNoiseReduction) + + val data = buffer.array() + attManager.write( + ATTHandles.TRANSPARENCY, + value = data + ) + } catch (e: IOException) { + e.printStackTrace() + } + } +} + +private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPManager?, eq: FloatArray, phoneEnabled: Boolean, mediaEnabled: Boolean) { + phoneMediaDebounceJob?.cancel() + phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + if (aacpManager == null) { + Log.w(TAG, "AACPManger is null; cannot send phone/media EQ") + return@launch + } + val phoneByte = if (phoneEnabled) 0x01.toByte() else 0x02.toByte() + val mediaByte = if (mediaEnabled) 0x01.toByte() else 0x02.toByte() + aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) + } catch (e: Exception) { + Log.w(TAG, "Error in sendPhoneMediaEQ: ${e.message}") + } + } +} + +private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { + val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value + return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt new file mode 100644 index 00000000..e7d60847 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -0,0 +1,341 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.screens + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Log +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.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.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.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.drawBehind +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.PointerEventPass +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.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import dev.chrisbanes.haze.HazeEffectScope +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch +import me.kavishdevar.librepods.composables.SinglePodANCSwitch +import me.kavishdevar.librepods.composables.StyledSwitch +import me.kavishdevar.librepods.composables.ToneVolumeSlider +import me.kavishdevar.librepods.composables.VolumeControlSwitch +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.ATTManager +import me.kavishdevar.librepods.utils.ATTHandles +import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.RadareOffsetFinder +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder +import kotlin.io.encoding.ExperimentalEncodingApi + +private var debounceJob: Job? = null +private var phoneMediaDebounceJob: Job? = null +private const val TAG = "AccessibilitySettings" + +@SuppressLint("DefaultLocale") +@ExperimentalHazeMaterialsApi +@OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) +@Composable +fun HearingAidScreen(navController: NavController) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val verticalScrollState = rememberScrollState() + val hazeState = remember { HazeState() } + val snackbarHostState = remember { SnackbarHostState() } + val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") + + val aacpManager = remember { ServiceManager.getService()?.aacpManager } + val context = LocalContext.current + val radareOffsetFinder = remember { RadareOffsetFinder(context) } + val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } + val service = ServiceManager.getService() + + 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 + + Scaffold( + containerColor = if (isSystemInDarkTheme()) Color( + 0xFF000000 + ) else Color( + 0xFFF2F2F7 + ), + topBar = { + val darkMode = isSystemInDarkTheme() + val mDensity = remember { mutableFloatStateOf(1f) } + + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(R.string.hearing_aid), + style = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + color = if (darkMode) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + }, + modifier = Modifier + .hazeEffect( + state = hazeState, + style = CupertinoMaterials.thick(), + block = fun HazeEffectScope.() { + alpha = + if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f + }) + .drawBehind { + mDensity.floatValue = density + val strokeWidth = 0.7.dp.value * density + val y = size.height - strokeWidth / 2 + if (verticalScrollState.value > 60.dp.value * density) { + drawLine( + if (darkMode) Color.DarkGray else Color.LightGray, + Offset(0f, y), + Offset(size.width, y), + strokeWidth + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent + ) + ) + }, + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + Column( + modifier = Modifier + .hazeSource(hazeState) + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp) + .verticalScroll(verticalScrollState), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + val hearingAidEnabled = remember { + val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } + val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } + mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte())) + } + + val hearingAidListener = remember { + object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value || + controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value) { + val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } + val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } + hearingAidEnabled.value = (aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte()) + } + } + } + } + + LaunchedEffect(Unit) { + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + } + + DisposableEffect(Unit) { + onDispose { + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + } + } + + fun onChange(value: Boolean) { + if (value) { + // Enable and enroll if not enrolled + val enrolled = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID }?.value?.getOrNull(0) == 0x01.toByte() + if (!enrolled) { + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) // Enroll and enable + } else { + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) // Enable + } + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x01.toByte()) // Enable assist + } else { + // Disable both, keep enrolled + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x02)) // Disable + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x02.toByte()) // Disable assist + } + hearingAidEnabled.value = value + } + + Text( + text = stringResource(R.string.hearing_aid).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + ) { + val isDarkThemeLocal = isSystemInDarkTheme() + var backgroundColorHA by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColorHA by animateColorAsState(targetValue = backgroundColorHA, animationSpec = tween(durationMillis = 500)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColorHA = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColorHA = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + onChange(value = !hearingAidEnabled.value) + } + ) + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.hearing_aid), modifier = Modifier.weight(1f), fontSize = 16.sp, color = textColor) + StyledSwitch( + checked = hearingAidEnabled.value, + onCheckedChange = { + onChange(value = it) + }, + ) + } + + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { navController.navigate("hearing_aid_adjustments") } + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.adjustments), + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.weight(1f)) + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + tint = textColor + ) + } + } + + Text( + text = stringResource(R.string.hearing_aid_description), + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(horizontal = 8.dp) + ) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index e5699339..a9825cb0 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -100,4 +100,34 @@ Hang Up Press Once Press Twice + Hearing Aid + Adjustments + Swipe to control amplification + When in Transparency and no media is playing, swipe up and down on the Touch controls of your AirPods Pro to increase or decrease the amplification of environmental sounds. + Transparency Mode + Customize Transparency Mode + Press Speed + Press and Hold Duration + Volume Swipe Speed + Equalizer + Apply EQ to + Phone + Media + Band %d + Default + Slower + Slowest + Longer + Longest + Darker + Brighter + Less + More + Amplification + Balance + Tone + Ambient Noise Reduction + Conversation Boost + Conversation Boost focuses your AirPods Pro on the person talking in front of you, making it easier to hear in a face-to-face conversation. + AirPods can use the results of a hearing test to make adjustments that improve the clarity of voices and sounds around you. \n\n Hearing Aid is only intended for people with perceived mild to moderate hearing loss. From ce229bec6eea89064d780f667393bad5aefc5b09 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 22 Sep 2025 10:44:48 +0530 Subject: [PATCH 28/72] android: add media assist options in hearing aid ui only --- .../composables/IndependentToggle.kt | 11 +- .../screens/AccessibilitySettingsScreen.kt | 17 ++- .../librepods/screens/HearingAidScreen.kt | 109 +++++++++++++++++- .../librepods/services/AirPodsService.kt | 6 - .../kavishdevar/librepods/utils/ATTManager.kt | 5 +- android/app/src/main/res/values/strings.xml | 4 + 6 files changed, 141 insertions(+), 11 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt index c83cc34a..25e142cc 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt @@ -146,7 +146,16 @@ fun IndependentToggle(name: String, service: AirPodsService? = null, functionNam .padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically ) { - Text(text = name, modifier = Modifier.weight(1f), fontSize = 16.sp, color = textColor) + Text( + text = name, + modifier = Modifier.weight(1f), + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) + ) StyledSwitch( checked = checked, onCheckedChange = { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 193d1622..3715c9d8 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -1055,7 +1055,7 @@ fun AccessibilitySettingsScreen() { @Composable -fun AccessibilityToggle(text: String, mutableState: MutableState, independent: Boolean = false, description: String? = null) { +fun AccessibilityToggle(text: String, mutableState: MutableState, independent: Boolean = false, description: String? = null, title: String? = null) { val isDarkTheme = isSystemInDarkTheme() var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) @@ -1065,7 +1065,20 @@ fun AccessibilityToggle(text: String, mutableState: MutableState, indep Column( modifier = Modifier .padding(vertical = 8.dp) - ) { + ) { + if (title != null) { + Text( + text = title, + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + Spacer(modifier = Modifier.height(4.dp)) + } Box ( modifier = Modifier .background(animatedBackgroundColor, cornerShape) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index e7d60847..0557e166 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -221,6 +221,10 @@ fun HearingAidScreen(navController: NavController) { } } + val mediaAssistEnabled = remember { mutableStateOf(false) } + val adjustMediaEnabled = remember { mutableStateOf(false) } + val adjustPhoneEnabled = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) @@ -256,7 +260,8 @@ fun HearingAidScreen(navController: NavController) { style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f) + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier.padding(8.dp, bottom = 2.dp) ) @@ -336,6 +341,108 @@ fun HearingAidScreen(navController: NavController) { ), modifier = Modifier.padding(horizontal = 8.dp) ) + + Spacer(modifier = Modifier.height(16.dp)) + + AccessibilityToggle( + text = stringResource(R.string.media_assist), + mutableState = mediaAssistEnabled, + independent = true, + description = stringResource(R.string.media_assist_description), + title = stringResource(R.string.media_assist).uppercase() + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Column ( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + ) { + val isDarkThemeLocal = isSystemInDarkTheme() + var backgroundColorAdjustMedia by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColorAdjustMedia by animateColorAsState(targetValue = backgroundColorAdjustMedia, animationSpec = tween(durationMillis = 500)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + adjustMediaEnabled.value = !adjustMediaEnabled.value + } + ) + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.adjust_media), + modifier = Modifier.weight(1f), + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) + ) + StyledSwitch( + checked = adjustMediaEnabled.value, + onCheckedChange = { + adjustMediaEnabled.value = it + }, + ) + } + + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(start = 12.dp, end = 0.dp) + ) + + var backgroundColorAdjustPhone by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColorAdjustPhone by animateColorAsState(targetValue = backgroundColorAdjustPhone, animationSpec = tween(durationMillis = 500)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + adjustPhoneEnabled.value = !adjustPhoneEnabled.value + } + ) + }, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.adjust_calls), + modifier = Modifier.weight(1f), + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) + ) + StyledSwitch( + checked = adjustPhoneEnabled.value, + onCheckedChange = { + adjustPhoneEnabled.value = it + }, + ) + } + } } } } \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 76e31e1a..b5fe15b3 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -1856,7 +1856,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } private lateinit var connectionReceiver: BroadcastReceiver - private lateinit var disconnectionReceiver: BroadcastReceiver private fun resToUri(resId: Int): Uri? { return try { @@ -2581,11 +2580,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } catch (e: Exception) { e.printStackTrace() } - try { - unregisterReceiver(disconnectionReceiver) - } catch (e: Exception) { - e.printStackTrace() - } try { unregisterReceiver(earReceiver) } catch (e: Exception) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index 09fceff7..33122176 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -173,7 +173,10 @@ class ATTManager(private val device: BluetoothDevice) { val inp = input ?: throw IllegalStateException("Not connected") val buffer = ByteArray(512) val len = inp.read(buffer) - if (len <= 0) throw IllegalStateException("No data read from ATT socket") + if (len == -1) { + disconnect() + throw IllegalStateException("End of stream reached") + } val data = buffer.copyOfRange(0, len) Log.wtf(TAG, "Read ${data.size} bytes from ATT") Log.d(TAG, "readPDU: ${data.joinToString(" ") { String.format("%02X", it) }}") diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index a9825cb0..83960d08 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -130,4 +130,8 @@ Conversation Boost Conversation Boost focuses your AirPods Pro on the person talking in front of you, making it easier to hear in a face-to-face conversation. AirPods can use the results of a hearing test to make adjustments that improve the clarity of voices and sounds around you. \n\n Hearing Aid is only intended for people with perceived mild to moderate hearing loss. + Media Assist + AirPods Pro can use the results of a hearing test to make adjustments that improve the clarity of music, video, and calls. + Adjust Music and Video + Adjust Calls From 4751f705796b6903ef5f16763df3e22372c7ab5c Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 22 Sep 2025 14:54:54 +0530 Subject: [PATCH 29/72] android: add hearing aid adjustments --- .../composables/ConfirmationDialog.kt | 180 ++++++++++++++++ .../composables/LoudSoundReductionSwitch.kt | 12 +- .../screens/AccessibilitySettingsScreen.kt | 198 ++---------------- .../screens/HearingAidAdjustmentsScreen.kt | 184 +++++++--------- .../librepods/screens/HearingAidScreen.kt | 90 ++++++-- .../kavishdevar/librepods/utils/ATTManager.kt | 8 +- .../librepods/utils/TransparencyUtils.kt | 149 +++++++++++++ 7 files changed, 501 insertions(+), 320 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt new file mode 100644 index 00000000..5587501a --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -0,0 +1,180 @@ +package me.kavishdevar.librepods.composables + +import androidx.compose.foundation.background +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.fillMaxWidth +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.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import me.kavishdevar.librepods.R + +@ExperimentalHazeMaterialsApi +@Composable +fun ConfirmationDialog( + showDialog: MutableState, + title: String, + message: String, + confirmText: String = "Enable", + dismissText: String = "Cancel", + onConfirm: () -> Unit, + onDismiss: () -> Unit = { showDialog.value = false }, + hazeState: HazeState, + isDarkTheme: Boolean, + textColor: Color, + activeTrackColor: Color +) { + if (showDialog.value) { + Dialog(onDismissRequest = { showDialog.value = false }) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f), RoundedCornerShape(14.dp)) + .clip(RoundedCornerShape(14.dp)) + .hazeEffect(hazeState, CupertinoMaterials.regular()) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(24.dp)) + Text( + title, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = textColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 16.dp) + ) + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(12.dp)) + Text( + message, + style = TextStyle( + fontSize = 14.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 16.dp) + ) + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier.fillMaxWidth() + ) + var leftPressed by remember { mutableStateOf(false) } + var rightPressed by remember { mutableStateOf(false) } + val pressedColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + Row( + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + val position = event.changes.first().position + val width = size.width.toFloat() + val height = size.height.toFloat() + val isWithinBounds = position.y >= 0 && position.y <= height + val isLeft = position.x < width / 2 + event.changes.first().consume() + when (event.type) { + PointerEventType.Press -> { + if (isWithinBounds) { + leftPressed = isLeft + rightPressed = !isLeft + } else { + leftPressed = false + rightPressed = false + } + } + PointerEventType.Move -> { + if (isWithinBounds) { + leftPressed = isLeft + rightPressed = !isLeft + } else { + leftPressed = false + rightPressed = false + } + } + PointerEventType.Release -> { + if (isWithinBounds) { + if (leftPressed) { + onDismiss() + } else if (rightPressed) { + onConfirm() + } + } + leftPressed = false + rightPressed = false + } + } + } + } + }, + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .background(if (leftPressed) pressedColor else Color.Transparent), + contentAlignment = Alignment.Center + ) { + Text(dismissText, color = activeTrackColor) + } + Box( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(Color(0x40888888)) + ) + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .background(if (rightPressed) pressedColor else Color.Transparent), + contentAlignment = Alignment.Center + ) { + Text(confirmText, color = activeTrackColor) + } + } + } + } + } + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt index b79bd582..ba5f6fdb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt @@ -70,14 +70,10 @@ fun LoudSoundReductionSwitch() { for (attempt in 1..3) { try { val data = attManager.read(ATTHandles.LOUD_SOUND_REDUCTION) - if (data.size == 2) { - loudSoundReductionEnabled = data[1].toInt() != 0 - Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}") - parsed = true - break - } else { - Log.d("LoudSoundReduction", "Read attempt $attempt returned empty data") - } + loudSoundReductionEnabled = data[0].toInt() != 0 + Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}") + parsed = true + break } catch (e: Exception) { Log.w("LoudSoundReduction", "Read attempt $attempt failed: ${e.message}") } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 3715c9d8..56021a59 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -106,6 +106,9 @@ import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.RadareOffsetFinder +import me.kavishdevar.librepods.utils.TransparencySettings +import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse +import me.kavishdevar.librepods.utils.sendTransparencySettings import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -1136,182 +1139,6 @@ fun AccessibilityToggle(text: String, mutableState: MutableState, indep } } -private data class TransparencySettings ( - val enabled: Boolean, - val leftEQ: FloatArray, - val rightEQ: FloatArray, - val leftAmplification: Float, - val rightAmplification: Float, - val leftTone: Float, - val rightTone: Float, - val leftConversationBoost: Boolean, - val rightConversationBoost: Boolean, - val leftAmbientNoiseReduction: Float, - val rightAmbientNoiseReduction: Float, - val netAmplification: Float, - val balance: Float -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TransparencySettings - - if (enabled != other.enabled) return false - if (leftAmplification != other.leftAmplification) return false - if (rightAmplification != other.rightAmplification) return false - if (leftTone != other.leftTone) return false - if (rightTone != other.rightTone) return false - if (leftConversationBoost != other.leftConversationBoost) return false - if (rightConversationBoost != other.rightConversationBoost) return false - if (leftAmbientNoiseReduction != other.leftAmbientNoiseReduction) return false - if (rightAmbientNoiseReduction != other.rightAmbientNoiseReduction) return false - if (!leftEQ.contentEquals(other.leftEQ)) return false - if (!rightEQ.contentEquals(other.rightEQ)) return false - - return true - } - - override fun hashCode(): Int { - var result = enabled.hashCode() - result = 31 * result + leftAmplification.hashCode() - result = 31 * result + rightAmplification.hashCode() - result = 31 * result + leftTone.hashCode() - result = 31 * result + rightTone.hashCode() - result = 31 * result + leftConversationBoost.hashCode() - result = 31 * result + rightConversationBoost.hashCode() - result = 31 * result + leftAmbientNoiseReduction.hashCode() - result = 31 * result + rightAmbientNoiseReduction.hashCode() - result = 31 * result + leftEQ.contentHashCode() - result = 31 * result + rightEQ.contentHashCode() - return result - } -} - -private fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings? { - val settingsData = data.copyOfRange(1, data.size) - val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) - - val enabled = buffer.float - Log.d(TAG, "Parsed enabled: $enabled") - - val leftEQ = FloatArray(8) - for (i in 0..7) { - leftEQ[i] = buffer.float - Log.d(TAG, "Parsed left EQ${i+1}: ${leftEQ[i]}") - } - val leftAmplification = buffer.float - Log.d(TAG, "Parsed left amplification: $leftAmplification") - val leftTone = buffer.float - Log.d(TAG, "Parsed left tone: $leftTone") - val leftConvFloat = buffer.float - val leftConversationBoost = leftConvFloat > 0.5f - Log.d(TAG, "Parsed left conversation boost: $leftConvFloat ($leftConversationBoost)") - val leftAmbientNoiseReduction = buffer.float - Log.d(TAG, "Parsed left ambient noise reduction: $leftAmbientNoiseReduction") - - val rightEQ = FloatArray(8) - for (i in 0..7) { - rightEQ[i] = buffer.float - Log.d(TAG, "Parsed right EQ${i+1}: ${rightEQ[i]}") - } - - val rightAmplification = buffer.float - Log.d(TAG, "Parsed right amplification: $rightAmplification") - val rightTone = buffer.float - Log.d(TAG, "Parsed right tone: $rightTone") - val rightConvFloat = buffer.float - val rightConversationBoost = rightConvFloat > 0.5f - Log.d(TAG, "Parsed right conversation boost: $rightConvFloat ($rightConversationBoost)") - val rightAmbientNoiseReduction = buffer.float - Log.d(TAG, "Parsed right ambient noise reduction: $rightAmbientNoiseReduction") - - Log.d(TAG, "Settings parsed successfully") - - val avg = (leftAmplification + rightAmplification) / 2 - val amplification = avg.coerceIn(-1f, 1f) - val diff = rightAmplification - leftAmplification - val balance = diff.coerceIn(-1f, 1f) - - return TransparencySettings( - enabled = enabled > 0.5f, - leftEQ = leftEQ, - rightEQ = rightEQ, - leftAmplification = leftAmplification, - rightAmplification = rightAmplification, - leftTone = leftTone, - rightTone = rightTone, - leftConversationBoost = leftConversationBoost, - rightConversationBoost = rightConversationBoost, - leftAmbientNoiseReduction = leftAmbientNoiseReduction, - rightAmbientNoiseReduction = rightAmbientNoiseReduction, - netAmplification = amplification, - balance = balance - ) -} - -private fun sendTransparencySettings( - attManager: ATTManager, - transparencySettings: TransparencySettings -) { - debounceJob?.cancel() - debounceJob = CoroutineScope(Dispatchers.IO).launch { - delay(100) - try { - val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) - - Log.d(TAG, - "Sending settings: $transparencySettings" - ) - - buffer.putFloat(if (transparencySettings.enabled) 1.0f else 0.0f) - - for (eq in transparencySettings.leftEQ) { - buffer.putFloat(eq) - } - buffer.putFloat(transparencySettings.leftAmplification) - buffer.putFloat(transparencySettings.leftTone) - buffer.putFloat(if (transparencySettings.leftConversationBoost) 1.0f else 0.0f) - buffer.putFloat(transparencySettings.leftAmbientNoiseReduction) - - for (eq in transparencySettings.rightEQ) { - buffer.putFloat(eq) - } - buffer.putFloat(transparencySettings.rightAmplification) - buffer.putFloat(transparencySettings.rightTone) - buffer.putFloat(if (transparencySettings.rightConversationBoost) 1.0f else 0.0f) - buffer.putFloat(transparencySettings.rightAmbientNoiseReduction) - - val data = buffer.array() - attManager.write( - ATTHandles.TRANSPARENCY, - value = data - ) - } catch (e: IOException) { - e.printStackTrace() - } - } -} - -// Debounced send helper for phone/media EQ (if needed elsewhere) -private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPManager?, eq: FloatArray, phoneEnabled: Boolean, mediaEnabled: Boolean) { - phoneMediaDebounceJob?.cancel() - phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { - delay(100) - try { - if (aacpManager == null) { - Log.w(TAG, "AACPManger is null; cannot send phone/media EQ") - return@launch - } - val phoneByte = if (phoneEnabled) 0x01.toByte() else 0x02.toByte() - val mediaByte = if (mediaEnabled) 0x01.toByte() else 0x02.toByte() - aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) - } catch (e: Exception) { - Log.w(TAG, "Error in sendPhoneMediaEQ: ${e.message}") - } - } -} - private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value @@ -1369,3 +1196,22 @@ private fun DropdownMenuComponent( } } } + +// Debounced send helper for phone/media EQ (if needed elsewhere) +private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPManager?, eq: FloatArray, phoneEnabled: Boolean, mediaEnabled: Boolean) { + phoneMediaDebounceJob?.cancel() + phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + if (aacpManager == null) { + Log.w(TAG, "AACPManger is null; cannot send phone/media EQ") + return@launch + } + val phoneByte = if (phoneEnabled) 0x01.toByte() else 0x02.toByte() + val mediaByte = if (mediaEnabled) 0x01.toByte() else 0x02.toByte() + aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) + } catch (e: Exception) { + Log.w(TAG, "Error in sendPhoneMediaEQ: ${e.message}") + } + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 287c8a87..25b2ebaa 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -115,7 +115,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi private var debounceJob: Job? = null private var phoneMediaDebounceJob: Job? = null -private const val TAG = "AccessibilitySettings" +private const val TAG = "HearingAidAdjustments" @SuppressLint("DefaultLocale") @ExperimentalHazeMaterialsApi @@ -201,6 +201,7 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { val ambientNoiseReductionSliderValue = remember { mutableFloatStateOf(0.0f) } val conversationBoostEnabled = remember { mutableStateOf(false) } val eq = remember { mutableStateOf(FloatArray(8)) } + val ownVoiceAmplification = remember { mutableFloatStateOf(0.5f) } val phoneMediaEQ = remember { mutableStateOf(FloatArray(8) { 0.5f }) } val phoneEQEnabled = remember { mutableStateOf(false) } @@ -214,7 +215,6 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { val HearingAidSettings = remember { mutableStateOf( HearingAidSettings( - enabled = enabled.value, leftEQ = eq.value, rightEQ = eq.value, leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, @@ -226,7 +226,8 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, netAmplification = amplificationSliderValue.floatValue, - balance = balanceSliderValue.floatValue + balance = balanceSliderValue.floatValue, + ownVoiceAmplification = ownVoiceAmplification.floatValue ) ) } @@ -250,6 +251,26 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { } } + val hearingAidATTListener = remember { + object : (ByteArray) -> Unit { + override fun invoke(value: ByteArray) { + val parsed = parseHearingAidSettingsResponse(value) + if (parsed != null) { + amplificationSliderValue.floatValue = parsed.netAmplification + balanceSliderValue.floatValue = parsed.balance + toneSliderValue.floatValue = parsed.leftTone + ambientNoiseReductionSliderValue.floatValue = parsed.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsed.leftConversationBoost + eq.value = parsed.leftEQ.copyOf() + ownVoiceAmplification.floatValue = parsed.ownVoiceAmplification + Log.d(TAG, "Updated hearing aid settings from notification") + } else { + Log.w(TAG, "Failed to parse hearing aid settings from notification") + } + } + } + } + LaunchedEffect(Unit) { aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) @@ -259,10 +280,11 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { onDispose { aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + attManager.unregisterListener(ATTHandles.HEARING_AID, hearingAidATTListener) } } - LaunchedEffect(enabled.value, amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, eq.value, initialLoadComplete.value, initialReadSucceeded.value) { + LaunchedEffect(amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, ownVoiceAmplification.floatValue, initialLoadComplete.value, initialReadSucceeded.value) { if (!initialLoadComplete.value) { Log.d(TAG, "Initial device load not complete - skipping send") return@LaunchedEffect @@ -274,7 +296,6 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { } HearingAidSettings.value = HearingAidSettings( - enabled = enabled.value, leftEQ = eq.value, rightEQ = eq.value, leftAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue < 0) -balanceSliderValue.floatValue else 0f, @@ -286,23 +307,18 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, netAmplification = amplificationSliderValue.floatValue, - balance = balanceSliderValue.floatValue + balance = balanceSliderValue.floatValue, + ownVoiceAmplification = ownVoiceAmplification.floatValue ) - Log.d("HearingAidSettings", "Updated settings: ${HearingAidSettings.value}") - // sendHearingAidSettings(attManager, HearingAidSettings.value) - } - - DisposableEffect(Unit) { - onDispose { - // attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener) - } + Log.d(TAG, "Updated settings: ${HearingAidSettings.value}") + sendHearingAidSettings(attManager, HearingAidSettings.value) } LaunchedEffect(Unit) { Log.d(TAG, "Connecting to ATT...") try { - // attManager.enableNotifications(ATTHandles.TRANSPARENCY) - // attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener) + attManager.enableNotifications(ATTHandles.HEARING_AID) + attManager.registerListener(ATTHandles.HEARING_AID, hearingAidATTListener) try { if (aacpManager != null) { @@ -324,12 +340,11 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { Log.w(TAG, "Error reading EQ from AACPManager: ${e.message}") } - /* var parsedSettings: HearingAidSettings? = null for (attempt in 1..3) { initialReadAttempts.value = attempt try { - val data = attManager.read(ATTHandles.TRANSPARENCY) + val data = attManager.read(ATTHandles.HEARING_AID) parsedSettings = parseHearingAidSettingsResponse(data = data) if (parsedSettings != null) { Log.d(TAG, "Parsed settings on attempt $attempt") @@ -344,19 +359,18 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { } if (parsedSettings != null) { - Log.d(TAG, "Initial transparency settings: $parsedSettings") - enabled.value = parsedSettings.enabled + Log.d(TAG, "Initial hearing aid settings: $parsedSettings") amplificationSliderValue.floatValue = parsedSettings.netAmplification balanceSliderValue.floatValue = parsedSettings.balance toneSliderValue.floatValue = parsedSettings.leftTone ambientNoiseReductionSliderValue.floatValue = parsedSettings.leftAmbientNoiseReduction conversationBoostEnabled.value = parsedSettings.leftConversationBoost eq.value = parsedSettings.leftEQ.copyOf() + ownVoiceAmplification.floatValue = parsedSettings.ownVoiceAmplification initialReadSucceeded.value = true } else { - Log.d(TAG, "Failed to read/parse initial transparency settings after ${initialReadAttempts.value} attempts") + Log.d(TAG, "Failed to read/parse initial hearing aid settings after ${initialReadAttempts.value} attempts") } - */ } catch (e: IOException) { e.printStackTrace() } finally { @@ -364,26 +378,6 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { } } - LaunchedEffect(phoneMediaEQ.value, phoneEQEnabled.value, mediaEQEnabled.value) { - phoneMediaDebounceJob?.cancel() - phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { - delay(150) - val manager = ServiceManager.getService()?.aacpManager - if (manager == null) { - Log.w(TAG, "Cannot write EQ: AACPManager not available") - return@launch - } - try { - val phoneByte = if (phoneEQEnabled.value) 0x01.toByte() else 0x02.toByte() - val mediaByte = if (mediaEQEnabled.value) 0x01.toByte() else 0x02.toByte() - Log.d(TAG, "Sending phone/media EQ (phoneEnabled=${phoneEQEnabled.value}, mediaEnabled=${mediaEQEnabled.value})") - manager.sendPhoneMediaEQ(phoneMediaEQ.value, phoneByte, mediaByte) - } catch (e: Exception) { - Log.w(TAG, "Error sending phone/media EQ: ${e.message}") - } - } - } - val isDarkThemeLocal = isSystemInDarkTheme() var backgroundColorHA by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColorHA by animateColorAsState(targetValue = backgroundColorHA, animationSpec = tween(durationMillis = 500)) @@ -577,7 +571,7 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { modifier = Modifier .fillMaxWidth() .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween + horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = stringResource(R.string.less), @@ -619,7 +613,6 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { } private data class HearingAidSettings( - val enabled: Boolean, val leftEQ: FloatArray, val rightEQ: FloatArray, val leftAmplification: Float, @@ -631,7 +624,8 @@ private data class HearingAidSettings( val leftAmbientNoiseReduction: Float, val rightAmbientNoiseReduction: Float, val netAmplification: Float, - val balance: Float + val balance: Float, + val ownVoiceAmplification: Float ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -639,7 +633,6 @@ private data class HearingAidSettings( other as HearingAidSettings - if (enabled != other.enabled) return false if (leftAmplification != other.leftAmplification) return false if (rightAmplification != other.rightAmplification) return false if (leftTone != other.leftTone) return false @@ -650,13 +643,13 @@ private data class HearingAidSettings( if (rightAmbientNoiseReduction != other.rightAmbientNoiseReduction) return false if (!leftEQ.contentEquals(other.leftEQ)) return false if (!rightEQ.contentEquals(other.rightEQ)) return false + if (ownVoiceAmplification != other.ownVoiceAmplification) return false return true } override fun hashCode(): Int { - var result = enabled.hashCode() - result = 31 * result + leftAmplification.hashCode() + var result = leftAmplification.hashCode() result = 31 * result + rightAmplification.hashCode() result = 31 * result + leftTone.hashCode() result = 31 * result + rightTone.hashCode() @@ -666,49 +659,40 @@ private data class HearingAidSettings( result = 31 * result + rightAmbientNoiseReduction.hashCode() result = 31 * result + leftEQ.contentHashCode() result = 31 * result + rightEQ.contentHashCode() + result = 31 * result + ownVoiceAmplification.hashCode() return result } } private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings? { - val settingsData = data.copyOfRange(1, data.size) - val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) + if (data.size < 104) return null + val buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN) - val enabled = buffer.float - Log.d(TAG, "Parsed enabled: $enabled") + val phoneEnabled = buffer.get() == 0x01.toByte() + val mediaEnabled = buffer.get() == 0x01.toByte() + buffer.getShort() // skip 0x60 0x00 val leftEQ = FloatArray(8) for (i in 0..7) { leftEQ[i] = buffer.float - Log.d(TAG, "Parsed left EQ${i+1}: ${leftEQ[i]}") } val leftAmplification = buffer.float - Log.d(TAG, "Parsed left amplification: $leftAmplification") val leftTone = buffer.float - Log.d(TAG, "Parsed left tone: $leftTone") val leftConvFloat = buffer.float val leftConversationBoost = leftConvFloat > 0.5f - Log.d(TAG, "Parsed left conversation boost: $leftConvFloat ($leftConversationBoost)") val leftAmbientNoiseReduction = buffer.float - Log.d(TAG, "Parsed left ambient noise reduction: $leftAmbientNoiseReduction") val rightEQ = FloatArray(8) for (i in 0..7) { rightEQ[i] = buffer.float - Log.d(TAG, "Parsed right EQ${i+1}: $rightEQ[i]") } - val rightAmplification = buffer.float - Log.d(TAG, "Parsed right amplification: $rightAmplification") val rightTone = buffer.float - Log.d(TAG, "Parsed right tone: $rightTone") val rightConvFloat = buffer.float val rightConversationBoost = rightConvFloat > 0.5f - Log.d(TAG, "Parsed right conversation boost: $rightConvFloat ($rightConversationBoost)") val rightAmbientNoiseReduction = buffer.float - Log.d(TAG, "Parsed right ambient noise reduction: $rightAmbientNoiseReduction") - Log.d(TAG, "Settings parsed successfully") + val ownVoiceAmplification = buffer.float val avg = (leftAmplification + rightAmplification) / 2 val amplification = avg.coerceIn(-1f, 1f) @@ -716,7 +700,6 @@ private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings val balance = diff.coerceIn(-1f, 1f) return HearingAidSettings( - enabled = enabled > 0.5f, leftEQ = leftEQ, rightEQ = rightEQ, leftAmplification = leftAmplification, @@ -728,7 +711,8 @@ private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings leftAmbientNoiseReduction = leftAmbientNoiseReduction, rightAmbientNoiseReduction = rightAmbientNoiseReduction, netAmplification = amplification, - balance = balance + balance = balance, + ownVoiceAmplification = ownVoiceAmplification ) } @@ -740,59 +724,41 @@ private fun sendHearingAidSettings( debounceJob = CoroutineScope(Dispatchers.IO).launch { delay(100) try { - val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) + val currentData = attManager.read(ATTHandles.HEARING_AID) + Log.d(TAG, "Current data before update: ${currentData.joinToString(" ") { String.format("%02X", it) }}") + if (currentData.size < 104) { + Log.w(TAG, "Current data size ${currentData.size} too small, cannot send settings") + return@launch + } + val buffer = ByteBuffer.wrap(currentData).order(ByteOrder.LITTLE_ENDIAN) - Log.d(TAG, - "Sending settings: $HearingAidSettings" - ) + // for some reason + buffer.put(2, 0x64) - buffer.putFloat(if (HearingAidSettings.enabled) 1.0f else 0.0f) + // Left ear adjustments + buffer.putFloat(36, HearingAidSettings.leftAmplification) + buffer.putFloat(40, HearingAidSettings.leftTone) + buffer.putFloat(44, if (HearingAidSettings.leftConversationBoost) 1.0f else 0.0f) + buffer.putFloat(48, HearingAidSettings.leftAmbientNoiseReduction) - for (eq in HearingAidSettings.leftEQ) { - buffer.putFloat(eq) - } - buffer.putFloat(HearingAidSettings.leftAmplification) - buffer.putFloat(HearingAidSettings.leftTone) - buffer.putFloat(if (HearingAidSettings.leftConversationBoost) 1.0f else 0.0f) - buffer.putFloat(HearingAidSettings.leftAmbientNoiseReduction) + // Right ear adjustments + buffer.putFloat(84, HearingAidSettings.rightAmplification) + buffer.putFloat(88, HearingAidSettings.rightTone) + buffer.putFloat(92, if (HearingAidSettings.rightConversationBoost) 1.0f else 0.0f) + buffer.putFloat(96, HearingAidSettings.rightAmbientNoiseReduction) - for (eq in HearingAidSettings.rightEQ) { - buffer.putFloat(eq) - } - buffer.putFloat(HearingAidSettings.rightAmplification) - buffer.putFloat(HearingAidSettings.rightTone) - buffer.putFloat(if (HearingAidSettings.rightConversationBoost) 1.0f else 0.0f) - buffer.putFloat(HearingAidSettings.rightAmbientNoiseReduction) - - val data = buffer.array() - attManager.write( - ATTHandles.TRANSPARENCY, - value = data - ) + // Own voice amplification + buffer.putFloat(100, HearingAidSettings.ownVoiceAmplification) + + Log.d(TAG, "Sending updated settings: ${currentData.joinToString(" ") { String.format("%02X", it) }}") + + attManager.write(ATTHandles.HEARING_AID, currentData) } catch (e: IOException) { e.printStackTrace() } } } -private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPManager?, eq: FloatArray, phoneEnabled: Boolean, mediaEnabled: Boolean) { - phoneMediaDebounceJob?.cancel() - phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { - delay(100) - try { - if (aacpManager == null) { - Log.w(TAG, "AACPManger is null; cannot send phone/media EQ") - return@launch - } - val phoneByte = if (phoneEnabled) 0x01.toByte() else 0x02.toByte() - val mediaByte = if (mediaEnabled) 0x01.toByte() else 0x02.toByte() - aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) - } catch (e: Exception) { - Log.w(TAG, "Error in sendPhoneMediaEQ: ${e.message}") - } - } -} - private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 0557e166..bdd1cc59 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -59,6 +59,7 @@ import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -71,14 +72,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -101,6 +103,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.composables.ConfirmationDialog import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch import me.kavishdevar.librepods.composables.SinglePodANCSwitch import me.kavishdevar.librepods.composables.StyledSwitch @@ -111,6 +114,9 @@ import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.RadareOffsetFinder +import me.kavishdevar.librepods.utils.TransparencySettings +import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse +import me.kavishdevar.librepods.utils.sendTransparencySettings import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -143,6 +149,14 @@ fun HearingAidScreen(navController: NavController) { val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) val labelTextColor = if (isDarkTheme) Color.White else Color.Black + val showDialog = remember { mutableStateOf(false) } + + val hearingAidEnabled = remember { + val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } + val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } + mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte())) + } + Scaffold( containerColor = if (isSystemInDarkTheme()) Color( 0xFF000000 @@ -202,12 +216,6 @@ fun HearingAidScreen(navController: NavController) { .verticalScroll(verticalScrollState), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - val hearingAidEnabled = remember { - val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } - val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } - mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte())) - } - val hearingAidListener = remember { object : AACPManager.ControlCommandListener { override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { @@ -239,20 +247,20 @@ fun HearingAidScreen(navController: NavController) { fun onChange(value: Boolean) { if (value) { - // Enable and enroll if not enrolled - val enrolled = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID }?.value?.getOrNull(0) == 0x01.toByte() - if (!enrolled) { - aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) // Enroll and enable - } else { - aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) // Enable - } - aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x01.toByte()) // Enable assist + showDialog.value = true } else { - // Disable both, keep enrolled - aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x02)) // Disable - aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x02.toByte()) // Disable assist + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x02)) + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x02.toByte()) + hearingAidEnabled.value = value } - hearingAidEnabled.value = value + } + + fun onAdjustPhoneChange(value: Boolean) { + adjustPhoneEnabled.value = value + } + + fun onAdjustMediaChange(value: Boolean) { + adjustMediaEnabled.value = value } Text( @@ -374,7 +382,7 @@ fun HearingAidScreen(navController: NavController) { backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - adjustMediaEnabled.value = !adjustMediaEnabled.value + onAdjustMediaChange(!adjustMediaEnabled.value) } ) }, @@ -393,7 +401,7 @@ fun HearingAidScreen(navController: NavController) { StyledSwitch( checked = adjustMediaEnabled.value, onCheckedChange = { - adjustMediaEnabled.value = it + onAdjustMediaChange(it) }, ) } @@ -419,7 +427,7 @@ fun HearingAidScreen(navController: NavController) { backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - adjustPhoneEnabled.value = !adjustPhoneEnabled.value + onAdjustPhoneChange(!adjustPhoneEnabled.value) } ) }, @@ -438,11 +446,47 @@ fun HearingAidScreen(navController: NavController) { StyledSwitch( checked = adjustPhoneEnabled.value, onCheckedChange = { - adjustPhoneEnabled.value = it + onAdjustPhoneChange(it) }, ) } } } } + + ConfirmationDialog( + showDialog = showDialog, + title = "Enable Hearing Aid", + message = "Enabling Hearing Aid will disable Headphone Accommodation and Customized Transparency Mode.", + confirmText = "Enable", + dismissText = "Cancel", + onConfirm = { + showDialog.value = false + val enrolled = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID }?.value?.getOrNull(0) == 0x01.toByte() + if (!enrolled) { + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) + } else { + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) + } + aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x01.toByte()) + hearingAidEnabled.value = true + // Disable transparency mode + CoroutineScope(Dispatchers.IO).launch { + try { + val data = attManager.read(ATTHandles.TRANSPARENCY) + val parsed = parseTransparencySettingsResponse(data) + if (parsed != null) { + val disabledSettings = parsed.copy(enabled = false) + sendTransparencySettings(attManager, disabledSettings) + } + } catch (e: Exception) { + Log.e(TAG, "Error disabling transparency: ${e.message}") + } + } + }, + hazeState = hazeState, + isDarkTheme = isDarkTheme, + textColor = textColor, + activeTrackColor = activeTrackColor + ) } \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index 33122176..3a52634a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -39,8 +39,8 @@ import java.util.concurrent.TimeUnit enum class ATTHandles(val value: Int) { TRANSPARENCY(0x18), - LOUD_SOUND_REDUCTION(0x1b), - HEARING_AID(0x2a), + LOUD_SOUND_REDUCTION(0x1B), + HEARING_AID(0x2A), } enum class ATTCCCDHandles(val value: Int) { @@ -85,7 +85,7 @@ class ATTManager(private val device: BluetoothDevice) { if (pdu.isNotEmpty() && pdu[0] == OPCODE_HANDLE_VALUE_NTF) { // notification -> dispatch to listeners val handle = (pdu[1].toInt() and 0xFF) or ((pdu[2].toInt() and 0xFF) shl 8) - val value = pdu.copyOfRange(2, pdu.size) + val value = pdu.copyOfRange(3, pdu.size) listeners[handle]?.forEach { listener -> try { listener(value) @@ -191,7 +191,7 @@ class ATTManager(private val device: BluetoothDevice) { throw IllegalStateException("No response read from ATT socket within $timeoutMs ms") } Log.d(TAG, "readResponse: ${resp.joinToString(" ") { String.format("%02X", it) }}") - return resp + return resp.copyOfRange(1, resp.size) } catch (e: InterruptedException) { Thread.currentThread().interrupt() throw IllegalStateException("Interrupted while waiting for ATT response", e) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt new file mode 100644 index 00000000..b8de0e16 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt @@ -0,0 +1,149 @@ +package me.kavishdevar.librepods.utils + +import android.util.Log +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.ByteOrder + +private const val TAG = "TransparencyUtils" + +data class TransparencySettings( + val enabled: Boolean, + val leftEQ: FloatArray, + val rightEQ: FloatArray, + val leftAmplification: Float, + val rightAmplification: Float, + val leftTone: Float, + val rightTone: Float, + val leftConversationBoost: Boolean, + val rightConversationBoost: Boolean, + val leftAmbientNoiseReduction: Float, + val rightAmbientNoiseReduction: Float, + val netAmplification: Float, + val balance: Float +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TransparencySettings + + if (enabled != other.enabled) return false + if (leftAmplification != other.leftAmplification) return false + if (rightAmplification != other.rightAmplification) return false + if (leftTone != other.leftTone) return false + if (rightTone != other.rightTone) return false + if (leftConversationBoost != other.leftConversationBoost) return false + if (rightConversationBoost != other.rightConversationBoost) return false + if (leftAmbientNoiseReduction != other.leftAmbientNoiseReduction) return false + if (rightAmbientNoiseReduction != other.rightAmbientNoiseReduction) return false + if (!leftEQ.contentEquals(other.leftEQ)) return false + if (!rightEQ.contentEquals(other.rightEQ)) return false + + return true + } + + override fun hashCode(): Int { + var result = enabled.hashCode() + result = 31 * result + leftAmplification.hashCode() + result = 31 * result + rightAmplification.hashCode() + result = 31 * result + leftTone.hashCode() + result = 31 * result + rightTone.hashCode() + result = 31 * result + leftConversationBoost.hashCode() + result = 31 * result + rightConversationBoost.hashCode() + result = 31 * result + leftAmbientNoiseReduction.hashCode() + result = 31 * result + rightAmbientNoiseReduction.hashCode() + result = 31 * result + leftEQ.contentHashCode() + result = 31 * result + rightEQ.contentHashCode() + return result + } +} + +fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings? { + val settingsData = data + val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) + + val enabled = buffer.float + + val leftEQ = FloatArray(8) + for (i in 0..7) { + leftEQ[i] = buffer.float + } + val leftAmplification = buffer.float + val leftTone = buffer.float + val leftConvFloat = buffer.float + val leftConversationBoost = leftConvFloat > 0.5f + val leftAmbientNoiseReduction = buffer.float + + val rightEQ = FloatArray(8) + for (i in 0..7) { + rightEQ[i] = buffer.float + } + + val rightAmplification = buffer.float + val rightTone = buffer.float + val rightConvFloat = buffer.float + val rightConversationBoost = rightConvFloat > 0.5f + val rightAmbientNoiseReduction = buffer.float + + val avg = (leftAmplification + rightAmplification) / 2 + val amplification = avg.coerceIn(-1f, 1f) + val diff = rightAmplification - leftAmplification + val balance = diff.coerceIn(-1f, 1f) + + return TransparencySettings( + enabled = enabled > 0.5f, + leftEQ = leftEQ, + rightEQ = rightEQ, + leftAmplification = leftAmplification, + rightAmplification = rightAmplification, + leftTone = leftTone, + rightTone = rightTone, + leftConversationBoost = leftConversationBoost, + rightConversationBoost = rightConversationBoost, + leftAmbientNoiseReduction = leftAmbientNoiseReduction, + rightAmbientNoiseReduction = rightAmbientNoiseReduction, + netAmplification = amplification, + balance = balance + ) +} + +private var debounceJob: Job? = null + +fun sendTransparencySettings(attManager: ATTManager, transparencySettings: TransparencySettings) { + debounceJob?.cancel() + debounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(100) + try { + val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) + + buffer.putFloat(if (transparencySettings.enabled) 1.0f else 0.0f) + + for (eq in transparencySettings.leftEQ) { + buffer.putFloat(eq) + } + buffer.putFloat(transparencySettings.leftAmplification) + buffer.putFloat(transparencySettings.leftTone) + buffer.putFloat(if (transparencySettings.leftConversationBoost) 1.0f else 0.0f) + buffer.putFloat(transparencySettings.leftAmbientNoiseReduction) + + for (eq in transparencySettings.rightEQ) { + buffer.putFloat(eq) + } + buffer.putFloat(transparencySettings.rightAmplification) + buffer.putFloat(transparencySettings.rightTone) + buffer.putFloat(if (transparencySettings.rightConversationBoost) 1.0f else 0.0f) + buffer.putFloat(transparencySettings.rightAmbientNoiseReduction) + + val data = buffer.array() + attManager.write(ATTHandles.TRANSPARENCY, value = data) + } catch (e: IOException) { + e.printStackTrace() + } + } +} From 4bc76de750ba2690d017f957d8d73f4c6a9e7bb7 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 23 Sep 2025 00:03:03 +0530 Subject: [PATCH 30/72] android: liquidglass sliders --- android/app/build.gradle.kts | 3 + android/app/src/main/AndroidManifest.xml | 5 +- .../librepods/CustomDeviceActivity.kt | 55 -- .../me/kavishdevar/librepods/MainActivity.kt | 8 + .../composables/AccessibilitySlider.kt | 139 ---- .../composables/CallControlSettings.kt | 14 +- .../composables/ConfirmationDialog.kt | 25 +- .../composables/MicrophoneSettings.kt | 54 +- .../librepods/composables/StyledSlider.kt | 364 +++++++++ .../librepods/composables/StyledSwitch.kt | 10 +- .../librepods/composables/ToneVolumeSlider.kt | 190 ----- .../screens/AccessibilitySettingsScreen.kt | 733 +++++------------- .../screens/AirPodsSettingsScreen.kt | 19 +- .../screens/HearingAidAdjustmentsScreen.kt | 312 ++------ .../librepods/screens/HearingAidScreen.kt | 65 +- .../screens/TransparencySettingsScreen.kt | 563 ++++++++++++++ .../kavishdevar/librepods/utils/ATTManager.kt | 1 - android/gradle/libs.versions.toml | 16 +- 18 files changed, 1318 insertions(+), 1258 deletions(-) delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index c8c0f9d6..2e7067ec 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -62,5 +62,8 @@ dependencies { implementation(libs.haze) implementation(libs.haze.materials) implementation(libs.androidx.dynamicanimation) + implementation(libs.androidx.compose.foundation.layout) compileOnly(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) + implementation(fileTree(mapOf("dir" to "lib", "include" to listOf("*.aar")))) + debugImplementation(libs.androidx.compose.ui.tooling) } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c9604273..08024b03 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,8 @@ android:required="false" /> - + @@ -32,6 +33,8 @@ tools:ignore="ScopedStorage" /> + diff --git a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt deleted file mode 100644 index bb9c9d8a..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/CustomDeviceActivity.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package me.kavishdevar.librepods - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen -import me.kavishdevar.librepods.ui.theme.LibrePodsTheme - -@ExperimentalHazeMaterialsApi -class CustomDevice : ComponentActivity() { - @SuppressLint("MissingPermission") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContent { - LibrePodsTheme { - val navController = rememberNavController() - - NavHost(navController = navController, startDestination = "main") { - composable("main") { - AccessibilitySettingsScreen() - } - } - } - } - } - - override fun onDestroy() { - super.onDestroy() - } -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 0395239b..9ffc793f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -108,12 +108,14 @@ import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberMultiplePermissionsState import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.constants.AirPodsNotifications +import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen import me.kavishdevar.librepods.screens.AirPodsSettingsScreen import me.kavishdevar.librepods.screens.AppSettingsScreen import me.kavishdevar.librepods.screens.DebugScreen import me.kavishdevar.librepods.screens.HeadTrackingScreen import me.kavishdevar.librepods.screens.HearingAidScreen import me.kavishdevar.librepods.screens.HearingAidAdjustmentsScreen +import me.kavishdevar.librepods.screens.TransparencySettingsScreen import me.kavishdevar.librepods.screens.LongPress import me.kavishdevar.librepods.screens.Onboarding import me.kavishdevar.librepods.screens.RenameScreen @@ -382,6 +384,12 @@ fun Main() { composable("onboarding") { Onboarding(navController, context) } + composable("accessibility") { + AccessibilitySettingsScreen(navController) + } + composable("transparency_customization") { + TransparencySettingsScreen(navController) + } composable("hearing_aid") { HearingAidScreen(navController) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt deleted file mode 100644 index 2141a013..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AccessibilitySlider.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package me.kavishdevar.librepods.composables - -import androidx.compose.foundation.background -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.fillMaxWidth -import androidx.compose.foundation.layout.height -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.Slider -import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -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.text.TextStyle -import androidx.compose.ui.text.font.Font -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 me.kavishdevar.librepods.R -import kotlin.math.roundToInt - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun AccessibilitySlider( - label: String? = null, - value: Float, - onValueChange: (Float) -> Unit, - valueRange: ClosedFloatingPointRange, - widthFrac: Float = 1f -) { - val isDarkTheme = isSystemInDarkTheme() - - 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 - - Column( - modifier = Modifier.fillMaxWidth(widthFrac), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (label != null) { - Text( - text = label, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = labelTextColor, - fontFamily = androidx.compose.ui.text.font.FontFamily(Font(R.font.sf_pro)) - ) - ) - } - - Slider( - value = value, - onValueChange = onValueChange, - valueRange = valueRange, - onValueChangeFinished = { - // Round to 2 decimal places - onValueChange((value * 100).roundToInt() / 100f) - }, - modifier = Modifier - .fillMaxWidth() - .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((value - valueRange.start) / (valueRange.endInclusive - valueRange.start)) - .height(4.dp) - .background(activeTrackColor, RoundedCornerShape(4.dp)) - ) - } - } - ) - } -} - -@Preview -@Composable -fun AccessibilitySliderPreview() { - AccessibilitySlider( - label = "Test Slider", - value = 1.0f, - onValueChange = {}, - valueRange = 0f..2f - ) -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt index 76d60701..c485c3a9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -61,13 +61,16 @@ 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 dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi +@ExperimentalHazeMaterialsApi @Composable -fun CallControlSettings() { +fun CallControlSettings(hazeState: HazeState) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) @@ -306,7 +309,8 @@ fun CallControlSettings() { 0x03 ) else byteArrayOf(0x00, 0x02) service.aacpManager.sendControlCommand(0x24, bytes) - } + }, + hazeState = hazeState ) } } @@ -433,7 +437,8 @@ fun CallControlSettings() { 0x02 ) else byteArrayOf(0x00, 0x03) service.aacpManager.sendControlCommand(0x24, bytes) - } + }, + hazeState = hazeState ) } } @@ -441,8 +446,9 @@ fun CallControlSettings() { } } +@ExperimentalHazeMaterialsApi @Preview @Composable fun CallControlSettingsPreview() { - CallControlSettings() + CallControlSettings(HazeState()) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index 5587501a..1378c335 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -1,6 +1,7 @@ package me.kavishdevar.librepods.composables import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -9,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidthIn import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider @@ -25,7 +27,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily @@ -51,18 +52,24 @@ fun ConfirmationDialog( onConfirm: () -> Unit, onDismiss: () -> Unit = { showDialog.value = false }, hazeState: HazeState, - isDarkTheme: Boolean, - textColor: Color, - activeTrackColor: Color ) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val accentColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) if (showDialog.value) { Dialog(onDismissRequest = { showDialog.value = false }) { Box( modifier = Modifier - .fillMaxWidth() - .background(if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f), RoundedCornerShape(14.dp)) + .fillMaxWidth(0.75f) + .requiredWidthIn(min = 200.dp, max = 360.dp) + .background(Color.Transparent, RoundedCornerShape(14.dp)) .clip(RoundedCornerShape(14.dp)) - .hazeEffect(hazeState, CupertinoMaterials.regular()) + .hazeEffect( + hazeState, + style = CupertinoMaterials.regular( + containerColor = if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f) + ) + ) ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(24.dp)) @@ -155,7 +162,7 @@ fun ConfirmationDialog( .background(if (leftPressed) pressedColor else Color.Transparent), contentAlignment = Alignment.Center ) { - Text(dismissText, color = activeTrackColor) + Text(dismissText, color = accentColor) } Box( modifier = Modifier @@ -170,7 +177,7 @@ fun ConfirmationDialog( .background(if (rightPressed) pressedColor else Color.Transparent), contentAlignment = Alignment.Center ) { - Text(confirmText, color = activeTrackColor) + Text(confirmText, color = accentColor) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt index 5ff5f384..8e10609c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.annotation.SuppressLint import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -70,19 +71,29 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Popup +import dev.chrisbanes.haze.HazeEffectScope +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.HazeTint +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi +@ExperimentalHazeMaterialsApi @Composable -fun MicrophoneSettings() { +fun MicrophoneSettings(hazeState: HazeState) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) @@ -287,19 +298,22 @@ fun MicrophoneSettings() { AACPManager.Companion.ControlCommandIdentifiers.MIC_MODE.value, byteArrayOf(byteValue.toByte()) ) - } + }, + hazeState = hazeState ) } } } } +@ExperimentalHazeMaterialsApi @Preview @Composable fun MicrophoneSettingsPreview() { - MicrophoneSettings() + MicrophoneSettings(HazeState()) } +@ExperimentalHazeMaterialsApi @Composable fun DragSelectableDropdown( expanded: Boolean, @@ -311,7 +325,8 @@ fun DragSelectableDropdown( onOptionSelected: (String) -> Unit, externalHoveredIndex: Int? = null, externalDragActive: Boolean = false, - modifier: Modifier = Modifier + hazeState: HazeState, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier ) { if (expanded) { val relativeOffset = touchOffset?.let { it - boxPosition } ?: Offset.Zero @@ -328,9 +343,7 @@ fun DragSelectableDropdown( modifier = modifier .padding(8.dp) .width(300.dp) - .background( - if (isSystemInDarkTheme()) Color(0xFF2C2C2E) else Color(0xFFFFFFFF) - ) + .background(Color.Transparent) .clip(RoundedCornerShape(8.dp)), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { @@ -389,14 +402,13 @@ fun DragSelectableDropdown( } else { index == hoveredIndex } + val isSystemInDarkTheme = isSystemInDarkTheme() Box( modifier = Modifier .fillMaxWidth() .height(itemHeight) .background( - if (isHovered) (if (isSystemInDarkTheme()) Color(0xFF3A3A3C) else Color( - 0xFFD1D1D6 - )) else Color.Transparent + Color.Transparent ) .clickable( interactionSource = remember { MutableInteractionSource() }, @@ -405,6 +417,22 @@ fun DragSelectableDropdown( onOptionSelected(text) onDismissRequest() } + .hazeEffect( + state = hazeState, + style = CupertinoMaterials.regular(), + block = fun HazeEffectScope.() { + alpha = 1f + backgroundColor = if (isSystemInDarkTheme) { + Color(0xB02C2C2E) + } else { + Color(0xB0FFFFFF) + } + tints = if (isHovered) listOf( + HazeTint( + color = if (isSystemInDarkTheme) Color(0x338A8A8A) else Color(0x40D9D9D9) + ) + ) else listOf() + }) .padding(horizontal = 12.dp), contentAlignment = Alignment.CenterStart ) { @@ -415,7 +443,11 @@ fun DragSelectableDropdown( ) { Text( text, - color = if (isSystemInDarkTheme()) Color.White else Color.Black + style = TextStyle( + fontSize = 16.sp, + color = if (isSystemInDarkTheme()) Color.White else Color.Black.copy(alpha = 0.75f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) Checkbox( checked = text == selectedOption, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt new file mode 100644 index 00000000..a86e4010 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -0,0 +1,364 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.composables + +import android.annotation.SuppressLint +import android.content.res.Configuration +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.spring +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableFloatState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.BlurEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.layer.CompositingStrategy +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity +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.compose.ui.util.fastCoerceIn +import androidx.compose.ui.util.fastRoundToInt +import androidx.compose.ui.util.lerp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.refractionWithDispersion +import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.rememberBackdrop +import com.kyant.backdrop.rememberCombinedBackdropDrawer +import com.kyant.backdrop.shadow.Shadow +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.R +import kotlin.math.roundToInt + +@Composable +fun StyledSlider( + label: String? = null, + mutableFloatState: MutableFloatState, + onValueChange: (Float) -> Unit, + valueRange: ClosedFloatingPointRange, + backdrop: Backdrop = rememberBackdrop(), + snapPoints: List = emptyList(), + snapThreshold: Float = 0.05f, + startIcon: String? = null, + endIcon: String? = null, + startLabel: String? = null, + endLabel: String? = null, + independent: Boolean = false, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier +) { + val backgroundColor = if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + val isLightTheme = !isSystemInDarkTheme() + val accentColor = + if (isLightTheme) Color(0xFF0088FF) + else Color(0xFF0091FF) + val trackColor = + if (isLightTheme) Color(0xFF787878).copy(0.2f) + else Color(0xFF787880).copy(0.36f) + val labelTextColor = if (isLightTheme) Color.Black else Color.White + + val fraction by remember { + derivedStateOf { + ((mutableFloatState.floatValue - valueRange.start) / (valueRange.endInclusive - valueRange.start)) + .fastCoerceIn(0f, 1f) + } + } + + val animationScope = rememberCoroutineScope() + val progressAnimationSpec = spring(0.5f, 300f, 0.001f) + val progressAnimation = remember { Animatable(0f) } + + val trackBackdrop = rememberBackdrop() + val innerShadowLayer = + rememberGraphicsLayer().apply { + compositingStrategy = CompositingStrategy.Offscreen + } + + val content = @Composable { + Column( + modifier = modifier.fillMaxWidth(1f).padding(vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (label != null) { + Text( + text = label, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + + if (startLabel != null || endLabel != null) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = startLabel ?: "", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = endLabel ?: "", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(0.dp) + ) { + if (startIcon != null) { + Text( + text = startIcon, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(horizontal = 12.dp) + ) + } + BoxWithConstraints( + Modifier + .weight(1f), + contentAlignment = Alignment.CenterStart + ) { + val density = LocalDensity.current + val trackWidth = constraints.maxWidth + + Box(Modifier.backdrop(trackBackdrop)) { + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(trackColor) + .height(6f.dp) + .fillMaxWidth() + ) + + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(accentColor) + .height(6f.dp) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val fraction = fraction + val width = (fraction * constraints.maxWidth).fastRoundToInt() + layout(width, placeable.height) { + placeable.place(0, 0) + } + } + ) + } + + Box( + Modifier + .graphicsLayer { + val fraction = fraction + translationX = + (-size.width / 2f + fraction * trackWidth) + .fastCoerceIn( + -size.width / 4f, + trackWidth - size.width * 3f / 4f + ) + } + .draggable( + rememberDraggableState { delta -> + val trackWidth = trackWidth - with(density) { 40f.dp.toPx() } + val targetFraction = fraction + delta / trackWidth + val targetValue = + lerp(valueRange.start, valueRange.endInclusive, targetFraction) + .fastCoerceIn(valueRange.start, valueRange.endInclusive) + val snappedValue = if (snapPoints.isNotEmpty()) snapIfClose( + targetValue, + snapPoints, + snapThreshold + ) else targetValue + onValueChange(snappedValue) + }, + Orientation.Horizontal, + startDragImmediately = true, + onDragStarted = { + animationScope.launch { + progressAnimation.animateTo(1f, progressAnimationSpec) + } + }, + onDragStopped = { + animationScope.launch { + progressAnimation.animateTo(0f, progressAnimationSpec) + onValueChange((mutableFloatState.floatValue * 100).roundToInt() / 100f) + } + } + ) + .drawBackdrop( + rememberCombinedBackdropDrawer(backdrop, trackBackdrop), + { RoundedCornerShape(28.dp) }, + highlight = { + val progress = progressAnimation.value + Highlight.AmbientDefault.copy(alpha = progress) + }, + shadow = { + Shadow( + elevation = 4f.dp, + color = Color.Black.copy(0.08f) + ) + }, + layer = { + val progress = progressAnimation.value + val scale = lerp(1f, 1.5f, progress) + scaleX = scale + scaleY = scale + }, + onDrawSurface = { + val progress = progressAnimation.value.fastCoerceIn(0f, 1f) + + val shape = RoundedCornerShape(28.dp) + val outline = shape.createOutline(size, layoutDirection, this) + val innerShadowOffset = 4f.dp.toPx() + val innerShadowBlurRadius = 4f.dp.toPx() + + innerShadowLayer.alpha = progress + innerShadowLayer.renderEffect = + BlurEffect( + innerShadowBlurRadius, + innerShadowBlurRadius, + TileMode.Decal + ) + innerShadowLayer.record { + drawOutline(outline, Color.Black.copy(0.2f)) + translate(0f, innerShadowOffset) { + drawOutline( + outline, + Color.Transparent, + blendMode = BlendMode.Clear + ) + } + } + drawLayer(innerShadowLayer) + + drawRect(Color.White.copy(1f - progress)) + } + ) { + refractionWithDispersion(6f.dp.toPx(), size.height / 2f) + } + .size(40f.dp, 24f.dp) + ) + } + if (endIcon != null) { + Text( + text = endIcon, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(horizontal = 12.dp) + ) + } + } + } + } + + if (independent) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + .heightIn(min = 55.dp), + contentAlignment = Alignment.Center + ) { + content() + } + } else { + content() + } +} + +private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { + val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value + return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value +} + +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun StyledSliderPreview() { + StyledSlider( + mutableFloatState = remember {mutableFloatStateOf(1f)}, + onValueChange = {}, + valueRange = 0f..2f, + independent = true, + startIcon = "A", + endIcon = "B" + ) +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt index ba7a67cf..5b01cc58 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt @@ -1,17 +1,17 @@ /* * LibrePods - AirPods liberated from Apple’s ecosystem - * + * * Copyright (C) 2025 LibrePods contributors - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -85,4 +85,4 @@ fun StyledSwitch( @Composable fun StyledSwitchPreview() { StyledSwitch(checked = true, onCheckedChange = {}) -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt deleted file mode 100644 index 98ef02ad..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ToneVolumeSlider.kt +++ /dev/null @@ -1,190 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -import android.util.Log -import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.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.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 me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi -import kotlin.math.roundToInt - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ToneVolumeSlider() { - val service = ServiceManager.getService()!! - val sliderValueFromAACP = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - val sliderValue = remember { mutableFloatStateOf( - sliderValueFromAACP?.toFloat() ?: -1f - ) } - val listener = object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0)?.toFloat() - if (newValue != null) { - sliderValue.floatValue = newValue - } - } - } - } - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME, listener) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME, listener) - } - } - Log.d("ToneVolumeSlider", "Slider value: ${sliderValue.floatValue}") - - val isDarkTheme = isSystemInDarkTheme() - - 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(0.95f), - 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 = snapIfClose(it, listOf(100f)) - }, - valueRange = 0f..125f, - onValueChangeFinished = { - sliderValue.floatValue = snapIfClose(sliderValue.floatValue.roundToInt().toFloat(), listOf(100f)) - service.aacpManager.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME.value, - value = byteArrayOf(sliderValue.floatValue.toInt().toByte(), - 0x50.toByte() - ) - ) - }, - 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 / 125) - .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) - ) - } -} - -@Preview -@Composable -fun ToneVolumeSliderPreview() { - ToneVolumeSlider() -} - -private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { - val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value - return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value -} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 56021a59..b6fc44f1 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -19,6 +19,7 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint +import android.bluetooth.BluetoothDevice import android.util.Log import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween @@ -31,15 +32,13 @@ 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.fillMaxHeight 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.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.CenterAlignedTopAppBar @@ -62,27 +61,27 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf 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.drawBehind -import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.PointerEventPass -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 dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeEffect @@ -95,50 +94,43 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch +import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.SinglePodANCSwitch import me.kavishdevar.librepods.composables.StyledSwitch -import me.kavishdevar.librepods.composables.ToneVolumeSlider import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTManager -import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.RadareOffsetFinder import me.kavishdevar.librepods.utils.TransparencySettings import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse import me.kavishdevar.librepods.utils.sendTransparencySettings import java.io.IOException -import java.nio.ByteBuffer -import java.nio.ByteOrder import kotlin.io.encoding.ExperimentalEncodingApi -private var debounceJob: Job? = null private var phoneMediaDebounceJob: Job? = null +private var toneVolumeDebounceJob: Job? = null private const val TAG = "AccessibilitySettings" @SuppressLint("DefaultLocale") @ExperimentalHazeMaterialsApi @OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) @Composable -fun AccessibilitySettingsScreen() { +fun AccessibilitySettingsScreen(navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black - val verticalScrollState = rememberScrollState() + val verticalScrollState = rememberScrollState() val hazeState = remember { HazeState() } val snackbarHostState = remember { SnackbarHostState() } - val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") - // get the AACP manager if available (used for EQ read/write) val aacpManager = remember { ServiceManager.getService()?.aacpManager } - val context = LocalContext.current - val radareOffsetFinder = remember { RadareOffsetFinder(context) } - val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } + val isSdpOffsetAvailable = + remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } 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 Scaffold( containerColor = if (isSystemInDarkTheme()) Color( @@ -216,7 +208,7 @@ fun AccessibilitySettingsScreen() { val initialLoadComplete = remember { mutableStateOf(false) } val initialReadSucceeded = remember { mutableStateOf(false) } - val initialReadAttempts = remember { mutableStateOf(0) } + val initialReadAttempts = remember { mutableIntStateOf(0) } val transparencySettings = remember { mutableStateOf( @@ -247,7 +239,8 @@ fun AccessibilitySettingsScreen() { amplificationSliderValue.floatValue = parsed.netAmplification balanceSliderValue.floatValue = parsed.balance toneSliderValue.floatValue = parsed.leftTone - ambientNoiseReductionSliderValue.floatValue = parsed.leftAmbientNoiseReduction + ambientNoiseReductionSliderValue.floatValue = + parsed.leftAmbientNoiseReduction conversationBoostEnabled.value = parsed.leftConversationBoost eq.value = parsed.leftEQ.copyOf() Log.d(TAG, "Updated transparency settings from notification") @@ -263,22 +256,34 @@ fun AccessibilitySettingsScreen() { 1.toByte() to "Slower", 2.toByte() to "Slowest" ) - val selectedPressSpeedValue = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var selectedPressSpeed by remember { mutableStateOf(pressSpeedOptions[selectedPressSpeedValue] ?: pressSpeedOptions[0]) } + val selectedPressSpeedValue = + aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL }?.value?.takeIf { it.isNotEmpty() } + ?.get(0) + var selectedPressSpeed by remember { + mutableStateOf( + pressSpeedOptions[selectedPressSpeedValue] ?: pressSpeedOptions[0] + ) + } val selectedPressSpeedListener = object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - selectedPressSpeed = pressSpeedOptions[newValue] ?: pressSpeedOptions[0] - } + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + selectedPressSpeed = pressSpeedOptions[newValue] ?: pressSpeedOptions[0] } } + } LaunchedEffect(Unit) { - aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL, selectedPressSpeedListener) + aacpManager?.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL, + selectedPressSpeedListener + ) } DisposableEffect(Unit) { onDispose { - aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL, selectedPressSpeedListener) + aacpManager?.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL, + selectedPressSpeedListener + ) } } @@ -287,22 +292,36 @@ fun AccessibilitySettingsScreen() { 1.toByte() to "Slower", 2.toByte() to "Slowest" ) - val selectedPressAndHoldDurationValue = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var selectedPressAndHoldDuration by remember { mutableStateOf(pressAndHoldDurationOptions[selectedPressAndHoldDurationValue] ?: pressAndHoldDurationOptions[0]) } + val selectedPressAndHoldDurationValue = + aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL }?.value?.takeIf { it.isNotEmpty() } + ?.get(0) + var selectedPressAndHoldDuration by remember { + mutableStateOf( + pressAndHoldDurationOptions[selectedPressAndHoldDurationValue] + ?: pressAndHoldDurationOptions[0] + ) + } val selectedPressAndHoldDurationListener = object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - selectedPressAndHoldDuration = pressAndHoldDurationOptions[newValue] ?: pressAndHoldDurationOptions[0] - } + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value) { + val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) + selectedPressAndHoldDuration = + pressAndHoldDurationOptions[newValue] ?: pressAndHoldDurationOptions[0] } } + } LaunchedEffect(Unit) { - aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL, selectedPressAndHoldDurationListener) + aacpManager?.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL, + selectedPressAndHoldDurationListener + ) } DisposableEffect(Unit) { onDispose { - aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL, selectedPressAndHoldDurationListener) + aacpManager?.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL, + selectedPressAndHoldDurationListener + ) } } @@ -311,123 +330,36 @@ fun AccessibilitySettingsScreen() { 2.toByte() to "Longer", 3.toByte() to "Longest" ) - val selectedVolumeSwipeSpeedValue = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var selectedVolumeSwipeSpeed by remember { mutableStateOf(volumeSwipeSpeedOptions[selectedVolumeSwipeSpeedValue] ?: volumeSwipeSpeedOptions[1]) } + val selectedVolumeSwipeSpeedValue = + aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL }?.value?.takeIf { it.isNotEmpty() } + ?.get(0) + var selectedVolumeSwipeSpeed by remember { + mutableStateOf( + volumeSwipeSpeedOptions[selectedVolumeSwipeSpeedValue] + ?: volumeSwipeSpeedOptions[1] + ) + } val selectedVolumeSwipeSpeedListener = object : AACPManager.ControlCommandListener { override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value) { val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - selectedVolumeSwipeSpeed = volumeSwipeSpeedOptions[newValue] ?: volumeSwipeSpeedOptions[1] + selectedVolumeSwipeSpeed = + volumeSwipeSpeedOptions[newValue] ?: volumeSwipeSpeedOptions[1] } } } LaunchedEffect(Unit) { - aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL, selectedVolumeSwipeSpeedListener) - } - DisposableEffect(Unit) { - onDispose { - aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL, selectedVolumeSwipeSpeedListener) - } - } - - LaunchedEffect(enabled.value, amplificationSliderValue.floatValue, balanceSliderValue.floatValue, toneSliderValue.floatValue, conversationBoostEnabled.value, ambientNoiseReductionSliderValue.floatValue, eq.value, initialLoadComplete.value, initialReadSucceeded.value) { - if (!initialLoadComplete.value) { - Log.d(TAG, "Initial device load not complete - skipping send") - return@LaunchedEffect - } - - if (!initialReadSucceeded.value) { - Log.d(TAG, "Initial device read not successful yet - skipping send until read succeeds") - return@LaunchedEffect - } - - transparencySettings.value = TransparencySettings( - enabled = enabled.value, - leftEQ = eq.value, - rightEQ = eq.value, - leftAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue < 0) -balanceSliderValue.floatValue else 0f, - rightAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue > 0) balanceSliderValue.floatValue else 0f, - leftTone = toneSliderValue.floatValue, - rightTone = toneSliderValue.floatValue, - leftConversationBoost = conversationBoostEnabled.value, - rightConversationBoost = conversationBoostEnabled.value, - leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, - rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, - netAmplification = amplificationSliderValue.floatValue, - balance = balanceSliderValue.floatValue + aacpManager?.registerControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL, + selectedVolumeSwipeSpeedListener ) - Log.d("TransparencySettings", "Updated settings: ${transparencySettings.value}") - sendTransparencySettings(attManager, transparencySettings.value) } - DisposableEffect(Unit) { onDispose { - attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener) - } - } - - LaunchedEffect(Unit) { - Log.d(TAG, "Connecting to ATT...") - try { - attManager.enableNotifications(ATTHandles.TRANSPARENCY) - attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener) - - // If we have an AACP manager, prefer its EQ data to populate EQ controls first - try { - if (aacpManager != null) { - Log.d(TAG, "Found AACPManager, reading cached EQ data") - val aacpEQ = aacpManager.eqData - if (aacpEQ.isNotEmpty()) { - eq.value = aacpEQ.copyOf() - phoneMediaEQ.value = aacpEQ.copyOf() - phoneEQEnabled.value = aacpManager.eqOnPhone - mediaEQEnabled.value = aacpManager.eqOnMedia - Log.d(TAG, "Populated EQ from AACPManager: ${aacpEQ.toList()}") - } else { - Log.d(TAG, "AACPManager EQ data empty") - } - } else { - Log.d(TAG, "No AACPManager available") - } - } catch (e: Exception) { - Log.w(TAG, "Error reading EQ from AACPManager: ${e.message}") - } - - var parsedSettings: TransparencySettings? = null - for (attempt in 1..3) { - initialReadAttempts.value = attempt - try { - val data = attManager.read(ATTHandles.TRANSPARENCY) - parsedSettings = parseTransparencySettingsResponse(data = data) - if (parsedSettings != null) { - Log.d(TAG, "Parsed settings on attempt $attempt") - break - } else { - Log.d(TAG, "Parsing returned null on attempt $attempt") - } - } catch (e: Exception) { - Log.w(TAG, "Read attempt $attempt failed: ${e.message}") - } - delay(200) - } - - if (parsedSettings != null) { - Log.d(TAG, "Initial transparency settings: $parsedSettings") - enabled.value = parsedSettings.enabled - amplificationSliderValue.floatValue = parsedSettings.netAmplification - balanceSliderValue.floatValue = parsedSettings.balance - toneSliderValue.floatValue = parsedSettings.leftTone - ambientNoiseReductionSliderValue.floatValue = parsedSettings.leftAmbientNoiseReduction - conversationBoostEnabled.value = parsedSettings.leftConversationBoost - eq.value = parsedSettings.leftEQ.copyOf() - initialReadSucceeded.value = true - } else { - Log.d(TAG, "Failed to read/parse initial transparency settings after ${initialReadAttempts.value} attempts") - } - } catch (e: IOException) { - e.printStackTrace() - } finally { - initialLoadComplete.value = true + aacpManager?.unregisterControlCommandListener( + AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL, + selectedVolumeSwipeSpeedListener + ) } } @@ -444,251 +376,63 @@ fun AccessibilitySettingsScreen() { try { val phoneByte = if (phoneEQEnabled.value) 0x01.toByte() else 0x02.toByte() val mediaByte = if (mediaEQEnabled.value) 0x01.toByte() else 0x02.toByte() - Log.d(TAG, "Sending phone/media EQ (phoneEnabled=${phoneEQEnabled.value}, mediaEnabled=${mediaEQEnabled.value})") + Log.d( + TAG, + "Sending phone/media EQ (phoneEnabled=${phoneEQEnabled.value}, mediaEnabled=${mediaEQEnabled.value})" + ) manager.sendPhoneMediaEQ(phoneMediaEQ.value, phoneByte, mediaByte) } catch (e: Exception) { Log.w(TAG, "Error sending phone/media EQ: ${e.message}") } } } - - // Only show transparency mode section if SDP offset is available - if (isSdpOffsetAvailable.value) { - AccessibilityToggle( - text = stringResource(R.string.transparency_mode), - mutableState = enabled, - independent = true, - description = stringResource(R.string.customize_transparency_mode_description) - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.amplification).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - .height(55.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxSize() - ) { - Text( - text = "􀊥", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(start = 4.dp) - ) - AccessibilitySlider( - valueRange = -1f..1f, - value = amplificationSliderValue.floatValue, - onValueChange = { - amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) - }, - widthFrac = 0.90f, - ) - Text( - text = "􀊩", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(end = 4.dp) - ) - } - } - - Text( - text = stringResource(R.string.balance).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.left), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = stringResource(R.string.right), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } - AccessibilitySlider( - valueRange = -1f..1f, - value = balanceSliderValue.floatValue, - onValueChange = { - balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) - } - } - - Text( - text = stringResource(R.string.tone).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.darker), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = stringResource(R.string.brighter), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } - AccessibilitySlider( - valueRange = -1f..1f, - value = toneSliderValue.floatValue, - onValueChange = { - toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) + val toneVolumeValue = remember { mutableFloatStateOf( + aacpManager?.controlCommandStatusList?.find { + it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME + }?.value?.takeIf { it.isNotEmpty() }?.get(0)?.toFloat() ?: 75f + ) } + LaunchedEffect(toneVolumeValue.floatValue) { + toneVolumeDebounceJob?.cancel() + toneVolumeDebounceJob = CoroutineScope(Dispatchers.IO).launch { + delay(150) + val manager = ServiceManager.getService()?.aacpManager + if (manager == null) { + Log.w(TAG, "Cannot write tone volume: AACPManager not available") + return@launch } - } - - Text( - text = stringResource(R.string.ambient_noise_reduction).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.less), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = stringResource(R.string.more), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } - AccessibilitySlider( - valueRange = 0f..1f, - value = ambientNoiseReductionSliderValue.floatValue, - onValueChange = { - ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) - }, + try { + manager.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.CHIME_VOLUME.value, + value = byteArrayOf(toneVolumeValue.floatValue.toInt().toByte(), 0x50.toByte()) ) + } catch (e: Exception) { + Log.w(TAG, "Error sending tone volume: ${e.message}") } } - - AccessibilityToggle( - text = stringResource(R.string.conversation_boost), - mutableState = conversationBoostEnabled, - independent = true, - description = stringResource(R.string.conversation_boost_description) - ) } Text( - text = stringResource(R.string.audio).uppercase(), + text = stringResource(R.string.tone_volume).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(8.dp, bottom = 0.dp) ) + StyledSlider( + mutableFloatState = toneVolumeValue, + onValueChange = { + toneVolumeValue.floatValue = it + }, + valueRange = 0f..125f, + snapPoints = listOf(100f), + startIcon = "\uDBC0\uDEA1", + endIcon = "\uDBC0\uDEA9", + independent = true + ) + Column( modifier = Modifier .fillMaxWidth() @@ -697,164 +441,73 @@ fun AccessibilitySettingsScreen() { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceBetween ) { - Text( - text = stringResource(R.string.tone_volume), - style = TextStyle( - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Light, - color = textColor - ), - modifier = Modifier - .padding(horizontal = 8.dp, vertical = 4.dp) - .fillMaxWidth() - ) - ToneVolumeSlider() SinglePodANCSwitch() VolumeControlSwitch() LoudSoundReductionSwitch() DropdownMenuComponent( label = stringResource(R.string.press_speed), - options = listOf(stringResource(R.string.default_option), stringResource(R.string.slower), stringResource(R.string.slowest)), + options = listOf( + stringResource(R.string.default_option), + stringResource(R.string.slower), + stringResource(R.string.slowest) + ), selectedOption = selectedPressSpeed.toString(), onOptionSelected = { newValue -> selectedPressSpeed = newValue aacpManager?.sendControlCommand( identifier = AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value, - value = pressSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 0.toByte() + value = pressSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() + ?: 0.toByte() ) }, textColor = textColor ) DropdownMenuComponent( label = stringResource(R.string.press_and_hold_duration), - options = listOf(stringResource(R.string.default_option), stringResource(R.string.slower), stringResource(R.string.slowest)), + options = listOf( + stringResource(R.string.default_option), + stringResource(R.string.slower), + stringResource(R.string.slowest) + ), selectedOption = selectedPressAndHoldDuration.toString(), onOptionSelected = { newValue -> selectedPressAndHoldDuration = newValue aacpManager?.sendControlCommand( identifier = AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value, - value = pressAndHoldDurationOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 0.toByte() + value = pressAndHoldDurationOptions.filterValues { it == newValue }.keys.firstOrNull() + ?: 0.toByte() ) }, textColor = textColor ) DropdownMenuComponent( label = stringResource(R.string.volume_swipe_speed), - options = listOf(stringResource(R.string.default_option), stringResource(R.string.longer), stringResource(R.string.longest)), + options = listOf( + stringResource(R.string.default_option), + stringResource(R.string.longer), + stringResource(R.string.longest) + ), selectedOption = selectedVolumeSwipeSpeed.toString(), onOptionSelected = { newValue -> selectedVolumeSwipeSpeed = newValue aacpManager?.sendControlCommand( identifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value, - value = volumeSwipeSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() ?: 1.toByte() + value = volumeSwipeSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() + ?: 1.toByte() ) }, textColor = textColor ) } - Spacer(modifier = Modifier.height(2.dp)) - - // Only show transparency mode EQ section if SDP offset is available - if (isSdpOffsetAvailable.value) { - Text( - text = stringResource(R.string.equalizer).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) - ) - - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween - ) { - for (i in 0 until 8) { - val eqValue = remember(eq.value[i]) { mutableFloatStateOf(eq.value[i]) } - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(38.dp) - ) { - Text( - text = String.format("%.2f", eqValue.floatValue), - fontSize = 12.sp, - color = textColor, - modifier = Modifier.padding(bottom = 4.dp) - ) - - Slider( - value = eqValue.floatValue, - onValueChange = { newVal -> - eqValue.floatValue = newVal - val newEQ = eq.value.copyOf() - newEQ[i] = eqValue.floatValue - eq.value = newEQ - }, - valueRange = 0f..100f, - modifier = Modifier - .fillMaxWidth(0.9f) - .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(eqValue.floatValue / 100f) - .height(4.dp) - .background(activeTrackColor, RoundedCornerShape(4.dp)) - ) - } - } - ) - - Text( - text = stringResource(R.string.band_label, i + 1), - fontSize = 12.sp, - color = textColor, - modifier = Modifier.padding(top = 4.dp) - ) - } - } - } - Spacer(modifier = Modifier.height(16.dp)) - } + NavigationButton( + to = "transparency_customization", + name = stringResource(R.string.customize_transparency_mode), + navController = navController + ) + Spacer(modifier = Modifier.height(2.dp)) Text( text = stringResource(R.string.apply_eq_to).uppercase(), style = TextStyle( @@ -874,8 +527,17 @@ fun AccessibilitySettingsScreen() { val darkModeLocal = isSystemInDarkTheme() val phoneShape = RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - var phoneBackgroundColor by remember { mutableStateOf(if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val phoneAnimatedBackgroundColor by animateColorAsState(targetValue = phoneBackgroundColor, animationSpec = tween(durationMillis = 500)) + var phoneBackgroundColor by remember { + mutableStateOf( + if (darkModeLocal) Color( + 0xFF1C1C1E + ) else Color(0xFFFFFFFF) + ) + } + val phoneAnimatedBackgroundColor by animateColorAsState( + targetValue = phoneBackgroundColor, + animationSpec = tween(durationMillis = 500) + ) Row( modifier = Modifier @@ -885,9 +547,11 @@ fun AccessibilitySettingsScreen() { .pointerInput(Unit) { detectTapGestures( onPress = { - phoneBackgroundColor = if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) + phoneBackgroundColor = + if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) tryAwaitRelease() - phoneBackgroundColor = if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + phoneBackgroundColor = + if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) phoneEQEnabled.value = !phoneEQEnabled.value } ) @@ -925,8 +589,17 @@ fun AccessibilitySettingsScreen() { ) val mediaShape = RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) - var mediaBackgroundColor by remember { mutableStateOf(if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val mediaAnimatedBackgroundColor by animateColorAsState(targetValue = mediaBackgroundColor, animationSpec = tween(durationMillis = 500)) + var mediaBackgroundColor by remember { + mutableStateOf( + if (darkModeLocal) Color( + 0xFF1C1C1E + ) else Color(0xFFFFFFFF) + ) + } + val mediaAnimatedBackgroundColor by animateColorAsState( + targetValue = mediaBackgroundColor, + animationSpec = tween(durationMillis = 500) + ) Row( modifier = Modifier @@ -936,9 +609,11 @@ fun AccessibilitySettingsScreen() { .pointerInput(Unit) { detectTapGestures( onPress = { - mediaBackgroundColor = if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) + mediaBackgroundColor = + if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) tryAwaitRelease() - mediaBackgroundColor = if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + mediaBackgroundColor = + if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) mediaEQEnabled.value = !mediaEQEnabled.value } ) @@ -979,7 +654,8 @@ fun AccessibilitySettingsScreen() { horizontalAlignment = Alignment.CenterHorizontally ) { for (i in 0 until 8) { - val eqPhoneValue = remember(phoneMediaEQ.value[i]) { mutableFloatStateOf(phoneMediaEQ.value[i]) } + val eqPhoneValue = + remember(phoneMediaEQ.value[i]) { mutableFloatStateOf(phoneMediaEQ.value[i]) } Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, @@ -1020,7 +696,7 @@ fun AccessibilitySettingsScreen() { ) }, track = { - Box ( + Box( modifier = Modifier .fillMaxWidth() .height(12.dp), @@ -1058,10 +734,25 @@ fun AccessibilitySettingsScreen() { @Composable -fun AccessibilityToggle(text: String, mutableState: MutableState, independent: Boolean = false, description: String? = null, title: String? = null) { +fun AccessibilityToggle( + text: String, + mutableState: MutableState, + independent: Boolean = false, + description: String? = null, + title: String? = null +) { val isDarkTheme = isSystemInDarkTheme() - var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) + var backgroundColor by remember { + mutableStateOf( + if (isDarkTheme) Color(0xFF1C1C1E) else Color( + 0xFFFFFFFF + ) + ) + } + val animatedBackgroundColor by animateColorAsState( + targetValue = backgroundColor, + animationSpec = tween(durationMillis = 500) + ) val textColor = if (isDarkTheme) Color.White else Color.Black val cornerShape = if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) @@ -1082,15 +773,17 @@ fun AccessibilityToggle(text: String, mutableState: MutableState, indep ) Spacer(modifier = Modifier.height(4.dp)) } - Box ( + Box( modifier = Modifier .background(animatedBackgroundColor, cornerShape) .pointerInput(Unit) { detectTapGestures( onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + backgroundColor = + if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + backgroundColor = + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { mutableState.value = !mutableState.value @@ -1139,11 +832,6 @@ fun AccessibilityToggle(text: String, mutableState: MutableState, indep } } -private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { - val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value - return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value -} - @Composable private fun DropdownMenuComponent( label: String, @@ -1154,7 +842,7 @@ private fun DropdownMenuComponent( ) { var expanded by remember { mutableStateOf(false) } - Column ( + Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 12.dp) @@ -1195,23 +883,4 @@ private fun DropdownMenuComponent( } } } -} - -// Debounced send helper for phone/media EQ (if needed elsewhere) -private fun sendPhoneMediaEQ(aacpManager: me.kavishdevar.librepods.utils.AACPManager?, eq: FloatArray, phoneEnabled: Boolean, mediaEnabled: Boolean) { - phoneMediaDebounceJob?.cancel() - phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { - delay(100) - try { - if (aacpManager == null) { - Log.w(TAG, "AACPManger is null; cannot send phone/media EQ") - return@launch - } - val phoneByte = if (phoneEnabled) 0x01.toByte() else 0x02.toByte() - val mediaByte = if (mediaEnabled) 0x01.toByte() else 0x02.toByte() - aacpManager.sendPhoneMediaEQ(eq, phoneByte, mediaByte) - } catch (e: Exception) { - Log.w(TAG, "Error in sendPhoneMediaEQ: ${e.message}") - } - } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 8a1419d0..56ec24fe 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -84,14 +84,13 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.chrisbanes.haze.rememberHazeState import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.CustomDevice import me.kavishdevar.librepods.composables.AudioSettings import me.kavishdevar.librepods.composables.BatteryView import me.kavishdevar.librepods.composables.CallControlSettings @@ -146,7 +145,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } val verticalScrollState = rememberScrollState() - val hazeState = remember { HazeState() } + val hazeState = rememberHazeState( blurEnabled = true ) val snackbarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() @@ -250,7 +249,8 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, block = fun HazeEffectScope.() { alpha = if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f - }) + } + ) .drawBehind { mDensity.floatValue = density val strokeWidth = 0.7.dp.value * density @@ -364,7 +364,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, NoiseControlSettings(service = service) Spacer(modifier = Modifier.height(16.dp)) - CallControlSettings() + CallControlSettings(hazeState = hazeState) // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events @@ -378,7 +378,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, ConnectionSettings() Spacer(modifier = Modifier.height(16.dp)) - MicrophoneSettings() + MicrophoneSettings(hazeState) Spacer(modifier = Modifier.height(16.dp)) IndependentToggle( @@ -388,15 +388,12 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, default = false, controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG ) - + Spacer(modifier = Modifier.height(16.dp)) NavigationButton(to = "head_tracking", stringResource(R.string.head_gestures), navController) Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "", "Accessibility", navController = navController, onClick = { - val intent = Intent(context, CustomDevice::class.java) - context.startActivity(intent) - }) + NavigationButton(to = "accessibility", "Accessibility", navController = navController) Spacer(modifier = Modifier.height(16.dp)) IndependentToggle( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 25b2ebaa..695965f6 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -21,37 +21,16 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint import android.content.Context import android.util.Log -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.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.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -59,22 +38,14 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf 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.drawBehind -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.draw.scale -import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -96,17 +67,12 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.AccessibilitySlider +import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.composables.IndependentToggle -import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch -import me.kavishdevar.librepods.composables.SinglePodANCSwitch -import me.kavishdevar.librepods.composables.StyledSwitch -import me.kavishdevar.librepods.composables.ToneVolumeSlider -import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTManager -import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.ATTHandles +import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.RadareOffsetFinder import java.io.IOException import java.nio.ByteBuffer @@ -114,14 +80,13 @@ import java.nio.ByteOrder import kotlin.io.encoding.ExperimentalEncodingApi private var debounceJob: Job? = null -private var phoneMediaDebounceJob: Job? = null private const val TAG = "HearingAidAdjustments" @SuppressLint("DefaultLocale") @ExperimentalHazeMaterialsApi @OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) @Composable -fun HearingAidAdjustmentsScreen(navController: NavController) { +fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val verticalScrollState = rememberScrollState() @@ -131,14 +96,14 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { val aacpManager = remember { ServiceManager.getService()?.aacpManager } val context = LocalContext.current - val radareOffsetFinder = remember { RadareOffsetFinder(context) } - val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } + remember { RadareOffsetFinder(context) } + remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } val service = ServiceManager.getService() - 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 + if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491) + if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) + if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) + if (isDarkTheme) Color.White else Color.Black Scaffold( containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7), @@ -192,9 +157,9 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { .verticalScroll(verticalScrollState), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - val enabled = remember { mutableStateOf(false) } + remember { mutableStateOf(false) } val amplificationSliderValue = remember { mutableFloatStateOf(0.5f) } val balanceSliderValue = remember { mutableFloatStateOf(0.5f) } val toneSliderValue = remember { mutableFloatStateOf(0.5f) } @@ -210,9 +175,9 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { val initialLoadComplete = remember { mutableStateOf(false) } val initialReadSucceeded = remember { mutableStateOf(false) } - val initialReadAttempts = remember { mutableStateOf(0) } + val initialReadAttempts = remember { mutableIntStateOf(0) } - val HearingAidSettings = remember { + val hearingAidSettings = remember { mutableStateOf( HearingAidSettings( leftEQ = eq.value, @@ -295,7 +260,7 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { return@LaunchedEffect } - HearingAidSettings.value = HearingAidSettings( + hearingAidSettings.value = HearingAidSettings( leftEQ = eq.value, rightEQ = eq.value, leftAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue < 0) -balanceSliderValue.floatValue else 0f, @@ -310,8 +275,8 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { balance = balanceSliderValue.floatValue, ownVoiceAmplification = ownVoiceAmplification.floatValue ) - Log.d(TAG, "Updated settings: ${HearingAidSettings.value}") - sendHearingAidSettings(attManager, HearingAidSettings.value) + Log.d(TAG, "Updated settings: ${hearingAidSettings.value}") + sendHearingAidSettings(attManager, hearingAidSettings.value) } LaunchedEffect(Unit) { @@ -342,7 +307,7 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { var parsedSettings: HearingAidSettings? = null for (attempt in 1..3) { - initialReadAttempts.value = attempt + initialReadAttempts.intValue = attempt try { val data = attManager.read(ATTHandles.HEARING_AID) parsedSettings = parseHearingAidSettingsResponse(data = data) @@ -369,7 +334,7 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { ownVoiceAmplification.floatValue = parsedSettings.ownVoiceAmplification initialReadSucceeded.value = true } else { - Log.d(TAG, "Failed to read/parse initial hearing aid settings after ${initialReadAttempts.value} attempts") + Log.d(TAG, "Failed to read/parse initial hearing aid settings after ${initialReadAttempts.intValue} attempts") } } catch (e: IOException) { e.printStackTrace() @@ -378,10 +343,6 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { } } - val isDarkThemeLocal = isSystemInDarkTheme() - var backgroundColorHA by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColorHA by animateColorAsState(targetValue = backgroundColorHA, animationSpec = tween(durationMillis = 500)) - Text( text = stringResource(R.string.amplification).uppercase(), style = TextStyle( @@ -392,48 +353,16 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { ), modifier = Modifier.padding(8.dp, bottom = 0.dp) ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - .height(55.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxSize() - ) { - Text( - text = "􀊥", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(start = 4.dp) - ) - AccessibilitySlider( - valueRange = -1f..1f, - value = amplificationSliderValue.floatValue, - onValueChange = { - amplificationSliderValue.floatValue = snapIfClose(it, listOf(-0.5f, -0.25f, 0f, 0.25f, 0.5f)) - }, - widthFrac = 0.90f - ) - Text( - text = "􀊩", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(end = 4.dp) - ) - } - } + StyledSlider( + valueRange = -1f..1f, + mutableFloatState = amplificationSliderValue, + onValueChange = { + amplificationSliderValue.floatValue = it + }, + startIcon = "􀊥", + endIcon = "􀊩", + independent = true, + ) val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) @@ -455,47 +384,17 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { ), modifier = Modifier.padding(8.dp, bottom = 0.dp) ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.left), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = stringResource(R.string.right), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } - AccessibilitySlider( - valueRange = -1f..1f, - value = balanceSliderValue.floatValue, - onValueChange = { - balanceSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) - } - } + StyledSlider( + valueRange = -1f..1f, + mutableFloatState = balanceSliderValue, + onValueChange = { + balanceSliderValue.floatValue = it + }, + snapPoints = listOf(0f), + startLabel = stringResource(R.string.left), + endLabel = stringResource(R.string.right), + independent = true, + ) Text( text = stringResource(R.string.tone).uppercase(), @@ -507,47 +406,16 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { ), modifier = Modifier.padding(8.dp, bottom = 0.dp) ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.darker), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = stringResource(R.string.brighter), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } - AccessibilitySlider( - valueRange = -1f..1f, - value = toneSliderValue.floatValue, - onValueChange = { - toneSliderValue.floatValue = snapIfClose(it, listOf(0f)) - }, - ) - } - } + StyledSlider( + valueRange = -1f..1f, + mutableFloatState = toneSliderValue, + onValueChange = { + toneSliderValue.floatValue = it + }, + startLabel = stringResource(R.string.darker), + endLabel = stringResource(R.string.brighter), + independent = true, + ) Text( text = stringResource(R.string.ambient_noise_reduction).uppercase(), @@ -560,47 +428,16 @@ fun HearingAidAdjustmentsScreen(navController: NavController) { modifier = Modifier.padding(8.dp, bottom = 0.dp) ) - Box( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.less), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = stringResource(R.string.more), - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } - AccessibilitySlider( - valueRange = 0f..1f, - value = ambientNoiseReductionSliderValue.floatValue, - onValueChange = { - ambientNoiseReductionSliderValue.floatValue = snapIfClose(it, listOf(0.1f, 0.3f, 0.5f, 0.7f, 0.9f)) - }, - ) - } - } + StyledSlider( + valueRange = 0f..1f, + mutableFloatState = ambientNoiseReductionSliderValue, + onValueChange = { + ambientNoiseReductionSliderValue.floatValue = it + }, + startLabel = stringResource(R.string.less), + endLabel = stringResource(R.string.more), + independent = true, + ) AccessibilityToggle( text = stringResource(R.string.conversation_boost), @@ -668,8 +505,6 @@ private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings if (data.size < 104) return null val buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN) - val phoneEnabled = buffer.get() == 0x01.toByte() - val mediaEnabled = buffer.get() == 0x01.toByte() buffer.getShort() // skip 0x60 0x00 val leftEQ = FloatArray(8) @@ -718,7 +553,7 @@ private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings private fun sendHearingAidSettings( attManager: ATTManager, - HearingAidSettings: HearingAidSettings + hearingAidSettings: HearingAidSettings ) { debounceJob?.cancel() debounceJob = CoroutineScope(Dispatchers.IO).launch { @@ -736,19 +571,19 @@ private fun sendHearingAidSettings( buffer.put(2, 0x64) // Left ear adjustments - buffer.putFloat(36, HearingAidSettings.leftAmplification) - buffer.putFloat(40, HearingAidSettings.leftTone) - buffer.putFloat(44, if (HearingAidSettings.leftConversationBoost) 1.0f else 0.0f) - buffer.putFloat(48, HearingAidSettings.leftAmbientNoiseReduction) + buffer.putFloat(36, hearingAidSettings.leftAmplification) + buffer.putFloat(40, hearingAidSettings.leftTone) + buffer.putFloat(44, if (hearingAidSettings.leftConversationBoost) 1.0f else 0.0f) + buffer.putFloat(48, hearingAidSettings.leftAmbientNoiseReduction) // Right ear adjustments - buffer.putFloat(84, HearingAidSettings.rightAmplification) - buffer.putFloat(88, HearingAidSettings.rightTone) - buffer.putFloat(92, if (HearingAidSettings.rightConversationBoost) 1.0f else 0.0f) - buffer.putFloat(96, HearingAidSettings.rightAmbientNoiseReduction) + buffer.putFloat(84, hearingAidSettings.rightAmplification) + buffer.putFloat(88, hearingAidSettings.rightTone) + buffer.putFloat(92, if (hearingAidSettings.rightConversationBoost) 1.0f else 0.0f) + buffer.putFloat(96, hearingAidSettings.rightAmbientNoiseReduction) // Own voice amplification - buffer.putFloat(100, HearingAidSettings.ownVoiceAmplification) + buffer.putFloat(100, hearingAidSettings.ownVoiceAmplification) Log.d(TAG, "Sending updated settings: ${currentData.joinToString(" ") { String.format("%02X", it) }}") @@ -758,8 +593,3 @@ private fun sendHearingAidSettings( } } } - -private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { - val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value - return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index bdd1cc59..27752ece 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -19,7 +19,6 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint -import android.content.Context import android.util.Log import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween @@ -28,43 +27,30 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.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.layout.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf @@ -72,16 +58,10 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.draw.scale -import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font @@ -99,27 +79,15 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.AccessibilitySlider import me.kavishdevar.librepods.composables.ConfirmationDialog -import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch -import me.kavishdevar.librepods.composables.SinglePodANCSwitch import me.kavishdevar.librepods.composables.StyledSwitch -import me.kavishdevar.librepods.composables.ToneVolumeSlider -import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTManager -import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.AACPManager -import me.kavishdevar.librepods.utils.RadareOffsetFinder -import me.kavishdevar.librepods.utils.TransparencySettings +import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse import me.kavishdevar.librepods.utils.sendTransparencySettings -import java.io.IOException -import java.nio.ByteBuffer -import java.nio.ByteOrder import kotlin.io.encoding.ExperimentalEncodingApi private var debounceJob: Job? = null @@ -139,15 +107,6 @@ fun HearingAidScreen(navController: NavController) { val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") val aacpManager = remember { ServiceManager.getService()?.aacpManager } - val context = LocalContext.current - val radareOffsetFinder = remember { RadareOffsetFinder(context) } - val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } - val service = ServiceManager.getService() - - 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 val showDialog = remember { mutableStateOf(false) } @@ -385,7 +344,11 @@ fun HearingAidScreen(navController: NavController) { onAdjustMediaChange(!adjustMediaEnabled.value) } ) - }, + } + .background( + animatedBackgroundColorAdjustMedia, + RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) + ), verticalAlignment = Alignment.CenterVertically ) { Text( @@ -430,7 +393,11 @@ fun HearingAidScreen(navController: NavController) { onAdjustPhoneChange(!adjustPhoneEnabled.value) } ) - }, + } + .background( + animatedBackgroundColorAdjustPhone, + RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + ), verticalAlignment = Alignment.CenterVertically ) { Text( @@ -466,11 +433,10 @@ fun HearingAidScreen(navController: NavController) { if (!enrolled) { aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) } else { - aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) + aacpManager.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x01)) } aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x01.toByte()) hearingAidEnabled.value = true - // Disable transparency mode CoroutineScope(Dispatchers.IO).launch { try { val data = attManager.read(ATTHandles.TRANSPARENCY) @@ -484,9 +450,6 @@ fun HearingAidScreen(navController: NavController) { } } }, - hazeState = hazeState, - isDarkTheme = isDarkTheme, - textColor = textColor, - activeTrackColor = activeTrackColor + hazeState = hazeState ) -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt new file mode 100644 index 00000000..dc2353aa --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -0,0 +1,563 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.screens + +import android.annotation.SuppressLint +import android.util.Log +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectTapGestures +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.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +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.drawBehind +import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +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 dev.chrisbanes.haze.HazeEffectScope +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledSlider +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.ATTHandles +import me.kavishdevar.librepods.utils.ATTManager +import me.kavishdevar.librepods.utils.RadareOffsetFinder +import me.kavishdevar.librepods.utils.TransparencySettings +import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse +import me.kavishdevar.librepods.utils.sendTransparencySettings +import java.io.IOException +import kotlin.io.encoding.ExperimentalEncodingApi + +private const val TAG = "TransparencySettings" + +@SuppressLint("DefaultLocale") +@ExperimentalHazeMaterialsApi +@OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) +@Composable +fun TransparencySettingsScreen(navController: NavController) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val verticalScrollState = rememberScrollState() + val hazeState = remember { HazeState() } + val snackbarHostState = remember { SnackbarHostState() } + val attManager = ServiceManager.getService()?.attManager ?: return + val aacpManager = remember { ServiceManager.getService()?.aacpManager } + val isSdpOffsetAvailable = + remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } + + 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) + + Scaffold( + containerColor = if (isSystemInDarkTheme()) Color( + 0xFF000000 + ) else Color( + 0xFFF2F2F7 + ), + topBar = { + val darkMode = isSystemInDarkTheme() + val mDensity = remember { mutableFloatStateOf(1f) } + + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(R.string.customize_transparency_mode), + style = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + color = if (darkMode) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + }, + modifier = Modifier + .hazeEffect( + state = hazeState, + style = CupertinoMaterials.thick(), + block = fun HazeEffectScope.() { + alpha = + if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f + }) + .drawBehind { + mDensity.floatValue = density + val strokeWidth = 0.7.dp.value * density + val y = size.height - strokeWidth / 2 + if (verticalScrollState.value > 60.dp.value * density) { + drawLine( + if (darkMode) Color.DarkGray else Color.LightGray, + Offset(0f, y), + Offset(size.width, y), + strokeWidth + ) + } + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + containerColor = Color.Transparent + ) + ) + }, + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + Column( + modifier = Modifier + .hazeSource(hazeState) + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp) + .verticalScroll(verticalScrollState), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + + val enabled = remember { mutableStateOf(false) } + val amplificationSliderValue = remember { mutableFloatStateOf(0.5f) } + val balanceSliderValue = remember { mutableFloatStateOf(0.5f) } + val toneSliderValue = remember { mutableFloatStateOf(0.5f) } + val ambientNoiseReductionSliderValue = remember { mutableFloatStateOf(0.0f) } + val conversationBoostEnabled = remember { mutableStateOf(false) } + val eq = remember { mutableStateOf(FloatArray(8)) } + val phoneMediaEQ = remember { mutableStateOf(FloatArray(8) { 0.5f }) } + + val initialLoadComplete = remember { mutableStateOf(false) } + + val initialReadSucceeded = remember { mutableStateOf(false) } + val initialReadAttempts = remember { mutableIntStateOf(0) } + + val transparencySettings = remember { + mutableStateOf( + TransparencySettings( + enabled = enabled.value, + leftEQ = eq.value, + rightEQ = eq.value, + leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, + rightAmplification = amplificationSliderValue.floatValue + (balanceSliderValue.floatValue - 0.5f) * amplificationSliderValue.floatValue * 2, + leftTone = toneSliderValue.floatValue, + rightTone = toneSliderValue.floatValue, + leftConversationBoost = conversationBoostEnabled.value, + rightConversationBoost = conversationBoostEnabled.value, + leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + netAmplification = amplificationSliderValue.floatValue, + balance = balanceSliderValue.floatValue + ) + ) + } + + val transparencyListener = remember { + object : (ByteArray) -> Unit { + override fun invoke(value: ByteArray) { + val parsed = parseTransparencySettingsResponse(value) + if (parsed != null) { + enabled.value = parsed.enabled + amplificationSliderValue.floatValue = parsed.netAmplification + balanceSliderValue.floatValue = parsed.balance + toneSliderValue.floatValue = parsed.leftTone + ambientNoiseReductionSliderValue.floatValue = + parsed.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsed.leftConversationBoost + eq.value = parsed.leftEQ.copyOf() + Log.d(TAG, "Updated transparency settings from notification") + } else { + Log.w(TAG, "Failed to parse transparency settings from notification") + } + } + } + } + + LaunchedEffect( + enabled.value, + amplificationSliderValue.floatValue, + balanceSliderValue.floatValue, + toneSliderValue.floatValue, + conversationBoostEnabled.value, + ambientNoiseReductionSliderValue.floatValue, + eq.value, + initialLoadComplete.value, + initialReadSucceeded.value + ) { + if (!initialLoadComplete.value) { + Log.d(TAG, "Initial device load not complete - skipping send") + return@LaunchedEffect + } + + if (!initialReadSucceeded.value) { + Log.d( + TAG, + "Initial device read not successful yet - skipping send until read succeeds" + ) + return@LaunchedEffect + } + + transparencySettings.value = TransparencySettings( + enabled = enabled.value, + leftEQ = eq.value, + rightEQ = eq.value, + leftAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue < 0) -balanceSliderValue.floatValue else 0f, + rightAmplification = amplificationSliderValue.floatValue + if (balanceSliderValue.floatValue > 0) balanceSliderValue.floatValue else 0f, + leftTone = toneSliderValue.floatValue, + rightTone = toneSliderValue.floatValue, + leftConversationBoost = conversationBoostEnabled.value, + rightConversationBoost = conversationBoostEnabled.value, + leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, + netAmplification = amplificationSliderValue.floatValue, + balance = balanceSliderValue.floatValue + ) + Log.d("TransparencySettings", "Updated settings: ${transparencySettings.value}") + sendTransparencySettings(attManager, transparencySettings.value) + } + + DisposableEffect(Unit) { + onDispose { + attManager.unregisterListener(ATTHandles.TRANSPARENCY, transparencyListener) + } + } + + LaunchedEffect(Unit) { + Log.d(TAG, "Connecting to ATT...") + try { + attManager.enableNotifications(ATTHandles.TRANSPARENCY) + attManager.registerListener(ATTHandles.TRANSPARENCY, transparencyListener) + + // If we have an AACP manager, prefer its EQ data to populate EQ controls first + try { + if (aacpManager != null) { + Log.d(TAG, "Found AACPManager, reading cached EQ data") + val aacpEQ = aacpManager.eqData + if (aacpEQ.isNotEmpty()) { + eq.value = aacpEQ.copyOf() + phoneMediaEQ.value = aacpEQ.copyOf() + Log.d(TAG, "Populated EQ from AACPManager: ${aacpEQ.toList()}") + } else { + Log.d(TAG, "AACPManager EQ data empty") + } + } else { + Log.d(TAG, "No AACPManager available") + } + } catch (e: Exception) { + Log.w(TAG, "Error reading EQ from AACPManager: ${e.message}") + } + + var parsedSettings: TransparencySettings? = null + for (attempt in 1..3) { + initialReadAttempts.intValue = attempt + try { + val data = attManager.read(ATTHandles.TRANSPARENCY) + parsedSettings = parseTransparencySettingsResponse(data = data) + if (parsedSettings != null) { + Log.d(TAG, "Parsed settings on attempt $attempt") + break + } else { + Log.d(TAG, "Parsing returned null on attempt $attempt") + } + } catch (e: Exception) { + Log.w(TAG, "Read attempt $attempt failed: ${e.message}") + } + delay(200) + } + + if (parsedSettings != null) { + Log.d(TAG, "Initial transparency settings: $parsedSettings") + enabled.value = parsedSettings.enabled + amplificationSliderValue.floatValue = parsedSettings.netAmplification + balanceSliderValue.floatValue = parsedSettings.balance + toneSliderValue.floatValue = parsedSettings.leftTone + ambientNoiseReductionSliderValue.floatValue = + parsedSettings.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsedSettings.leftConversationBoost + eq.value = parsedSettings.leftEQ.copyOf() + initialReadSucceeded.value = true + } else { + Log.d( + TAG, + "Failed to read/parse initial transparency settings after ${initialReadAttempts.intValue} attempts" + ) + } + } catch (e: IOException) { + e.printStackTrace() + } finally { + initialLoadComplete.value = true + } + } + + // Only show transparency mode section if SDP offset is available + if (isSdpOffsetAvailable.value) { + AccessibilityToggle( + text = stringResource(R.string.transparency_mode), + mutableState = enabled, + independent = true, + description = stringResource(R.string.customize_transparency_mode_description) + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.amplification).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + StyledSlider( + valueRange = -1f..1f, + mutableFloatState = amplificationSliderValue, + onValueChange = { + amplificationSliderValue.floatValue = it + }, + startIcon = "􀊥", + endIcon = "􀊩", + independent = true + ) + + Text( + text = stringResource(R.string.balance).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + StyledSlider( + valueRange = -1f..1f, + mutableFloatState = balanceSliderValue, + onValueChange = { + balanceSliderValue.floatValue = it + }, + snapPoints = listOf(0f), + startLabel = stringResource(R.string.left), + endLabel = stringResource(R.string.right), + independent = true, + ) + + Text( + text = stringResource(R.string.tone).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + StyledSlider( + valueRange = -1f..1f, + mutableFloatState = toneSliderValue, + onValueChange = { + toneSliderValue.floatValue = it + }, + startLabel = stringResource(R.string.darker), + endLabel = stringResource(R.string.brighter), + independent = true, + ) + + Text( + text = stringResource(R.string.ambient_noise_reduction).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + + StyledSlider( + valueRange = 0f..1f, + mutableFloatState = ambientNoiseReductionSliderValue, + onValueChange = { + ambientNoiseReductionSliderValue.floatValue = it + }, + startLabel = stringResource(R.string.less), + endLabel = stringResource(R.string.more), + independent = true, + ) + + AccessibilityToggle( + text = stringResource(R.string.conversation_boost), + mutableState = conversationBoostEnabled, + independent = true, + description = stringResource(R.string.conversation_boost_description) + ) + } + + // Only show transparency mode EQ section if SDP offset is available + if (isSdpOffsetAvailable.value) { + Text( + text = stringResource(R.string.equalizer).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + for (i in 0 until 8) { + val eqValue = remember(eq.value[i]) { mutableFloatStateOf(eq.value[i]) } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(38.dp) + ) { + Text( + text = String.format("%.2f", eqValue.floatValue), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + + Slider( + value = eqValue.floatValue, + onValueChange = { newVal -> + eqValue.floatValue = newVal + val newEQ = eq.value.copyOf() + newEQ[i] = eqValue.floatValue + eq.value = newEQ + }, + valueRange = 0f..100f, + modifier = Modifier + .fillMaxWidth(0.9f) + .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(eqValue.floatValue / 100f) + .height(4.dp) + .background( + activeTrackColor, + RoundedCornerShape(4.dp) + ) + ) + } + } + ) + + Text( + text = stringResource(R.string.band_label, i + 1), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index 3a52634a..15b6144c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -178,7 +178,6 @@ class ATTManager(private val device: BluetoothDevice) { throw IllegalStateException("End of stream reached") } val data = buffer.copyOfRange(0, len) - Log.wtf(TAG, "Read ${data.size} bytes from ATT") Log.d(TAG, "readPDU: ${data.joinToString(" ") { String.format("%02X", it) }}") return data } diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 415d5ceb..94dfbc97 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -10,12 +10,12 @@ composeBom = "2025.04.00" annotations = "26.0.2" navigationCompose = "2.8.9" constraintlayout = "2.2.1" -haze = "1.5.3" -hazeMaterials = "1.5.3" -sliceBuilders = "1.1.0-alpha02" -sliceCore = "1.1.0-alpha02" -sliceView = "1.1.0-alpha02" +haze = "1.6.10" +hazeMaterials = "1.6.10" dynamicanimation = "1.1.0" +foundationLayout = "1.9.1" +uiTooling = "1.9.1" +mockk = "1.14.3" [libraries] accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" } @@ -33,10 +33,10 @@ androidx-navigation-compose = { group = "androidx.navigation", name = "navigatio androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" } haze-materials = { group = "dev.chrisbanes.haze", name = "haze-materials", version.ref = "hazeMaterials" } -androidx-slice-builders = { group = "androidx.slice", name = "slice-builders", version.ref = "sliceBuilders" } -androidx-slice-core = { group = "androidx.slice", name = "slice-core", version.ref = "sliceCore" } -androidx-slice-view = { group = "androidx.slice", name = "slice-view", version.ref = "sliceView" } androidx-dynamicanimation = { group = "androidx.dynamicanimation", name = "dynamicanimation", version.ref = "dynamicanimation" } +androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" } +mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From 8760757b763b2fefe78c448fd287051f4a496082 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 23 Sep 2025 00:27:39 +0530 Subject: [PATCH 31/72] android: improve liquid glass sliders --- .../librepods/composables/StyledSlider.kt | 433 ++++++++++-------- 1 file changed, 235 insertions(+), 198 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt index a86e4010..b5c629d9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -18,7 +18,6 @@ package me.kavishdevar.librepods.composables -import android.annotation.SuppressLint import android.content.res.Configuration import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.spring @@ -29,9 +28,9 @@ import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints 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.heightIn @@ -60,6 +59,9 @@ import androidx.compose.ui.graphics.layer.CompositingStrategy import androidx.compose.ui.graphics.layer.drawLayer import androidx.compose.ui.graphics.rememberGraphicsLayer import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font @@ -96,8 +98,7 @@ fun StyledSlider( endIcon: String? = null, startLabel: String? = null, endLabel: String? = null, - independent: Boolean = false, - @SuppressLint("ModifierParameter") modifier: Modifier = Modifier + independent: Boolean = false ) { val backgroundColor = if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val isLightTheme = !isSystemInDarkTheme() @@ -119,213 +120,234 @@ fun StyledSlider( val animationScope = rememberCoroutineScope() val progressAnimationSpec = spring(0.5f, 300f, 0.001f) val progressAnimation = remember { Animatable(0f) } - - val trackBackdrop = rememberBackdrop() val innerShadowLayer = rememberGraphicsLayer().apply { compositingStrategy = CompositingStrategy.Offscreen } - val content = @Composable { - Column( - modifier = modifier.fillMaxWidth(1f).padding(vertical = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (label != null) { - Text( - text = label, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - } + val sliderBackdrop = rememberBackdrop() + val trackWidthState = remember { mutableFloatStateOf(0f) } + val trackPositionState = remember { mutableFloatStateOf(0f) } + val startIconWidthState = remember { mutableFloatStateOf(0f) } + val endIconWidthState = remember { mutableFloatStateOf(0f) } + val density = LocalDensity.current - if (startLabel != null || endLabel != null) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween + val content = @Composable { + Box(Modifier.fillMaxWidth()) { + Box(Modifier + .backdrop(sliderBackdrop) + .fillMaxWidth()) { + Column( + modifier = Modifier + .fillMaxWidth(1f) + .padding(vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - Text( - text = startLabel ?: "", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Text( - text = endLabel ?: "", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) + if (label != null) { + Text( + text = label, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - ) - } - } - - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(0.dp) - ) { - if (startIcon != null) { - Text( - text = startIcon, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(horizontal = 12.dp) - ) - } - BoxWithConstraints( - Modifier - .weight(1f), - contentAlignment = Alignment.CenterStart - ) { - val density = LocalDensity.current - val trackWidth = constraints.maxWidth + } - Box(Modifier.backdrop(trackBackdrop)) { - Box( - Modifier - .clip(RoundedCornerShape(28.dp)) - .background(trackColor) - .height(6f.dp) - .fillMaxWidth() - ) + if (startLabel != null || endLabel != null) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = startLabel ?: "", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Text( + text = endLabel ?: "", + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(0.dp) + ) { + if (startIcon != null) { + Text( + text = startIcon, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp) + .onGloballyPositioned { + startIconWidthState.floatValue = it.size.width.toFloat() + } + ) + } Box( Modifier - .clip(RoundedCornerShape(28.dp)) - .background(accentColor) - .height(6f.dp) - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - val fraction = fraction - val width = (fraction * constraints.maxWidth).fastRoundToInt() - layout(width, placeable.height) { - placeable.place(0, 0) - } + .weight(1f) + .onSizeChanged { trackWidthState.floatValue = it.width.toFloat() } + .onGloballyPositioned { + trackPositionState.floatValue = + it.positionInParent().y + it.size.height / 2f } - ) - } + ) { + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(trackColor) + .height(6f.dp) + .fillMaxWidth() + ) - Box( - Modifier - .graphicsLayer { - val fraction = fraction - translationX = - (-size.width / 2f + fraction * trackWidth) - .fastCoerceIn( - -size.width / 4f, - trackWidth - size.width * 3f / 4f - ) - } - .draggable( - rememberDraggableState { delta -> - val trackWidth = trackWidth - with(density) { 40f.dp.toPx() } - val targetFraction = fraction + delta / trackWidth - val targetValue = - lerp(valueRange.start, valueRange.endInclusive, targetFraction) - .fastCoerceIn(valueRange.start, valueRange.endInclusive) - val snappedValue = if (snapPoints.isNotEmpty()) snapIfClose( - targetValue, - snapPoints, - snapThreshold - ) else targetValue - onValueChange(snappedValue) - }, - Orientation.Horizontal, - startDragImmediately = true, - onDragStarted = { - animationScope.launch { - progressAnimation.animateTo(1f, progressAnimationSpec) + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(accentColor) + .height(6f.dp) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val fraction = fraction + val width = + (fraction * constraints.maxWidth).fastRoundToInt() + layout(width, placeable.height) { + placeable.place(0, 0) + } } - }, - onDragStopped = { - animationScope.launch { - progressAnimation.animateTo(0f, progressAnimationSpec) - onValueChange((mutableFloatState.floatValue * 100).roundToInt() / 100f) + ) + } + if (endIcon != null) { + Text( + text = endIcon, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = labelTextColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp) + .onGloballyPositioned { + endIconWidthState.floatValue = it.size.width.toFloat() } - } ) - .drawBackdrop( - rememberCombinedBackdropDrawer(backdrop, trackBackdrop), - { RoundedCornerShape(28.dp) }, - highlight = { - val progress = progressAnimation.value - Highlight.AmbientDefault.copy(alpha = progress) - }, - shadow = { - Shadow( - elevation = 4f.dp, - color = Color.Black.copy(0.08f) - ) - }, - layer = { - val progress = progressAnimation.value - val scale = lerp(1f, 1.5f, progress) - scaleX = scale - scaleY = scale - }, - onDrawSurface = { - val progress = progressAnimation.value.fastCoerceIn(0f, 1f) + } + } + } + } - val shape = RoundedCornerShape(28.dp) - val outline = shape.createOutline(size, layoutDirection, this) - val innerShadowOffset = 4f.dp.toPx() - val innerShadowBlurRadius = 4f.dp.toPx() + Box( + Modifier + .graphicsLayer { + val startOffset = + if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else 0f + translationX = + startOffset + fraction * trackWidthState.floatValue - size.width / 2f + translationY = trackPositionState.floatValue / 2f + } + .draggable( + rememberDraggableState { delta -> + val trackWidth = trackWidthState.floatValue + if (trackWidth > 0f) { + val targetFraction = fraction + delta / trackWidth + val targetValue = + lerp(valueRange.start, valueRange.endInclusive, targetFraction) + .fastCoerceIn(valueRange.start, valueRange.endInclusive) + val snappedValue = if (snapPoints.isNotEmpty()) snapIfClose( + targetValue, + snapPoints, + snapThreshold + ) else targetValue + onValueChange(snappedValue) + } + }, + Orientation.Horizontal, + startDragImmediately = true, + onDragStarted = { + animationScope.launch { + progressAnimation.animateTo(1f, progressAnimationSpec) + } + }, + onDragStopped = { + animationScope.launch { + progressAnimation.animateTo(0f, progressAnimationSpec) + onValueChange((mutableFloatState.floatValue * 100).roundToInt() / 100f) + } + } + ) + .drawBackdrop( + rememberCombinedBackdropDrawer(backdrop, sliderBackdrop), + { RoundedCornerShape(28.dp) }, + highlight = { + val progress = progressAnimation.value + Highlight.AmbientDefault.copy(alpha = progress) + }, + shadow = { + Shadow( + elevation = 4f.dp, + color = Color.Black.copy(0.08f) + ) + }, + layer = { + val progress = progressAnimation.value + val scale = lerp(1f, 1.5f, progress) + scaleX = scale + scaleY = scale + }, + onDrawSurface = { + val progress = progressAnimation.value.fastCoerceIn(0f, 1f) - innerShadowLayer.alpha = progress - innerShadowLayer.renderEffect = - BlurEffect( - innerShadowBlurRadius, - innerShadowBlurRadius, - TileMode.Decal - ) - innerShadowLayer.record { - drawOutline(outline, Color.Black.copy(0.2f)) - translate(0f, innerShadowOffset) { - drawOutline( - outline, - Color.Transparent, - blendMode = BlendMode.Clear - ) - } - } - drawLayer(innerShadowLayer) + val shape = RoundedCornerShape(28.dp) + val outline = shape.createOutline(size, layoutDirection, this) + val innerShadowOffset = 4f.dp.toPx() + val innerShadowBlurRadius = 4f.dp.toPx() - drawRect(Color.White.copy(1f - progress)) + innerShadowLayer.alpha = progress + innerShadowLayer.renderEffect = + BlurEffect( + innerShadowBlurRadius, + innerShadowBlurRadius, + TileMode.Decal + ) + innerShadowLayer.record { + drawOutline(outline, Color.Black.copy(0.2f)) + translate(0f, innerShadowOffset) { + drawOutline( + outline, + Color.Transparent, + blendMode = BlendMode.Clear + ) } - ) { - refractionWithDispersion(6f.dp.toPx(), size.height / 2f) } - .size(40f.dp, 24f.dp) - ) - } - if (endIcon != null) { - Text( - text = endIcon, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(horizontal = 12.dp) - ) - } - } + drawLayer(innerShadowLayer) + + drawRect(Color.White.copy(1f - progress)) + } + ) { + refractionWithDispersion(6f.dp.toPx(), size.height / 2f) + } + .size(40f.dp, 24f.dp) + ) } } @@ -350,15 +372,30 @@ private fun snapIfClose(value: Float, points: List, threshold: Float = 0. return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Composable fun StyledSliderPreview() { - StyledSlider( - mutableFloatState = remember {mutableFloatStateOf(1f)}, - onValueChange = {}, - valueRange = 0f..2f, - independent = true, - startIcon = "A", - endIcon = "B" - ) + val a = remember { mutableFloatStateOf(1f) } + Box( + Modifier + .background(if (isSystemInDarkTheme()) Color(0xFF121212) else Color(0xFFF0F0F0)) + .padding(16.dp) + .fillMaxSize() + ) { + Box ( + Modifier.align(Alignment.Center) + ) + { + StyledSlider( + mutableFloatState = a, + onValueChange = { + a.floatValue = it + }, + valueRange = 0f..2f, + independent = true, + startIcon = "A", + endIcon = "B" + ) + } + } } From 26de42243fc2d9dc8a7c14174f782d87e5df4b17 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 23 Sep 2025 01:20:41 +0530 Subject: [PATCH 32/72] android: little more liquid glass --- .../composables/ConfirmationDialog.kt | 175 +++++++----------- .../librepods/composables/StyledSlider.kt | 34 ++-- .../librepods/screens/HearingAidScreen.kt | 14 +- 3 files changed, 95 insertions(+), 128 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index 1378c335..c3310c5c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -1,47 +1,39 @@ package me.kavishdevar.librepods.composables import androidx.compose.foundation.background +import androidx.compose.foundation.clickable 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.fillMaxHeight +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.requiredWidthIn -import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.PointerEventType -import androidx.compose.ui.input.pointer.pointerInput 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.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Dialog -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect -import dev.chrisbanes.haze.materials.CupertinoMaterials -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.colorFilter +import com.kyant.backdrop.effects.refraction import me.kavishdevar.librepods.R -@ExperimentalHazeMaterialsApi @Composable fun ConfirmationDialog( showDialog: MutableState, @@ -51,133 +43,100 @@ fun ConfirmationDialog( dismissText: String = "Cancel", onConfirm: () -> Unit, onDismiss: () -> Unit = { showDialog.value = false }, - hazeState: HazeState, + backdrop: Backdrop, ) { - val isDarkTheme = isSystemInDarkTheme() - val textColor = if (isDarkTheme) Color.White else Color.Black - val accentColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) if (showDialog.value) { - Dialog(onDismissRequest = { showDialog.value = false }) { + val isLightTheme = !isSystemInDarkTheme() + val contentColor = if (isLightTheme) Color.Black else Color.White + val accentColor = if (isLightTheme) Color(0xFF0088FF) else Color(0xFF0091FF) + val containerColor = if (isLightTheme) Color(0xFFFAFAFA).copy(0.6f) else Color(0xFF121212).copy(0.4f) + val dimColor = if (isLightTheme) Color(0xFF29293A).copy(0.23f) else Color(0xFF121212).copy(0.56f) + + Box( + Modifier + .background(dimColor) + .fillMaxSize() + .clickable(onClick = onDismiss) + ) { Box( - modifier = Modifier + Modifier + .align(Alignment.Center) + .drawBackdrop( + backdrop, + { RoundedCornerShape(48f.dp) }, +// highlight = { Highlight { HighlightStyle.Solid } }, + onDrawSurface = { drawRect(containerColor) } + ) { + colorFilter( + brightness = if (isLightTheme) 0.2f else 0.1f, + saturation = 1.5f + ) + blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) + refraction(24f.dp.toPx(), 48f.dp.toPx(), true) + } .fillMaxWidth(0.75f) .requiredWidthIn(min = 200.dp, max = 360.dp) - .background(Color.Transparent, RoundedCornerShape(14.dp)) - .clip(RoundedCornerShape(14.dp)) - .hazeEffect( - hazeState, - style = CupertinoMaterials.regular( - containerColor = if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f) - ) - ) ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(24.dp)) + Column(horizontalAlignment = Alignment.Start) { + Spacer(modifier = Modifier.height(28.dp)) Text( title, style = TextStyle( fontSize = 16.sp, fontWeight = FontWeight.Bold, - color = textColor, + color = contentColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), - textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) Text( message, style = TextStyle( fontSize = 14.sp, - color = textColor.copy(alpha = 0.8f), + color = contentColor.copy(alpha = 0.8f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) - androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp)) - HorizontalDivider( - thickness = 1.dp, - color = Color(0x40888888), - modifier = Modifier.fillMaxWidth() - ) - var leftPressed by remember { mutableStateOf(false) } - var rightPressed by remember { mutableStateOf(false) } - val pressedColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + Spacer(modifier = Modifier.height(16.dp)) + Row( - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .pointerInput(Unit) { - awaitPointerEventScope { - while (true) { - val event = awaitPointerEvent() - val position = event.changes.first().position - val width = size.width.toFloat() - val height = size.height.toFloat() - val isWithinBounds = position.y >= 0 && position.y <= height - val isLeft = position.x < width / 2 - event.changes.first().consume() - when (event.type) { - PointerEventType.Press -> { - if (isWithinBounds) { - leftPressed = isLeft - rightPressed = !isLeft - } else { - leftPressed = false - rightPressed = false - } - } - PointerEventType.Move -> { - if (isWithinBounds) { - leftPressed = isLeft - rightPressed = !isLeft - } else { - leftPressed = false - rightPressed = false - } - } - PointerEventType.Release -> { - if (isWithinBounds) { - if (leftPressed) { - onDismiss() - } else if (rightPressed) { - onConfirm() - } - } - leftPressed = false - rightPressed = false - } - } - } - } - }, - horizontalArrangement = Arrangement.Start, + Modifier + .padding(24.dp, 12.dp, 24.dp, 24.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically ) { Box( - modifier = Modifier + Modifier + .clip(RoundedCornerShape(50.dp)) + .background(containerColor.copy(0.2f)) + .clickable(onClick = onDismiss) + .height(48.dp) .weight(1f) - .fillMaxHeight() - .background(if (leftPressed) pressedColor else Color.Transparent), + .padding(horizontal = 16.dp), contentAlignment = Alignment.Center ) { - Text(dismissText, color = accentColor) + Text( + dismissText, + style = TextStyle(contentColor, 16.sp) + ) } Box( - modifier = Modifier - .width(1.dp) - .fillMaxHeight() - .background(Color(0x40888888)) - ) - Box( - modifier = Modifier + Modifier + .clip(RoundedCornerShape(50.dp)) + .background(accentColor) + .clickable(onClick = onConfirm) + .height(48.dp) .weight(1f) - .fillMaxHeight() - .background(if (rightPressed) pressedColor else Color.Transparent), + .padding(horizontal = 16.dp), contentAlignment = Alignment.Center ) { - Text(confirmText, color = accentColor) + Text( + confirmText, + style = TextStyle(Color.White, 16.sp) + ) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt index b5c629d9..64187d15 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -133,16 +133,18 @@ fun StyledSlider( val density = LocalDensity.current val content = @Composable { - Box(Modifier.fillMaxWidth()) { + Box( + Modifier.fillMaxWidth(if (startIcon == null && endIcon == null) 0.95f else 1f) + ) { Box(Modifier .backdrop(sliderBackdrop) .fillMaxWidth()) { Column( modifier = Modifier .fillMaxWidth(1f) - .padding(vertical = 8.dp), + .padding(vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.spacedBy(12.dp) ) { if (label != null) { Text( @@ -152,13 +154,14 @@ fun StyledSlider( fontWeight = FontWeight.Medium, color = labelTextColor, fontFamily = FontFamily(Font(R.font.sf_pro)) - ) + ), ) } if (startLabel != null || endLabel != null) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text( @@ -183,7 +186,10 @@ fun StyledSlider( } Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .then(if (startIcon == null && endIcon == null) Modifier.padding(horizontal = 12.dp) else Modifier), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(0.dp) ) { @@ -191,7 +197,7 @@ fun StyledSlider( Text( text = startIcon, style = TextStyle( - fontSize = 16.sp, + fontSize = 18.sp, fontWeight = FontWeight.Normal, color = labelTextColor, fontFamily = FontFamily(Font(R.font.sf_pro)) @@ -240,7 +246,7 @@ fun StyledSlider( Text( text = endIcon, style = TextStyle( - fontSize = 16.sp, + fontSize = 18.sp, fontWeight = FontWeight.Normal, color = labelTextColor, fontFamily = FontFamily(Font(R.font.sf_pro)) @@ -260,10 +266,10 @@ fun StyledSlider( Modifier .graphicsLayer { val startOffset = - if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else 0f + if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else with(density) { 8.dp.toPx() } translationX = startOffset + fraction * trackWidthState.floatValue - size.width / 2f - translationY = trackPositionState.floatValue / 2f + translationY = if (startLabel != null || endLabel != null) trackPositionState.floatValue + with(density) { 22.dp.toPx() } + size.height / 2f else trackPositionState.floatValue + with(density) { 4.dp.toPx() } } .draggable( rememberDraggableState { delta -> @@ -372,13 +378,13 @@ private fun snapIfClose(value: Float, points: List, threshold: Float = 0. return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun StyledSliderPreview() { val a = remember { mutableFloatStateOf(1f) } Box( Modifier - .background(if (isSystemInDarkTheme()) Color(0xFF121212) else Color(0xFFF0F0F0)) + .background(if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF0F0F0)) .padding(16.dp) .fillMaxSize() ) { @@ -393,8 +399,8 @@ fun StyledSliderPreview() { }, valueRange = 0f..2f, independent = true, - startIcon = "A", - endIcon = "B" + startLabel = "A", + endLabel = "B" ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 27752ece..cf022a43 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -70,6 +70,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.kyant.backdrop.backdrop +import com.kyant.backdrop.rememberBackdrop import dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeEffect @@ -78,7 +80,6 @@ import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.ConfirmationDialog @@ -90,8 +91,6 @@ import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse import me.kavishdevar.librepods.utils.sendTransparencySettings import kotlin.io.encoding.ExperimentalEncodingApi -private var debounceJob: Job? = null -private var phoneMediaDebounceJob: Job? = null private const val TAG = "AccessibilitySettings" @SuppressLint("DefaultLocale") @@ -109,6 +108,7 @@ fun HearingAidScreen(navController: NavController) { val aacpManager = remember { ServiceManager.getService()?.aacpManager } val showDialog = remember { mutableStateOf(false) } + val backdrop = rememberBackdrop() val hearingAidEnabled = remember { val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } @@ -164,7 +164,9 @@ fun HearingAidScreen(navController: NavController) { ) ) }, - snackbarHost = { SnackbarHost(snackbarHostState) } + snackbarHost = { SnackbarHost(snackbarHostState) }, + modifier = Modifier + .backdrop(backdrop) ) { paddingValues -> Column( modifier = Modifier @@ -210,7 +212,7 @@ fun HearingAidScreen(navController: NavController) { } else { aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x02)) aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x02.toByte()) - hearingAidEnabled.value = value + hearingAidEnabled.value = false } } @@ -450,6 +452,6 @@ fun HearingAidScreen(navController: NavController) { } } }, - hazeState = hazeState + backdrop = backdrop ) } From 173e06c5e703cea326acb8a7e42f08ae1e4d5741 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 23 Sep 2025 02:53:10 +0530 Subject: [PATCH 33/72] android: fix hearing aid parsing --- .../librepods/screens/HearingAidAdjustmentsScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 695965f6..36815e22 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -67,8 +67,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.composables.IndependentToggle +import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.ATTHandles @@ -505,6 +505,8 @@ private fun parseHearingAidSettingsResponse(data: ByteArray): HearingAidSettings if (data.size < 104) return null val buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN) + buffer.get() // skip 0x02 + buffer.get() // skip 0x02 buffer.getShort() // skip 0x60 0x00 val leftEQ = FloatArray(8) From 29a35ceebea7eb7432c1a8c2bbe63cef333efe6a Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 23 Sep 2025 11:03:55 +0530 Subject: [PATCH 34/72] android: remove customdeviceactivity from manifest --- android/app/src/main/AndroidManifest.xml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 08024b03..43a2d73c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -74,15 +74,6 @@ android:resource="@xml/battery_widget_info" /> - - - - - Date: Tue, 23 Sep 2025 11:14:31 +0530 Subject: [PATCH 35/72] android: remove unused strings --- android/app/src/main/res/values-zh-rCN/strings.xml | 2 -- android/app/src/main/res/values/strings.xml | 7 ++++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/res/values-zh-rCN/strings.xml b/android/app/src/main/res/values-zh-rCN/strings.xml index 8ebffb3f..3af7d48a 100644 --- a/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/android/app/src/main/res/values-zh-rCN/strings.xml @@ -1,7 +1,5 @@ - LibrePods 让你的 AirPods 摆脱苹果的生态系统。 - GATT 测试 在主屏幕上即可查看 AirPods 的电池状态! 辅助功能 提示音音量 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 83960d08..8634953d 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,7 +1,8 @@ - + LibrePods - Liberate your AirPods from Apple\'s ecosystem. - GATT Testing + Liberate your AirPods from Apple\'s ecosystem. See your AirPods battery status right from your home screen! Accessibility Tone Volume From 7e5ee6726f90a0f394929498a5ca193309d63184 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Tue, 23 Sep 2025 23:52:28 +0530 Subject: [PATCH 36/72] android: small ui tweaks --- android/.gitignore | 1 + .../composables/AdaptiveStrengthSlider.kt | 88 +-- .../composables/CallControlSettings.kt | 4 +- .../composables/MicrophoneSettings.kt | 172 +---- .../librepods/composables/StyledDropdown.kt | 244 +++++++ .../librepods/composables/StyledSlider.kt | 50 +- .../screens/AccessibilitySettingsScreen.kt | 620 +++++++++++------- .../screens/HearingAidAdjustmentsScreen.kt | 45 +- .../screens/TransparencySettingsScreen.kt | 45 +- .../librepods/utils/TransparencyUtils.kt | 22 +- 10 files changed, 693 insertions(+), 598 deletions(-) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt diff --git a/android/.gitignore b/android/.gitignore index 28a82e32..62ca9dc6 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,3 +1,4 @@ +crowdin.yml *.iml .gradle /local.properties diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt index e60bea9e..7e5c8a4e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt @@ -20,22 +20,12 @@ package me.kavishdevar.librepods.composables -import androidx.compose.foundation.background 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.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.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.DisposableEffect @@ -43,19 +33,19 @@ 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.res.stringResource import androidx.compose.ui.text.TextStyle 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 me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.roundToInt -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AdaptiveStrengthSlider() { val sliderValue = remember { mutableFloatStateOf(0f) } @@ -100,80 +90,20 @@ fun AdaptiveStrengthSlider() { Column( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), + .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - Slider( - value = sliderValue.floatValue, + StyledSlider( + mutableFloatState = sliderValue, onValueChange = { sliderValue.floatValue = snapIfClose(it, listOf(0f, 50f, 100f)) }, valueRange = 0f..100f, - onValueChangeFinished = { - sliderValue.floatValue = snapIfClose(sliderValue.floatValue.roundToInt().toFloat(), listOf(0f, 50f, 100f)) - service.aacpManager.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH.value, - value = (100 - sliderValue.floatValue).toInt() - ) - }, - modifier = Modifier - .fillMaxWidth() - .height(36.dp), - colors = SliderDefaults.colors( - thumbColor = thumbColor, - 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)) - ) - } - - } + snapPoints = listOf(0f, 50f, 100f), + startLabel = stringResource(R.string.less_noise), + endLabel = stringResource(R.string.more_noise), + independent = false ) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "Less Noise", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = labelTextColor - ), - modifier = Modifier.padding(start = 4.dp) - ) - Text( - text = "More Noise", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = labelTextColor - ), - modifier = Modifier.padding(end = 4.dp) - ) - } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt index c485c3a9..3a06981b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -287,7 +287,7 @@ fun CallControlSettings(hazeState: HazeState) { ) } - DragSelectableDropdown( + StyledDropdown( expanded = showSinglePressDropdown, onDismissRequest = { showSinglePressDropdown = false @@ -415,7 +415,7 @@ fun CallControlSettings(hazeState: HazeState) { ) } - DragSelectableDropdown( + StyledDropdown( expanded = showDoublePressDropdown, onDismissRequest = { showDoublePressDropdown = false diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt index 8e10609c..b87d74fa 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -269,7 +269,7 @@ fun MicrophoneSettings(hazeState: HazeState) { val microphoneAlwaysRightText = stringResource(R.string.microphone_always_right) val microphoneAlwaysLeftText = stringResource(R.string.microphone_always_left) - DragSelectableDropdown( + StyledDropdown( expanded = showDropdown, onDismissRequest = { showDropdown = false @@ -312,173 +312,3 @@ fun MicrophoneSettings(hazeState: HazeState) { fun MicrophoneSettingsPreview() { MicrophoneSettings(HazeState()) } - -@ExperimentalHazeMaterialsApi -@Composable -fun DragSelectableDropdown( - expanded: Boolean, - onDismissRequest: () -> Unit, - options: List, - selectedOption: String, - touchOffset: Offset?, - boxPosition: Offset, - onOptionSelected: (String) -> Unit, - externalHoveredIndex: Int? = null, - externalDragActive: Boolean = false, - hazeState: HazeState, - @SuppressLint("ModifierParameter") modifier: Modifier = Modifier -) { - if (expanded) { - val relativeOffset = touchOffset?.let { it - boxPosition } ?: Offset.Zero - Popup( - offset = IntOffset(relativeOffset.x.toInt(), relativeOffset.y.toInt()), - onDismissRequest = onDismissRequest - ) { - AnimatedVisibility( - visible = true, - enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(), - exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut() - ) { - Card( - modifier = modifier - .padding(8.dp) - .width(300.dp) - .background(Color.Transparent) - .clip(RoundedCornerShape(8.dp)), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) - ) { - var hoveredIndex by remember { mutableStateOf(null) } - val itemHeight = 48.dp - - var popupSize by remember { mutableStateOf(IntSize(0, 0)) } - var lastDragPosition by remember { mutableStateOf(null) } - - LaunchedEffect(externalHoveredIndex, externalDragActive) { - if (externalDragActive) { - hoveredIndex = externalHoveredIndex - } - } - - Column( - modifier = Modifier - .onGloballyPositioned { coordinates -> - popupSize = coordinates.size - } - .pointerInput(popupSize) { - detectDragGestures( - onDragStart = { offset -> - hoveredIndex = (offset.y / itemHeight.toPx()).toInt() - lastDragPosition = offset - }, - onDrag = { change, _ -> - val y = change.position.y - hoveredIndex = (y / itemHeight.toPx()).toInt() - lastDragPosition = change.position - }, - onDragEnd = { - val pos = lastDragPosition - val withinBounds = pos != null && - pos.x >= 0f && pos.y >= 0f && - pos.x <= popupSize.width.toFloat() && pos.y <= popupSize.height.toFloat() - - if (withinBounds) { - hoveredIndex?.let { idx -> - if (idx in options.indices) { - onOptionSelected(options[idx]) - } - } - onDismissRequest() - } else { - hoveredIndex = null - } - } - ) - } - ) { - options.forEachIndexed { index, text -> - val isHovered = - if (externalDragActive && externalHoveredIndex != null) { - index == externalHoveredIndex - } else { - index == hoveredIndex - } - val isSystemInDarkTheme = isSystemInDarkTheme() - Box( - modifier = Modifier - .fillMaxWidth() - .height(itemHeight) - .background( - Color.Transparent - ) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null - ) { - onOptionSelected(text) - onDismissRequest() - } - .hazeEffect( - state = hazeState, - style = CupertinoMaterials.regular(), - block = fun HazeEffectScope.() { - alpha = 1f - backgroundColor = if (isSystemInDarkTheme) { - Color(0xB02C2C2E) - } else { - Color(0xB0FFFFFF) - } - tints = if (isHovered) listOf( - HazeTint( - color = if (isSystemInDarkTheme) Color(0x338A8A8A) else Color(0x40D9D9D9) - ) - ) else listOf() - }) - .padding(horizontal = 12.dp), - contentAlignment = Alignment.CenterStart - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text, - style = TextStyle( - fontSize = 16.sp, - color = if (isSystemInDarkTheme()) Color.White else Color.Black.copy(alpha = 0.75f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - Checkbox( - checked = text == selectedOption, - onCheckedChange = { onOptionSelected(text) }, - 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 - ) - ) - } - } - - if (index != options.lastIndex) { - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888), - modifier = Modifier.padding(start = 12.dp, end = 0.dp) - ) - } - } - } - } - } - } - } -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt new file mode 100644 index 00000000..46168b8d --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt @@ -0,0 +1,244 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.composables + +import android.annotation.SuppressLint +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectDragGestures +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.fillMaxWidth +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.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup +import dev.chrisbanes.haze.HazeEffectScope +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.HazeTint +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import me.kavishdevar.librepods.R + +@ExperimentalHazeMaterialsApi +@Composable +fun StyledDropdown( + expanded: Boolean, + onDismissRequest: () -> Unit, + options: List, + selectedOption: String, + touchOffset: Offset?, + boxPosition: Offset, + onOptionSelected: (String) -> Unit, + externalHoveredIndex: Int? = null, + externalDragActive: Boolean = false, + hazeState: HazeState, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier +) { + if (expanded) { + val relativeOffset = touchOffset?.let { it - boxPosition } ?: Offset.Zero + Popup( + offset = IntOffset(relativeOffset.x.toInt(), relativeOffset.y.toInt()), + onDismissRequest = onDismissRequest + ) { + AnimatedVisibility( + visible = true, + enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut() + ) { + Card( + modifier = modifier + .padding(8.dp) + .width(300.dp) + .background(Color.Transparent) + .clip(RoundedCornerShape(8.dp)), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + var hoveredIndex by remember { mutableStateOf(null) } + val itemHeight = 48.dp + + var popupSize by remember { mutableStateOf(IntSize(0, 0)) } + var lastDragPosition by remember { mutableStateOf(null) } + + LaunchedEffect(externalHoveredIndex, externalDragActive) { + if (externalDragActive) { + hoveredIndex = externalHoveredIndex + } + } + + Column( + modifier = Modifier + .onGloballyPositioned { coordinates -> + popupSize = coordinates.size + } + .pointerInput(popupSize) { + detectDragGestures( + onDragStart = { offset -> + hoveredIndex = (offset.y / itemHeight.toPx()).toInt() + lastDragPosition = offset + }, + onDrag = { change, _ -> + val y = change.position.y + hoveredIndex = (y / itemHeight.toPx()).toInt() + lastDragPosition = change.position + }, + onDragEnd = { + val pos = lastDragPosition + val withinBounds = pos != null && + pos.x >= 0f && pos.y >= 0f && + pos.x <= popupSize.width.toFloat() && pos.y <= popupSize.height.toFloat() + + if (withinBounds) { + hoveredIndex?.let { idx -> + if (idx in options.indices) { + onOptionSelected(options[idx]) + } + } + onDismissRequest() + } else { + hoveredIndex = null + } + } + ) + } + ) { + options.forEachIndexed { index, text -> + val isHovered = + if (externalDragActive && externalHoveredIndex != null) { + index == externalHoveredIndex + } else { + index == hoveredIndex + } + val isSystemInDarkTheme = isSystemInDarkTheme() + Box( + modifier = Modifier + .fillMaxWidth() + .height(itemHeight) + .background( + Color.Transparent + ) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + onOptionSelected(text) + onDismissRequest() + } + .hazeEffect( + state = hazeState, + style = CupertinoMaterials.regular(), + block = fun HazeEffectScope.() { + alpha = 1f + backgroundColor = if (isSystemInDarkTheme) { + Color(0xB02C2C2E) + } else { + Color(0xB0FFFFFF) + } + tints = if (isHovered) listOf( + HazeTint( + color = if (isSystemInDarkTheme) Color(0x338A8A8A) else Color(0x40D9D9D9) + ) + ) else listOf() + }) + .padding(horizontal = 12.dp), + contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text, + style = TextStyle( + fontSize = 16.sp, + color = if (isSystemInDarkTheme()) Color.White else Color.Black.copy(alpha = 0.75f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + Checkbox( + checked = text == selectedOption, + onCheckedChange = { onOptionSelected(text) }, + 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 + ) + ) + } + } + + if (index != options.lastIndex) { + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) + ) + } + } + } + } + } + } + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt index 64187d15..523eb2c2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -19,6 +19,7 @@ package me.kavishdevar.librepods.composables import android.content.res.Configuration +import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.spring import androidx.compose.foundation.background @@ -87,7 +88,7 @@ import kotlin.math.roundToInt @Composable fun StyledSlider( - label: String? = null, + label: String? = null, // New optional parameter for the label mutableFloatState: MutableFloatState, onValueChange: (Float) -> Unit, valueRange: ClosedFloatingPointRange, @@ -146,18 +147,6 @@ fun StyledSlider( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp) ) { - if (label != null) { - Text( - text = label, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = labelTextColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - } - if (startLabel != null || endLabel != null) { Row( modifier = Modifier @@ -358,17 +347,38 @@ fun StyledSlider( } if (independent) { - Box( + + Column ( modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(horizontal = 8.dp, vertical = 0.dp) - .heightIn(min = 55.dp), - contentAlignment = Alignment.Center + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(2.dp) ) { - content() + if (label != null) { + Text( + text = label, + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = labelTextColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp) + ) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(horizontal = 8.dp, vertical = 0.dp) + .heightIn(min = 55.dp), + contentAlignment = Alignment.Center + ) { + content() + } } } else { + if (label != null) Log.w("StyledSlider", "Label is ignored when independent is false") content() } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index b6fc44f1..95df8362 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -41,6 +42,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxDefaults @@ -48,6 +51,7 @@ import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults @@ -62,6 +66,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -73,6 +78,9 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInParent +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font @@ -94,10 +102,11 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.SinglePodANCSwitch +import me.kavishdevar.librepods.composables.StyledSlider +import me.kavishdevar.librepods.composables.StyledDropdown import me.kavishdevar.librepods.composables.StyledSwitch import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager @@ -132,6 +141,36 @@ fun AccessibilitySettingsScreen(navController: NavController) { val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) + val hearingAidEnabled = remember { mutableStateOf( + aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID }?.value?.getOrNull(1) == 0x01.toByte() && + aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG }?.value?.getOrNull(0) == 0x01.toByte() + ) } + + val hearingAidListener = remember { + object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value || + controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value) { + val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } + val assistStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG } + hearingAidEnabled.value = (aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte()) + } + } + } + } + + LaunchedEffect(Unit) { + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) + aacpManager?.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + } + + DisposableEffect(Unit) { + onDispose { + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID, hearingAidListener) + aacpManager?.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG, hearingAidListener) + } + } + Scaffold( containerColor = if (isSystemInDarkTheme()) Color( 0xFF000000 @@ -411,23 +450,14 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } - Text( - text = stringResource(R.string.tone_volume).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.tone_volume).uppercase(), mutableFloatState = toneVolumeValue, onValueChange = { toneVolumeValue.floatValue = it }, - valueRange = 0f..125f, - snapPoints = listOf(100f), + valueRange = 0f..100f, + snapPoints = listOf(75f), startIcon = "\uDBC0\uDEA1", endIcon = "\uDBC0\uDEA9", independent = true @@ -442,8 +472,25 @@ fun AccessibilitySettingsScreen(navController: NavController) { verticalArrangement = Arrangement.SpaceBetween ) { SinglePodANCSwitch() + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) + ) + VolumeControlSwitch() + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) + ) + LoudSoundReductionSwitch() + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) + ) DropdownMenuComponent( label = stringResource(R.string.press_speed), @@ -461,8 +508,15 @@ fun AccessibilitySettingsScreen(navController: NavController) { ?: 0.toByte() ) }, - textColor = textColor + textColor = textColor, + hazeState = hazeState ) + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) + ) + DropdownMenuComponent( label = stringResource(R.string.press_and_hold_duration), options = listOf( @@ -479,8 +533,15 @@ fun AccessibilitySettingsScreen(navController: NavController) { ?: 0.toByte() ) }, - textColor = textColor + textColor = textColor, + hazeState = hazeState + ) + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888), + modifier = Modifier.padding(start = 12.dp, end = 0.dp) ) + DropdownMenuComponent( label = stringResource(R.string.volume_swipe_speed), options = listOf( @@ -497,234 +558,237 @@ fun AccessibilitySettingsScreen(navController: NavController) { ?: 1.toByte() ) }, - textColor = textColor + textColor = textColor, + hazeState = hazeState ) } - NavigationButton( - to = "transparency_customization", - name = stringResource(R.string.customize_transparency_mode), - navController = navController - ) - - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = stringResource(R.string.apply_eq_to).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(vertical = 0.dp) - ) { - val darkModeLocal = isSystemInDarkTheme() - - val phoneShape = RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - var phoneBackgroundColor by remember { - mutableStateOf( - if (darkModeLocal) Color( - 0xFF1C1C1E - ) else Color(0xFFFFFFFF) - ) - } - val phoneAnimatedBackgroundColor by animateColorAsState( - targetValue = phoneBackgroundColor, - animationSpec = tween(durationMillis = 500) + if (!hearingAidEnabled.value) { + NavigationButton( + to = "transparency_customization", + name = stringResource(R.string.customize_transparency_mode), + navController = navController ) - Row( + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = stringResource(R.string.apply_eq_to).uppercase(), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(8.dp, bottom = 0.dp) + ) + Column( modifier = Modifier - .height(48.dp) .fillMaxWidth() - .background(phoneAnimatedBackgroundColor, phoneShape) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - phoneBackgroundColor = - if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - phoneBackgroundColor = - if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - phoneEQEnabled.value = !phoneEQEnabled.value - } - ) - } - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(vertical = 0.dp) ) { - Text( - stringResource(R.string.phone), - fontSize = 16.sp, - color = textColor, - fontFamily = FontFamily(Font(R.font.sf_pro)), - modifier = Modifier.weight(1f) - ) - Checkbox( - checked = phoneEQEnabled.value, - onCheckedChange = { phoneEQEnabled.value = it }, - colors = CheckboxDefaults.colors().copy( - checkedCheckmarkColor = Color(0xFF007AFF), - uncheckedCheckmarkColor = Color.Transparent, - checkedBoxColor = Color.Transparent, - uncheckedBoxColor = Color.Transparent, - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent - ), - modifier = Modifier - .height(24.dp) - .scale(1.5f) + val darkModeLocal = isSystemInDarkTheme() + + val phoneShape = RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) + var phoneBackgroundColor by remember { + mutableStateOf( + if (darkModeLocal) Color( + 0xFF1C1C1E + ) else Color(0xFFFFFFFF) + ) + } + val phoneAnimatedBackgroundColor by animateColorAsState( + targetValue = phoneBackgroundColor, + animationSpec = tween(durationMillis = 500) ) - } - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888) - ) + Row( + modifier = Modifier + .height(48.dp) + .fillMaxWidth() + .background(phoneAnimatedBackgroundColor, phoneShape) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + phoneBackgroundColor = + if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + phoneBackgroundColor = + if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + phoneEQEnabled.value = !phoneEQEnabled.value + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + stringResource(R.string.phone), + fontSize = 16.sp, + color = textColor, + fontFamily = FontFamily(Font(R.font.sf_pro)), + modifier = Modifier.weight(1f) + ) + Checkbox( + checked = phoneEQEnabled.value, + onCheckedChange = { phoneEQEnabled.value = it }, + colors = CheckboxDefaults.colors().copy( + checkedCheckmarkColor = Color(0xFF007AFF), + uncheckedCheckmarkColor = Color.Transparent, + checkedBoxColor = Color.Transparent, + uncheckedBoxColor = Color.Transparent, + checkedBorderColor = Color.Transparent, + uncheckedBorderColor = Color.Transparent + ), + modifier = Modifier + .height(24.dp) + .scale(1.5f) + ) + } - val mediaShape = RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) - var mediaBackgroundColor by remember { - mutableStateOf( - if (darkModeLocal) Color( - 0xFF1C1C1E - ) else Color(0xFFFFFFFF) + HorizontalDivider( + thickness = 1.5.dp, + color = Color(0x40888888) ) - } - val mediaAnimatedBackgroundColor by animateColorAsState( - targetValue = mediaBackgroundColor, - animationSpec = tween(durationMillis = 500) - ) - Row( - modifier = Modifier - .height(48.dp) - .fillMaxWidth() - .background(mediaAnimatedBackgroundColor, mediaShape) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - mediaBackgroundColor = - if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - mediaBackgroundColor = - if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - mediaEQEnabled.value = !mediaEQEnabled.value - } - ) - } - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - stringResource(R.string.media), - fontSize = 16.sp, - color = textColor, - fontFamily = FontFamily(Font(R.font.sf_pro)), - modifier = Modifier.weight(1f) - ) - Checkbox( - checked = mediaEQEnabled.value, - onCheckedChange = { mediaEQEnabled.value = it }, - colors = CheckboxDefaults.colors().copy( - checkedCheckmarkColor = Color(0xFF007AFF), - uncheckedCheckmarkColor = Color.Transparent, - checkedBoxColor = Color.Transparent, - uncheckedBoxColor = Color.Transparent, - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent - ), - modifier = Modifier - .height(24.dp) - .scale(1.5f) + val mediaShape = RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + var mediaBackgroundColor by remember { + mutableStateOf( + if (darkModeLocal) Color( + 0xFF1C1C1E + ) else Color(0xFFFFFFFF) + ) + } + val mediaAnimatedBackgroundColor by animateColorAsState( + targetValue = mediaBackgroundColor, + animationSpec = tween(durationMillis = 500) ) - } - } - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(12.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - for (i in 0 until 8) { - val eqPhoneValue = - remember(phoneMediaEQ.value[i]) { mutableFloatStateOf(phoneMediaEQ.value[i]) } Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, modifier = Modifier + .height(48.dp) .fillMaxWidth() - .height(38.dp) + .background(mediaAnimatedBackgroundColor, mediaShape) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + mediaBackgroundColor = + if (darkModeLocal) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + mediaBackgroundColor = + if (darkModeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + mediaEQEnabled.value = !mediaEQEnabled.value + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically ) { Text( - text = String.format("%.2f", eqPhoneValue.floatValue), - fontSize = 12.sp, + stringResource(R.string.media), + fontSize = 16.sp, color = textColor, - modifier = Modifier.padding(bottom = 4.dp) + fontFamily = FontFamily(Font(R.font.sf_pro)), + modifier = Modifier.weight(1f) + ) + Checkbox( + checked = mediaEQEnabled.value, + onCheckedChange = { mediaEQEnabled.value = it }, + colors = CheckboxDefaults.colors().copy( + checkedCheckmarkColor = Color(0xFF007AFF), + uncheckedCheckmarkColor = Color.Transparent, + checkedBoxColor = Color.Transparent, + uncheckedBoxColor = Color.Transparent, + checkedBorderColor = Color.Transparent, + uncheckedBorderColor = Color.Transparent + ), + modifier = Modifier + .height(24.dp) + .scale(1.5f) ) + } + } - Slider( - value = eqPhoneValue.floatValue, - onValueChange = { newVal -> - eqPhoneValue.floatValue = newVal - val newEQ = phoneMediaEQ.value.copyOf() - newEQ[i] = eqPhoneValue.floatValue - phoneMediaEQ.value = newEQ - }, - valueRange = 0f..100f, + Column( + modifier = Modifier + .fillMaxWidth() + .background(backgroundColor, RoundedCornerShape(14.dp)) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + for (i in 0 until 8) { + val eqPhoneValue = + remember(phoneMediaEQ.value[i]) { mutableFloatStateOf(phoneMediaEQ.value[i]) } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, modifier = Modifier - .fillMaxWidth(0.9f) - .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 - ) - { + .fillMaxWidth() + .height(38.dp) + ) { + Text( + text = String.format("%.2f", eqPhoneValue.floatValue), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + + Slider( + value = eqPhoneValue.floatValue, + onValueChange = { newVal -> + eqPhoneValue.floatValue = newVal + val newEQ = phoneMediaEQ.value.copyOf() + newEQ[i] = eqPhoneValue.floatValue + phoneMediaEQ.value = newEQ + }, + valueRange = 0f..100f, + modifier = Modifier + .fillMaxWidth(0.9f) + .height(36.dp), + colors = SliderDefaults.colors( + thumbColor = thumbColor, + activeTrackColor = activeTrackColor, + inactiveTrackColor = trackColor + ), + thumb = { Box( modifier = Modifier - .fillMaxWidth() - .height(4.dp) - .background(trackColor, RoundedCornerShape(4.dp)) + .size(24.dp) + .shadow(4.dp, CircleShape) + .background(thumbColor, CircleShape) ) + }, + track = { Box( modifier = Modifier - .fillMaxWidth(eqPhoneValue.floatValue / 100f) - .height(4.dp) - .background(activeTrackColor, RoundedCornerShape(4.dp)) + .fillMaxWidth() + .height(12.dp), + contentAlignment = Alignment.CenterStart ) + { + Box( + modifier = Modifier + .fillMaxWidth() + .height(4.dp) + .background(trackColor, RoundedCornerShape(4.dp)) + ) + Box( + modifier = Modifier + .fillMaxWidth(eqPhoneValue.floatValue / 100f) + .height(4.dp) + .background(activeTrackColor, RoundedCornerShape(4.dp)) + ) + } } - } - ) + ) - Text( - text = stringResource(R.string.band_label, i + 1), - fontSize = 12.sp, - color = textColor, - modifier = Modifier.padding(top = 4.dp) - ) + Text( + text = stringResource(R.string.band_label, i + 1), + fontSize = 12.sp, + color = textColor, + modifier = Modifier.padding(top = 4.dp) + ) + } } } } @@ -832,55 +896,129 @@ fun AccessibilityToggle( } } + @Composable private fun DropdownMenuComponent( label: String, options: List, selectedOption: String, onOptionSelected: (String) -> Unit, - textColor: Color + textColor: Color, + hazeState: HazeState ) { + val density = LocalDensity.current + val itemHeightPx = with(density) { 48.dp.toPx() } + var expanded by remember { mutableStateOf(false) } + var touchOffset by remember { mutableStateOf(null) } + var boxPosition by remember { mutableStateOf(Offset.Zero) } + var lastDismissTime by remember { mutableLongStateOf(0L) } + var parentHoveredIndex by remember { mutableStateOf(null) } + var parentDragActive by remember { mutableStateOf(false) } - Column( + Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp) + .padding(start = 12.dp, end = 12.dp) + .height(55.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val now = System.currentTimeMillis() + if (expanded) { + expanded = false + lastDismissTime = now + } else { + if (now - lastDismissTime > 250L) { + touchOffset = offset + expanded = true + } + } + } + } + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + val now = System.currentTimeMillis() + touchOffset = offset + if (!expanded && now - lastDismissTime > 250L) { + expanded = true + } + lastDismissTime = now + parentDragActive = true + parentHoveredIndex = 0 + }, + onDrag = { change, _ -> + val current = change.position + val touch = touchOffset ?: current + val posInPopupY = current.y - touch.y + val idx = (posInPopupY / itemHeightPx).toInt() + parentHoveredIndex = idx + }, + onDragEnd = { + parentDragActive = false + parentHoveredIndex?.let { idx -> + if (idx in options.indices) { + onOptionSelected(options[idx]) + expanded = false + lastDismissTime = System.currentTimeMillis() + } + } + parentHoveredIndex = null + }, + onDragCancel = { + parentDragActive = false + parentHoveredIndex = null + } + ) + }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { Text( text = label, - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor - ) + fontSize = 16.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) ) - Box( - modifier = Modifier - .fillMaxWidth() - .clickable { expanded = true } - .padding(8.dp) - ) { - Text( - text = selectedOption, - modifier = Modifier.padding(16.dp), - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } + modifier = Modifier.onGloballyPositioned { coordinates -> + boxPosition = coordinates.positionInParent() + } ) { - options.forEach { option -> - DropdownMenuItem( - onClick = { - onOptionSelected(option) - expanded = false - }, - text = { Text(text = option) } + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedOption, + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f) + ) + Icon( + Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = textColor.copy(alpha = 0.6f) ) } + + StyledDropdown( + expanded = expanded, + onDismissRequest = { + expanded = false + lastDismissTime = System.currentTimeMillis() + }, + options = options, + selectedOption = selectedOption, + touchOffset = touchOffset, + boxPosition = boxPosition, + externalHoveredIndex = parentHoveredIndex, + externalDragActive = parentDragActive, + onOptionSelected = { option -> + onOptionSelected(option) + expanded = false + }, + hazeState = hazeState + ) } } } \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 36815e22..1dacdf65 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -343,17 +343,8 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController } } - Text( - text = stringResource(R.string.amplification).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.amplification).uppercase(), valueRange = -1f..1f, mutableFloatState = amplificationSliderValue, onValueChange = { @@ -374,17 +365,8 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController description = stringResource(R.string.swipe_amplification_description) ) - Text( - text = stringResource(R.string.balance).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.balance).uppercase(), valueRange = -1f..1f, mutableFloatState = balanceSliderValue, onValueChange = { @@ -396,17 +378,8 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController independent = true, ) - Text( - text = stringResource(R.string.tone).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.tone).uppercase(), valueRange = -1f..1f, mutableFloatState = toneSliderValue, onValueChange = { @@ -417,18 +390,8 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController independent = true, ) - Text( - text = stringResource(R.string.ambient_noise_reduction).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - StyledSlider( + label = stringResource(R.string.ambient_noise_reduction).uppercase(), valueRange = 0f..1f, mutableFloatState = ambientNoiseReductionSliderValue, onValueChange = { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt index dc2353aa..e47e417f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -363,17 +363,8 @@ fun TransparencySettingsScreen(navController: NavController) { description = stringResource(R.string.customize_transparency_mode_description) ) Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.amplification).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.amplification).uppercase(), valueRange = -1f..1f, mutableFloatState = amplificationSliderValue, onValueChange = { @@ -384,17 +375,8 @@ fun TransparencySettingsScreen(navController: NavController) { independent = true ) - Text( - text = stringResource(R.string.balance).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.balance).uppercase(), valueRange = -1f..1f, mutableFloatState = balanceSliderValue, onValueChange = { @@ -406,17 +388,8 @@ fun TransparencySettingsScreen(navController: NavController) { independent = true, ) - Text( - text = stringResource(R.string.tone).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) StyledSlider( + label = stringResource(R.string.tone).uppercase(), valueRange = -1f..1f, mutableFloatState = toneSliderValue, onValueChange = { @@ -427,18 +400,8 @@ fun TransparencySettingsScreen(navController: NavController) { independent = true, ) - Text( - text = stringResource(R.string.ambient_noise_reduction).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 0.dp) - ) - StyledSlider( + label = stringResource(R.string.ambient_noise_reduction).uppercase(), valueRange = 0f..1f, mutableFloatState = ambientNoiseReductionSliderValue, onValueChange = { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt index b8de0e16..fdf84bc0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt @@ -25,7 +25,8 @@ data class TransparencySettings( val leftAmbientNoiseReduction: Float, val rightAmbientNoiseReduction: Float, val netAmplification: Float, - val balance: Float + val balance: Float, + val ownVoiceAmplification: Float? = null ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -44,6 +45,7 @@ data class TransparencySettings( if (rightAmbientNoiseReduction != other.rightAmbientNoiseReduction) return false if (!leftEQ.contentEquals(other.leftEQ)) return false if (!rightEQ.contentEquals(other.rightEQ)) return false + if (ownVoiceAmplification != other.ownVoiceAmplification) return false return true } @@ -60,6 +62,7 @@ data class TransparencySettings( result = 31 * result + rightAmbientNoiseReduction.hashCode() result = 31 * result + leftEQ.contentHashCode() result = 31 * result + rightEQ.contentHashCode() + result = 31 * result + (ownVoiceAmplification?.hashCode() ?: 0) return result } } @@ -91,6 +94,12 @@ fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings? { val rightConversationBoost = rightConvFloat > 0.5f val rightAmbientNoiseReduction = buffer.float + val ownVoiceAmplification = if (buffer.remaining() >= 4) { + buffer.float + } else { + null + } + val avg = (leftAmplification + rightAmplification) / 2 val amplification = avg.coerceIn(-1f, 1f) val diff = rightAmplification - leftAmplification @@ -109,7 +118,8 @@ fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings? { leftAmbientNoiseReduction = leftAmbientNoiseReduction, rightAmbientNoiseReduction = rightAmbientNoiseReduction, netAmplification = amplification, - balance = balance + balance = balance, + ownVoiceAmplification = ownVoiceAmplification ) } @@ -120,7 +130,9 @@ fun sendTransparencySettings(attManager: ATTManager, transparencySettings: Trans debounceJob = CoroutineScope(Dispatchers.IO).launch { delay(100) try { - val buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN) + val buffer = ByteBuffer.allocate( + if (transparencySettings.ownVoiceAmplification != null) 104 else 100 + ).order(ByteOrder.LITTLE_ENDIAN) buffer.putFloat(if (transparencySettings.enabled) 1.0f else 0.0f) @@ -140,6 +152,10 @@ fun sendTransparencySettings(attManager: ATTManager, transparencySettings: Trans buffer.putFloat(if (transparencySettings.rightConversationBoost) 1.0f else 0.0f) buffer.putFloat(transparencySettings.rightAmbientNoiseReduction) + if (transparencySettings.ownVoiceAmplification != null) { + buffer.putFloat(transparencySettings.ownVoiceAmplification) + } + val data = buffer.array() attManager.write(ATTHandles.TRANSPARENCY, value = data) } catch (e: IOException) { From 86a6a28dc13ef0e4c628146389f1cd6b6b40c3e6 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 26 Sep 2025 03:22:01 +0530 Subject: [PATCH 37/72] android: a very big commit refactoring ui, mostly --- .../me/kavishdevar/librepods/MainActivity.kt | 13 +- .../librepods/QuickSettingsDialogActivity.kt | 18 + .../librepods/composables/AudioSettings.kt | 63 ++- .../composables/AutomaticConnectionSwitch.kt | 22 +- .../librepods/composables/BatteryIndicator.kt | 150 +++---- .../librepods/composables/BatteryView.kt | 104 +++-- .../composables/ConfirmationDialog.kt | 259 +++++++++-- .../composables/ConnectionSettings.kt | 13 +- .../ConversationalAwarenessSwitch.kt | 161 ------- .../composables/EarDetectionSwitch.kt | 23 +- .../composables/IndependentToggle.kt | 189 -------- .../composables/LoudSoundReductionSwitch.kt | 3 +- .../composables/MicrophoneSettings.kt | 26 -- .../librepods/composables/NavigationButton.kt | 4 +- .../composables/NoiseControlSettings.kt | 3 +- .../composables/PersonalizedVolumeSwitch.kt | 10 +- .../librepods/composables/StyledButton.kt | 284 ++++++++++++ .../librepods/composables/StyledIconButton.kt | 258 +++++++++++ .../librepods/composables/StyledScaffold.kt | 166 +++++++ .../librepods/composables/StyledSlider.kt | 54 ++- .../librepods/composables/StyledToggle.kt | 416 ++++++++++++++++++ .../librepods/constants/Packets.kt | 40 +- .../screens/AccessibilitySettingsScreen.kt | 199 +++------ .../AdaptiveStrengthScreen.kt} | 111 +++-- .../screens/AirPodsSettingsScreen.kt | 189 ++------ .../librepods/screens/AppSettingsScreen.kt | 212 +++------ .../librepods/screens/DebugScreen.kt | 303 ++++--------- .../librepods/screens/HeadTrackingScreen.kt | 192 ++------ .../screens/HearingAidAdjustmentsScreen.kt | 103 +---- .../librepods/screens/HearingAidScreen.kt | 133 ++---- .../librepods/screens/Onboarding.kt | 82 ++-- .../screens/PressAndHoldSettingsScreen.kt | 94 ++-- .../librepods/screens/RenameScreen.kt | 71 +-- .../screens/TransparencySettingsScreen.kt | 95 +--- .../screens/TroubleshootingScreen.kt | 122 +---- .../librepods/utils/AACPManager.kt | 46 +- .../kavishdevar/librepods/utils/ATTManager.kt | 4 +- .../librepods/utils/BluetoothCryptography.kt | 4 +- .../kavishdevar/librepods/utils/DragUtils.kt | 102 +++++ .../librepods/utils/GestureDetector.kt | 18 + .../librepods/utils/GestureFeedback.kt | 18 + .../librepods/utils/HeadOrientation.kt | 18 + .../librepods/utils/IslandWindow.kt | 1 - .../librepods/utils/LogCollector.kt | 2 +- .../librepods/utils/RadareOffsetFinder.kt | 3 +- .../librepods/utils/TransparencyUtils.kt | 26 +- .../app/src/main/res/drawable/pro_2_case.png | Bin 56178 -> 53180 bytes .../src/main/res/values-zh-rCN/strings.xml | 2 - android/app/src/main/res/values/strings.xml | 41 +- 49 files changed, 2377 insertions(+), 2093 deletions(-) delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt rename android/app/src/main/java/me/kavishdevar/librepods/{composables/AdaptiveStrengthSlider.kt => screens/AdaptiveStrengthScreen.kt} (53%) create mode 100644 android/app/src/main/java/me/kavishdevar/librepods/utils/DragUtils.kt diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 9ffc793f..9696eb9f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -109,16 +109,17 @@ import com.google.accompanist.permissions.rememberMultiplePermissionsState import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.constants.AirPodsNotifications import me.kavishdevar.librepods.screens.AccessibilitySettingsScreen +import me.kavishdevar.librepods.screens.AdaptiveStrengthScreen import me.kavishdevar.librepods.screens.AirPodsSettingsScreen import me.kavishdevar.librepods.screens.AppSettingsScreen import me.kavishdevar.librepods.screens.DebugScreen import me.kavishdevar.librepods.screens.HeadTrackingScreen -import me.kavishdevar.librepods.screens.HearingAidScreen import me.kavishdevar.librepods.screens.HearingAidAdjustmentsScreen -import me.kavishdevar.librepods.screens.TransparencySettingsScreen +import me.kavishdevar.librepods.screens.HearingAidScreen import me.kavishdevar.librepods.screens.LongPress import me.kavishdevar.librepods.screens.Onboarding import me.kavishdevar.librepods.screens.RenameScreen +import me.kavishdevar.librepods.screens.TransparencySettingsScreen import me.kavishdevar.librepods.screens.TroubleshootingScreen import me.kavishdevar.librepods.services.AirPodsService import me.kavishdevar.librepods.ui.theme.LibrePodsTheme @@ -201,15 +202,12 @@ class MainActivity : ComponentActivity() { if (data != null && data.scheme == "librepods") { when (data.host) { "add-magic-keys" -> { - // Extract query parameters val queryParams = data.queryParameterNames queryParams.forEach { param -> val value = data.getQueryParameter(param) - // Handle your parameters here Log.d("LibrePods", "Parameter: $param = $value") } - // Process the magic keys addition handleAddMagicKeys(data) } } @@ -369,7 +367,7 @@ fun Main() { name = navBackStackEntry.arguments?.getString("bud")!! ) } - composable("rename") { navBackStackEntry -> + composable("rename") { RenameScreen(navController) } composable("app_settings") { @@ -396,6 +394,9 @@ fun Main() { composable("hearing_aid_adjustments") { HearingAidAdjustmentsScreen(navController) } + composable("adaptive_strength") { + AdaptiveStrengthScreen(navController) + } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt index b30c3ed4..bd46412b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/QuickSettingsDialogActivity.kt @@ -1,3 +1,21 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + @file:OptIn(ExperimentalEncodingApi::class) package me.kavishdevar.librepods diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt index 059dc103..0a355d9e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AudioSettings.kt @@ -20,19 +20,17 @@ package me.kavishdevar.librepods.composables -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -40,12 +38,14 @@ 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 androidx.navigation.compose.rememberNavController import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi @Composable -fun AudioSettings() { +fun AudioSettings(navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black @@ -63,12 +63,18 @@ fun AudioSettings() { Column( modifier = Modifier + .clip(RoundedCornerShape(14.dp)) .fillMaxWidth() .background(backgroundColor, RoundedCornerShape(14.dp)) .padding(top = 2.dp) ) { - PersonalizedVolumeSwitch() + StyledToggle( + label = stringResource(R.string.personalized_volume), + description = stringResource(R.string.personalized_volume_description), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, + independent = false + ) HorizontalDivider( thickness = 1.5.dp, color = Color(0x40888888), @@ -76,7 +82,12 @@ fun AudioSettings() { .padding(start = 12.dp, end = 0.dp) ) - ConversationalAwarenessSwitch() + StyledToggle( + label = stringResource(R.string.conversational_awareness), + description = stringResource(R.string.conversational_awareness_description), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG, + independent = false + ) HorizontalDivider( thickness = 1.5.dp, color = Color(0x40888888), @@ -92,39 +103,17 @@ fun AudioSettings() { .padding(start = 12.dp, end = 0.dp) ) - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 10.dp) - ) { - Text( - text = stringResource(R.string.adaptive_audio), - modifier = Modifier - .padding(end = 8.dp, bottom = 2.dp, start = 2.dp) - .fillMaxWidth(), - style = TextStyle( - fontSize = 16.sp, - color = textColor - ) - ) - Text( - text = stringResource(R.string.adaptive_audio_description), - modifier = Modifier - .padding(bottom = 8.dp, top = 2.dp) - .padding(end = 2.dp, start = 2.dp) - .fillMaxWidth(), - style = TextStyle( - fontSize = 12.sp, - color = textColor.copy(alpha = 0.6f) - ) - ) - AdaptiveStrengthSlider() - } + NavigationButton( + to = "adaptive_strength", + name = stringResource(R.string.adaptive_audio), + navController = navController, + independent = false + ) } } @Preview @Composable fun AudioSettingsPreview() { - AudioSettings() + AudioSettings(rememberNavController()) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt index 994da455..04a9adb9 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.content.Context.MODE_PRIVATE import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures @@ -35,8 +36,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -45,7 +46,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext -import android.content.Context.MODE_PRIVATE import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -59,9 +59,9 @@ import kotlin.io.encoding.ExperimentalEncodingApi fun AutomaticConnectionSwitch() { val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) val service = ServiceManager.getService()!! - - val shared_preference_key = "automatic_connection_ctrl_cmd" - + + val sharedPreferenceKey = "automatic_connection_ctrl_cmd" + val automaticConnectionEnabledValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG }?.value?.takeIf { it.isNotEmpty() }?.get(0) @@ -71,7 +71,7 @@ fun AutomaticConnectionSwitch() { if (automaticConnectionEnabledValue != null) { automaticConnectionEnabledValue == 1.toByte() } else { - sharedPreferences.getBoolean(shared_preference_key, false) + sharedPreferences.getBoolean(sharedPreferenceKey, false) } ) } @@ -83,9 +83,9 @@ fun AutomaticConnectionSwitch() { enabled ) // todo: send other connected devices smartAudioRoutingDisabled or something, check packets again. - + sharedPreferences.edit() - .putBoolean(shared_preference_key, enabled) + .putBoolean(sharedPreferenceKey, enabled) .apply() } @@ -95,14 +95,14 @@ fun AutomaticConnectionSwitch() { val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) val enabled = newValue == 1.toByte() automaticConnectionEnabled = enabled - + sharedPreferences.edit() - .putBoolean(shared_preference_key, enabled) + .putBoolean(sharedPreferenceKey, enabled) .apply() } } } - + LaunchedEffect(Unit) { service.aacpManager.registerControlCommandListener( AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt index 130f71af..8aba9a0e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryIndicator.kt @@ -1,17 +1,17 @@ /* * LibrePods - AirPods liberated from Apple’s ecosystem - * + * * Copyright (C) 2025 LibrePods contributors - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -19,31 +19,28 @@ package me.kavishdevar.librepods.composables -import androidx.compose.animation.core.animateFloatAsState +import android.content.res.Configuration +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.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.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.LaunchedEffect +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.StrokeCap 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.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -51,85 +48,78 @@ import androidx.compose.ui.unit.sp import me.kavishdevar.librepods.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 +fun BatteryIndicator( + batteryPercentage: Int, + charging: Boolean = false, + prefix: String = "", + previousCharging: Boolean = false, +) { + val isDarkTheme = isSystemInDarkTheme() + val backgroundColor = if (isDarkTheme) Color.Black else Color(0xFFF2F2F7) + val batteryTextColor = if (isDarkTheme) Color.White else Color.Black + val batteryFillColor = if (batteryPercentage > 25) + if (isDarkTheme) Color(0xFF2ED158) else Color(0xFF35C759) + else if (isDarkTheme) Color(0xFFFC4244) else Color(0xFFfe373C) - val batteryWidth = 40.dp - val batteryHeight = 15.dp - val batteryCornerRadius = 4.dp - val tipWidth = 5.dp - val tipHeight = batteryHeight * 0.375f + val initialScale = if (previousCharging) 1f else 0f + val scaleAnim = remember { Animatable(initialScale) } + val targetScale = if (charging) 1f else 0f - val animatedFillWidth by animateFloatAsState(targetValue = batteryPercentage / 100f) - val animatedScale by animateFloatAsState(targetValue = if (charging) 1.2f else 1f) + LaunchedEffect(previousCharging, charging) { + scaleAnim.animateTo(targetScale, animationSpec = tween(durationMillis = 250)) + } Column( + modifier = Modifier + .padding(12.dp) + .background(backgroundColor), // just for haze to work horizontalAlignment = Alignment.CenterHorizontally ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(0.dp), - modifier = Modifier.padding(bottom = 4.dp) + Box( + modifier = Modifier.padding(bottom = 4.dp), + contentAlignment = Alignment.Center ) { - Box( - modifier = Modifier - .width(batteryWidth) - .height(batteryHeight) - ) { - Box ( - modifier = Modifier - .fillMaxSize() - .border(1.dp, batteryOutlineColor, RoundedCornerShape(batteryCornerRadius)) - ) - Box( - modifier = Modifier - .fillMaxHeight() - .padding(2.dp) - .width(batteryWidth * animatedFillWidth) - .background(batteryFillColor, RoundedCornerShape(2.dp)) - ) - if (charging) { - Text( - text = "\uDBC0\uDEE6", - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = Color.White, - modifier = Modifier - .scale(animatedScale) - .fillMaxSize(), - textAlign = TextAlign.Center - ) - } - } - 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 - ) - ) + CircularProgressIndicator( + progress = { batteryPercentage / 100f }, + modifier = Modifier.size(40.dp), + color = batteryFillColor, + gapSize = 0.dp, + strokeCap = StrokeCap.Round, + strokeWidth = 2.dp, + trackColor = if (isDarkTheme) Color(0xFF0E0E0F) else Color(0xFFE3E3E8) + ) + + Text( + text = "\uDBC0\uDEE6", + style = TextStyle( + fontSize = 12.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = batteryFillColor, + textAlign = TextAlign.Center + ), + modifier = Modifier.scale(scaleAnim.value) ) } Text( - text = "$batteryPercentage%", + text = "$prefix $batteryPercentage%", color = batteryTextColor, - style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold) + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + textAlign = TextAlign.Center + ), ) } } -@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun BatteryIndicatorPreview() { - BatteryIndicator(batteryPercentage = 48, charging = true) -} \ No newline at end of file + val bg = if (isSystemInDarkTheme()) Color.Black else Color(0xFFF2F2F7) + Box( + modifier = Modifier.background(bg) + ) { + BatteryIndicator(batteryPercentage = 24, charging = true, prefix = "\uDBC6\uDCE5", previousCharging = false) + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt index 4f906628..a993c179 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/BatteryView.kt @@ -24,14 +24,19 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.res.Configuration import android.os.Build import android.util.Log import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -39,7 +44,7 @@ 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.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.imageResource @@ -57,6 +62,9 @@ import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun BatteryView(service: AirPodsService, preview: Boolean = false) { val batteryStatus = remember { mutableStateOf>(listOf()) } + + val previousBatteryStatus = remember { mutableStateOf>(listOf()) } + @Suppress("DEPRECATION") val batteryReceiver = remember { object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -96,16 +104,37 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { } } + previousBatteryStatus.value = batteryStatus.value batteryStatus.value = service.getBattery() if (preview) { batteryStatus.value = listOf( - Battery(BatteryComponent.LEFT, 100, BatteryStatus.CHARGING), - Battery(BatteryComponent.RIGHT, 50, BatteryStatus.NOT_CHARGING), - Battery(BatteryComponent.CASE, 5, BatteryStatus.CHARGING) + Battery(BatteryComponent.LEFT, 100, BatteryStatus.NOT_CHARGING), + Battery(BatteryComponent.RIGHT, 94, BatteryStatus.NOT_CHARGING), + Battery(BatteryComponent.CASE, 40, BatteryStatus.CHARGING) ) + previousBatteryStatus.value = batteryStatus.value } + val left = batteryStatus.value.find { it.component == BatteryComponent.LEFT } + val right = batteryStatus.value.find { it.component == BatteryComponent.RIGHT } + val case = batteryStatus.value.find { it.component == BatteryComponent.CASE } + val leftLevel = left?.level ?: 0 + val rightLevel = right?.level ?: 0 + val caseLevel = case?.level ?: 0 + val leftCharging = left?.status == BatteryStatus.CHARGING + val rightCharging = right?.status == BatteryStatus.CHARGING + val caseCharging = case?.status == BatteryStatus.CHARGING + + val prevLeft = previousBatteryStatus.value.find { it.component == BatteryComponent.LEFT } + val prevRight = previousBatteryStatus.value.find { it.component == BatteryComponent.RIGHT } + val prevCase = previousBatteryStatus.value.find { it.component == BatteryComponent.CASE } + val prevLeftCharging = prevLeft?.status == BatteryStatus.CHARGING + val prevRightCharging = prevRight?.status == BatteryStatus.CHARGING + val prevCaseCharging = prevCase?.status == BatteryStatus.CHARGING + + val singleDisplayed = remember { mutableStateOf(false) } + Row { Column ( modifier = Modifier @@ -117,43 +146,48 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { contentDescription = stringResource(R.string.buds), modifier = Modifier .fillMaxWidth() - .scale(0.80f) + .padding(12.dp) + ) + if ( + leftCharging == rightCharging && + (leftLevel - rightLevel) in -3..3 ) - 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) + BatteryIndicator( + leftLevel.coerceAtMost(rightLevel), + leftCharging, + previousCharging = (prevLeftCharging && prevRightCharging) + ) + singleDisplayed.value = true } else { + singleDisplayed.value = false Row ( modifier = Modifier .fillMaxWidth(), horizontalArrangement = Arrangement.Center ) { -// if (left?.status != BatteryStatus.DISCONNECTED) { - if (left?.level != null) { + if (leftLevel > 0 || left?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator( - left.level, - left.status == BatteryStatus.CHARGING + leftLevel, + leftCharging, + "\uDBC6\uDCE5", + previousCharging = prevLeftCharging ) } -// } -// if (left?.status != BatteryStatus.DISCONNECTED && right?.status != BatteryStatus.DISCONNECTED) { - if (left?.level != null && right?.level != null) + if (leftLevel > 0 && rightLevel > 0) { - Spacer(modifier = Modifier.width(16.dp)) + Spacer(modifier = Modifier.width(4.dp)) } -// } -// if (right?.status != BatteryStatus.DISCONNECTED) { - if (right?.level != null) + if (rightLevel > 0 || right?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator( - right.level, - right.status == BatteryStatus.CHARGING + rightLevel, + rightCharging, + "\uDBC6\uDCE8", + previousCharging = prevRightCharging ) } -// } } } } @@ -163,26 +197,32 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) { .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { - val case = batteryStatus.value.find { it.component == BatteryComponent.CASE } - Image( bitmap = ImageBitmap.imageResource(R.drawable.pro_2_case), contentDescription = stringResource(R.string.case_alt), modifier = Modifier .fillMaxWidth() - .scale(1.25f) + .padding(12.dp) ) -// if (case?.status != BatteryStatus.DISCONNECTED) { - if (case?.level != null) { - BatteryIndicator(case.level, case.status == BatteryStatus.CHARGING) + if (caseLevel > 0 || case?.status != BatteryStatus.DISCONNECTED) { + BatteryIndicator( + caseLevel, + caseCharging, + prefix = if (!singleDisplayed.value) "\uDBC3\uDE6C" else "", + previousCharging = prevCaseCharging + ) } -// } } } } -@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun BatteryViewPreview() { - BatteryView(AirPodsService(), preview = true) + val bg = if (isSystemInDarkTheme()) Color.Black else Color(0xFFF2F2F7) + Box( + modifier = Modifier.background(bg) + ) { + BatteryView(AirPodsService(), preview = true) + } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index c3310c5c..18139ec4 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -1,7 +1,36 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package me.kavishdevar.librepods.composables -import androidx.compose.foundation.background +import android.graphics.RuntimeShader +import android.os.Build +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.VisibilityThreshold +import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut 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 @@ -17,72 +46,207 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.pointerInput 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.compose.ui.util.fastCoerceAtMost +import androidx.compose.ui.util.fastCoerceIn +import androidx.compose.ui.util.lerp import com.kyant.backdrop.Backdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.blur -import com.kyant.backdrop.effects.colorFilter +import com.kyant.backdrop.effects.colorControls import com.kyant.backdrop.effects.refraction +import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.highlight.HighlightStyle +import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.utils.inspectDragGestures +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.tanh @Composable fun ConfirmationDialog( showDialog: MutableState, title: String, message: String, - confirmText: String = "Enable", + confirmText: String = "Ok", dismissText: String = "Cancel", onConfirm: () -> Unit, onDismiss: () -> Unit = { showDialog.value = false }, backdrop: Backdrop, ) { - if (showDialog.value) { + AnimatedVisibility( + visible = showDialog.value, + enter = fadeIn() + scaleIn(initialScale = 1.25f), + exit = fadeOut() + scaleOut(targetScale = 0.9f) + ) { + val animationScope = rememberCoroutineScope() + val progressAnimation = remember { Animatable(0f) } + var pressStartPosition by remember { mutableStateOf(Offset.Zero) } + val offsetAnimation = remember { Animatable(Offset.Zero, Offset.VectorConverter) } + + val interactiveHighlightShader = remember { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + RuntimeShader( + """ +uniform float2 size; +layout(color) uniform half4 color; +uniform float radius; +uniform float2 offset; + +half4 main(float2 coord) { + float2 center = offset; + float dist = distance(coord, center); + float intensity = smoothstep(radius, radius * 0.5, dist); + return color * intensity; +}""" + ) + } else { + null + } + } + val isLightTheme = !isSystemInDarkTheme() val contentColor = if (isLightTheme) Color.Black else Color.White val accentColor = if (isLightTheme) Color(0xFF0088FF) else Color(0xFF0091FF) - val containerColor = if (isLightTheme) Color(0xFFFAFAFA).copy(0.6f) else Color(0xFF121212).copy(0.4f) - val dimColor = if (isLightTheme) Color(0xFF29293A).copy(0.23f) else Color(0xFF121212).copy(0.56f) + val containerColor = if (isLightTheme) Color(0xFFFFFFFF).copy(0.6f) else Color(0xFF101010).copy(0.6f) Box( Modifier - .background(dimColor) .fillMaxSize() - .clickable(onClick = onDismiss) + .clickable(onClick = onDismiss, indication = null, interactionSource = remember { MutableInteractionSource() } ) ) { Box( Modifier .align(Alignment.Center) + .clickable(onClick = {}, indication = null, interactionSource = remember { MutableInteractionSource() } ) .drawBackdrop( backdrop, { RoundedCornerShape(48f.dp) }, -// highlight = { Highlight { HighlightStyle.Solid } }, - onDrawSurface = { drawRect(containerColor) } - ) { - colorFilter( - brightness = if (isLightTheme) 0.2f else 0.1f, - saturation = 1.5f - ) - blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) - refraction(24f.dp.toPx(), 48f.dp.toPx(), true) - } + highlight = { Highlight { HighlightStyle.Solid } }, + onDrawSurface = { drawRect(containerColor) }, + effects = { + colorControls( + brightness = if (isLightTheme) 0.4f else 0.2f, + saturation = 1.5f + ) + blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) + refraction(24f.dp.toPx(), 48f.dp.toPx(), true) + }, + layer = { + val width = size.width + val height = size.height + + val progress = progressAnimation.value + val maxScale = 0f + val scale = lerp(1f, 1f + maxScale, progress) + + val maxOffset = size.minDimension + val initialDerivative = 0.05f + val offset = offsetAnimation.value + translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) + translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) + + val maxDragScale = 0.1f + val offsetAngle = atan2(offset.y, offset.x) + scaleX = + scale + + maxDragScale * abs(cos(offsetAngle) * offset.x / size.maxDimension) * + (width / height).fastCoerceAtMost(1f) + scaleY = + scale + + maxDragScale * abs(sin(offsetAngle) * offset.y / size.maxDimension) * + (height / width).fastCoerceAtMost(1f) + }, + onDrawFront = { + val progress = progressAnimation.value.fastCoerceIn(0f, 1f) + if (progress > 0f) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader != null) { + drawRect( + Color.White.copy(0.05f * progress), + blendMode = BlendMode.Plus + ) + interactiveHighlightShader.apply { + val offset = pressStartPosition + offsetAnimation.value + setFloatUniform("size", size.width, size.height) + setColorUniform("color", Color.White.copy(0.075f * progress).toArgb()) + setFloatUniform("radius", size.maxDimension / 2) + setFloatUniform( + "offset", + offset.x.fastCoerceIn(0f, size.width), + offset.y.fastCoerceIn(0f, size.height) + ) + } + drawRect( + ShaderBrush(interactiveHighlightShader), + blendMode = BlendMode.Plus + ) + } else { + drawRect( + Color.White.copy(0.125f * progress), + blendMode = BlendMode.Plus + ) + } + } + }, + contentEffects = { + refraction(8f.dp.toPx(), 24f.dp.toPx(), false) + } + ) .fillMaxWidth(0.75f) .requiredWidthIn(min = 200.dp, max = 360.dp) + .pointerInput(animationScope) { + val progressAnimationSpec = spring(0.5f, 300f, 0.001f) + val offsetAnimationSpec = spring(1f, 300f, Offset.VisibilityThreshold) + val onDragStop: () -> Unit = { + animationScope.launch { + launch { progressAnimation.animateTo(0f, progressAnimationSpec) } + launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) } + } + } + inspectDragGestures( + onDragStart = { down -> + pressStartPosition = down.position + animationScope.launch { + launch { progressAnimation.animateTo(1f, progressAnimationSpec) } + launch { offsetAnimation.snapTo(Offset.Zero) } + } + }, + onDragEnd = { onDragStop() }, + onDragCancel = onDragStop + ) { _, dragAmount -> + animationScope.launch { + offsetAnimation.snapTo(offsetAnimation.value + dragAmount) + } + } + } ) { Column(horizontalAlignment = Alignment.Start) { Spacer(modifier = Modifier.height(28.dp)) Text( title, style = TextStyle( - fontSize = 16.sp, + fontSize = 18.sp, fontWeight = FontWeight.Bold, color = contentColor, fontFamily = FontFamily(Font(R.font.sf_pro)) @@ -103,35 +267,50 @@ fun ConfirmationDialog( Row( Modifier - .padding(24.dp, 12.dp, 24.dp, 24.dp) + .padding(horizontal = 12.dp) + .padding(top = 12.dp, bottom = 24.dp) .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { - Box( - Modifier - .clip(RoundedCornerShape(50.dp)) - .background(containerColor.copy(0.2f)) - .clickable(onClick = onDismiss) - .height(48.dp) - .weight(1f) - .padding(horizontal = 16.dp), - contentAlignment = Alignment.Center + // Box( + // Modifier + // .clip(RoundedCornerShape(50.dp)) + // .background(containerColor.copy(0.2f)) + // .clickable(onClick = onDismiss) + // .height(48.dp) + // .weight(1f) + // .padding(horizontal = 16.dp), + // contentAlignment = Alignment.Center + // ) { + StyledButton( + onClick = onDismiss, + backdrop = backdrop, + surfaceColor = if (isLightTheme) Color(0xFFAAAAAA).copy(0.8f) else Color(0xFF202020).copy(0.8f), + modifier = Modifier.weight(1f), + isInteractive = false ) { Text( dismissText, style = TextStyle(contentColor, 16.sp) ) } - Box( - Modifier - .clip(RoundedCornerShape(50.dp)) - .background(accentColor) - .clickable(onClick = onConfirm) - .height(48.dp) - .weight(1f) - .padding(horizontal = 16.dp), - contentAlignment = Alignment.Center + // Box( + // Modifier + // .clip(RoundedCornerShape(50.dp)) + // .background(accentColor) + // .clickable(onClick = onConfirm) + // .height(48.dp) + // .weight(1f) + // .padding(horizontal = 16.dp), + // contentAlignment = Alignment.Center + // ) { + StyledButton( + onClick = onConfirm, + backdrop = backdrop, + surfaceColor = accentColor, + modifier = Modifier.weight(1f), + isInteractive = false ) { Text( confirmText, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt index 28f796e6..d0e386cb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt @@ -20,34 +20,23 @@ package me.kavishdevar.librepods.composables -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -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 me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun ConnectionSettings() { val isDarkTheme = isSystemInDarkTheme() - val textColor = if (isDarkTheme) Color.White else Color.Black val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Column( @@ -72,4 +61,4 @@ fun ConnectionSettings() { @Composable fun ConnectionSettingsPreview() { ConnectionSettings() -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt deleted file mode 100644 index 7492a629..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConversationalAwarenessSwitch.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -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.isSystemInDarkTheme -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun ConversationalAwarenessSwitch() { - val service = ServiceManager.getService()!! - val conversationEnabledValue = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var conversationalAwarenessEnabled by remember { - mutableStateOf( - conversationEnabledValue == 1.toByte() - ) - } - - fun updateConversationalAwareness(enabled: Boolean) { - conversationalAwarenessEnabled = enabled - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG.value, - enabled - ) - } - - val conversationalAwarenessListener = object: AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - conversationalAwarenessEnabled = newValue == 1.toByte() - } - } - } - - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG, - conversationalAwarenessListener - ) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.CONVERSATION_DETECT_CONFIG, - conversationalAwarenessListener - ) - } - } - - val isDarkTheme = isSystemInDarkTheme() - 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() } - ) { - updateConversationalAwareness(!conversationalAwarenessEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.conversational_awareness), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.conversational_awareness_description), - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, - ) - } - StyledSwitch( - checked = conversationalAwarenessEnabled, - onCheckedChange = { - updateConversationalAwareness(it) - }, - ) - } -} - -@Preview -@Composable -fun ConversationalAwarenessSwitchPreview() { - ConversationalAwarenessSwitch() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt index 37ef7c83..52eafc1f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.content.Context.MODE_PRIVATE import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures @@ -27,16 +28,14 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -49,8 +48,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import android.content.Context.MODE_PRIVATE -import android.content.SharedPreferences import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager @@ -60,8 +57,8 @@ import kotlin.io.encoding.ExperimentalEncodingApi fun EarDetectionSwitch() { val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) val service = ServiceManager.getService()!! - - val shared_preference_key = "automatic_ear_detection" + + val sharedPreferenceKey = "automatic_ear_detection" val earDetectionEnabledValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG @@ -72,7 +69,7 @@ fun EarDetectionSwitch() { if (earDetectionEnabledValue != null) { earDetectionEnabledValue == 1.toByte() } else { - sharedPreferences.getBoolean(shared_preference_key, false) + sharedPreferences.getBoolean(sharedPreferenceKey, false) } ) } @@ -84,9 +81,9 @@ fun EarDetectionSwitch() { enabled ) service.setEarDetection(enabled) - + sharedPreferences.edit() - .putBoolean(shared_preference_key, enabled) + .putBoolean(sharedPreferenceKey, enabled) .apply() } @@ -96,14 +93,14 @@ fun EarDetectionSwitch() { val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) val enabled = newValue == 1.toByte() earDetectionEnabled = enabled - + sharedPreferences.edit() - .putBoolean(shared_preference_key, enabled) + .putBoolean(sharedPreferenceKey, enabled) .apply() } } } - + LaunchedEffect(Unit) { service.aacpManager.registerControlCommandListener( AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt deleted file mode 100644 index 25e142cc..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/IndependentToggle.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -import android.content.SharedPreferences -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Arrangement -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.services.AirPodsService -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi -import androidx.core.content.edit -import android.util.Log - -@Composable -fun IndependentToggle(name: String, service: AirPodsService? = null, functionName: String? = null, sharedPreferences: SharedPreferences, default: Boolean = false, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers? = null, description: String? = null) { - val isDarkTheme = isSystemInDarkTheme() - val textColor = if (isDarkTheme) Color.White else Color.Black - val snakeCasedName = - controlCommandIdentifier?.name ?: name.replace(Regex("[\\W\\s]+"), "_").lowercase() - var checked by remember { mutableStateOf(default) } - - if (controlCommandIdentifier != null) { - checked = service!!.aacpManager.controlCommandStatusList.find { - it.identifier == controlCommandIdentifier - }?.value?.takeIf { it.isNotEmpty() }?.get(0) == 1.toByte() - } - - var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - - fun cb() { - if (controlCommandIdentifier == null) { - sharedPreferences.edit { putBoolean(snakeCasedName, checked) } - } - if (functionName != null && service != null) { - val method = - service::class.java.getMethod(functionName, Boolean::class.java) - method.invoke(service, checked) - } - if (controlCommandIdentifier != null) { - service?.aacpManager?.sendControlCommand(identifier = controlCommandIdentifier.value, value = checked) - } - } - - LaunchedEffect(sharedPreferences) { - checked = sharedPreferences.getBoolean(snakeCasedName, true) - } - - if (controlCommandIdentifier != null) { - val listener = remember { - object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == controlCommandIdentifier.value) { - Log.d("IndependentToggle", "Received control command for $name: ${controlCommand.value}") - checked = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) == 1.toByte() - } - } - } - } - LaunchedEffect(Unit) { - service?.aacpManager?.registerControlCommandListener(controlCommandIdentifier, listener) - } - DisposableEffect(Unit) { - onDispose { - service?.aacpManager?.unregisterControlCommandListener(controlCommandIdentifier, listener) - } - } - } - Column ( - modifier = Modifier - .padding(vertical = 8.dp), - ) { - Box ( - modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - checked = !checked - cb() - } - ) - }, - ) - { - Row( - modifier = Modifier - .fillMaxWidth() - .height(55.dp) - .padding(horizontal = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = name, - modifier = Modifier.weight(1f), - style = TextStyle( - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Normal, - color = textColor - ) - ) - StyledSwitch( - checked = checked, - onCheckedChange = { - checked = it - cb() - }, - ) - } - } - if (description != null) { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = description, - style = TextStyle( - fontSize = 12.sp, - fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier - .padding(horizontal = 8.dp) - ) - } - } -} - -@Preview -@Composable -fun IndependentTogglePreview() { - IndependentToggle("Test", AirPodsService(), "test", LocalContext.current.getSharedPreferences("preview", 0), true) -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt index ba5f6fdb..5d319634 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt @@ -51,7 +51,6 @@ import androidx.compose.ui.unit.sp import kotlinx.coroutines.delay import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.ATTHandles import kotlin.io.encoding.ExperimentalEncodingApi @@ -62,7 +61,7 @@ fun LoudSoundReductionSwitch() { false ) } - val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") + val attManager = ServiceManager.getService()?.attManager ?: return LaunchedEffect(Unit) { attManager.enableNotifications(ATTHandles.LOUD_SOUND_REDUCTION) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt index b87d74fa..4f88af07 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -20,19 +20,10 @@ package me.kavishdevar.librepods.composables -import android.annotation.SuppressLint import android.util.Log -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -42,15 +33,9 @@ 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.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -63,7 +48,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput @@ -71,20 +55,10 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.window.Popup -import dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.HazeTint -import dev.chrisbanes.haze.hazeEffect -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt index 3b4bbf30..0baa894f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt @@ -50,14 +50,14 @@ import androidx.navigation.NavController @Composable -fun NavigationButton(to: String, name: String, navController: NavController, onClick: (() -> Unit)? = null) { +fun NavigationButton(to: String, name: String, navController: NavController, onClick: (() -> Unit)? = null, independent: Boolean = true) { val isDarkTheme = isSystemInDarkTheme() var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) Row( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(if (independent) 14.dp else 0.dp)) .height(55.dp) .pointerInput(Unit) { detectTapGestures( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt index afb17964..648e610b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt @@ -189,6 +189,7 @@ fun NoiseControlSettings( ), modifier = Modifier.padding(8.dp, bottom = 2.dp) ) + @Suppress("COMPOSE_APPLIER_CALL_MISMATCH") BoxWithConstraints( modifier = Modifier .fillMaxWidth() @@ -437,7 +438,7 @@ fun NoiseControlSettings( } } -@Preview() +@Preview @Composable fun NoiseControlSettingsPreview() { NoiseControlSettings(AirPodsService()) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt index 14bb876f..ef72c926 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt @@ -1,4 +1,4 @@ -/* + /* * LibrePods - AirPods liberated from Apple’s ecosystem * * Copyright (C) 2025 LibrePods contributors @@ -35,8 +35,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -53,10 +53,10 @@ import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi -@Composable + @Composable fun PersonalizedVolumeSwitch() { val service = ServiceManager.getService()!! - + val adaptiveVolumeEnabledValue = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG }?.value?.takeIf { it.isNotEmpty() }?.get(0) @@ -83,7 +83,7 @@ fun PersonalizedVolumeSwitch() { } } } - + LaunchedEffect(Unit) { service.aacpManager.registerControlCommandListener( AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt new file mode 100644 index 00000000..ce683722 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt @@ -0,0 +1,284 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.composables + +import android.graphics.RuntimeShader +import android.os.Build +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.VisibilityThreshold +import androidx.compose.animation.core.spring +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtMost +import androidx.compose.ui.util.fastCoerceIn +import androidx.compose.ui.util.lerp +import com.kyant.backdrop.Backdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur +import com.kyant.backdrop.effects.refraction +import com.kyant.backdrop.effects.vibrancy +import com.kyant.backdrop.highlight.Highlight +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.utils.inspectDragGestures +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.tanh + +@Composable +fun StyledButton( + onClick: () -> Unit, + backdrop: Backdrop, + modifier: Modifier = Modifier, + isInteractive: Boolean = true, + tint: Color = Color.Unspecified, + surfaceColor: Color = Color.Unspecified, + content: @Composable RowScope.() -> Unit +) { + val animationScope = rememberCoroutineScope() + val progressAnimation = remember { Animatable(0f) } + var pressStartPosition by remember { mutableStateOf(Offset.Zero) } + val offsetAnimation = remember { Animatable(Offset.Zero, Offset.VectorConverter) } + var isPressed by remember { mutableStateOf(false) } + + val interactiveHighlightShader = remember { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + RuntimeShader( + """ +uniform float2 size; +layout(color) uniform half4 color; +uniform float radius; +uniform float2 offset; + +half4 main(float2 coord) { + float2 center = offset; + float dist = distance(coord, center); + float intensity = smoothstep(radius, radius * 0.5, dist); + return color * intensity; +}""" + ) + } else { + null + } + } + + Row( + modifier + .then( + if (!isInteractive) { + Modifier.drawBackdrop( + backdrop = backdrop, + shape = { RoundedCornerShape(28f.dp) }, + effects = { + blur(16f.dp.toPx()) + }, + layer = null, + onDrawSurface = { + if (tint.isSpecified) { + drawRect(tint, blendMode = BlendMode.Hue) + drawRect(tint.copy(alpha = 0.75f)) + } else { + drawRect(Color.White.copy(0.1f)) + } + if (surfaceColor.isSpecified) { + val color = if (!isInteractive && isPressed) { + Color( + red = surfaceColor.red * 0.5f, + green = surfaceColor.green * 0.5f, + blue = surfaceColor.blue * 0.5f, + alpha = surfaceColor.alpha + ) + } else { + surfaceColor + } + drawRect(color) + } + }, + onDrawFront = null, + highlight = { Highlight.AmbientDefault.copy(alpha = 0f) } + ) + } else { + Modifier.drawBackdrop( + backdrop = backdrop, + shape = { RoundedCornerShape(28f.dp) }, + effects = { + vibrancy() + blur(2f.dp.toPx()) + refraction(12f.dp.toPx(), 24f.dp.toPx()) + }, + layer = { + val width = size.width + val height = size.height + + val progress = progressAnimation.value + val maxScale = 0.1f + val scale = lerp(1f, 1f + maxScale, progress) + + val maxOffset = size.minDimension + val initialDerivative = 0.05f + val offset = offsetAnimation.value + translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) + translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) + + val maxDragScale = 0.1f + val offsetAngle = atan2(offset.y, offset.x) + scaleX = + scale + + maxDragScale * abs(cos(offsetAngle) * offset.x / size.maxDimension) * + (width / height).fastCoerceAtMost(1f) + scaleY = + scale + + maxDragScale * abs(sin(offsetAngle) * offset.y / size.maxDimension) * + (height / width).fastCoerceAtMost(1f) + }, + onDrawSurface = { + if (tint.isSpecified) { + drawRect(tint, blendMode = BlendMode.Hue) + drawRect(tint.copy(alpha = 0.75f)) + } else { + drawRect(Color.White.copy(0.1f)) + } + if (surfaceColor.isSpecified) { + val color = if (!isInteractive && isPressed) { + Color( + red = surfaceColor.red * 0.5f, + green = surfaceColor.green * 0.5f, + blue = surfaceColor.blue * 0.5f, + alpha = surfaceColor.alpha + ) + } else { + surfaceColor + } + drawRect(color) + } + }, + onDrawFront = { + val progress = progressAnimation.value.fastCoerceIn(0f, 1f) + if (progress > 0f) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader != null) { + drawRect( + Color.White.copy(0.1f * progress), + blendMode = BlendMode.Plus + ) + interactiveHighlightShader.apply { + val offset = pressStartPosition + offsetAnimation.value + setFloatUniform("size", size.width, size.height) + setColorUniform("color", Color.White.copy(0.15f * progress).toArgb()) + setFloatUniform("radius", size.maxDimension) + setFloatUniform( + "offset", + offset.x.fastCoerceIn(0f, size.width), + offset.y.fastCoerceIn(0f, size.height) + ) + } + drawRect( + ShaderBrush(interactiveHighlightShader), + blendMode = BlendMode.Plus + ) + } else { + drawRect( + Color.White.copy(0.25f * progress), + blendMode = BlendMode.Plus + ) + } + } + } + ) + } + ) + .clickable( + interactionSource = null, + indication = null, + role = Role.Button, + onClick = onClick + ) + .then( + if (isInteractive) { + Modifier.pointerInput(animationScope) { + val progressAnimationSpec = spring(0.5f, 300f, 0.001f) + val offsetAnimationSpec = spring(1f, 300f, Offset.VisibilityThreshold) + val onDragStop: () -> Unit = { + animationScope.launch { + launch { progressAnimation.animateTo(0f, progressAnimationSpec) } + launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) } + } + } + inspectDragGestures( + onDragStart = { down -> + pressStartPosition = down.position + animationScope.launch { + launch { progressAnimation.animateTo(1f, progressAnimationSpec) } + launch { offsetAnimation.snapTo(Offset.Zero) } + } + }, + onDragEnd = { onDragStop() }, + onDragCancel = onDragStop + ) { _, dragAmount -> + animationScope.launch { + offsetAnimation.snapTo(offsetAnimation.value + dragAmount) + } + } + } + } else { + Modifier.pointerInput(Unit) { + detectTapGestures( + onPress = { + isPressed = true + tryAwaitRelease() + isPressed = false + }, + onTap = { + onClick() + } + ) + } + } + ) + .height(48f.dp) + .padding(horizontal = 16f.dp), + horizontalArrangement = Arrangement.spacedBy(8f.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + content = content + ) +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt new file mode 100644 index 00000000..4e941934 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt @@ -0,0 +1,258 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.composables + +import android.graphics.RuntimeShader +import android.os.Build +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.animation.core.VisibilityThreshold +import androidx.compose.animation.core.spring +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.BlurEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.isSpecified +import androidx.compose.ui.graphics.layer.CompositingStrategy +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.pointerInput +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.compose.ui.util.fastCoerceAtMost +import androidx.compose.ui.util.fastCoerceIn +import androidx.compose.ui.util.lerp +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.refractionWithDispersion +import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.shadow.Shadow +import kotlinx.coroutines.launch +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.utils.inspectDragGestures +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.tanh + +@Composable +fun StyledIconButton( + onClick: () -> Unit, + icon: String, + darkMode: Boolean, + tint: Color = Color.Unspecified, +) { + val animationScope = rememberCoroutineScope() + val progressAnimationSpec = spring(0.5f, 300f, 0.001f) + val offsetAnimationSpec = spring(1f, 300f, Offset.VisibilityThreshold) + val progressAnimation = remember { Animatable(0f) } + val offsetAnimation = remember { Animatable(Offset.Zero, Offset.VectorConverter) } + var pressStartPosition by remember { mutableStateOf(Offset.Zero) } + val innerShadowLayer = rememberGraphicsLayer().apply { + compositingStrategy = CompositingStrategy.Offscreen + } + + val interactiveHighlightShader = remember { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + RuntimeShader( + """ +uniform float2 size; +layout(color) uniform half4 color; +uniform float radius; +uniform float2 offset; + +half4 main(float2 coord) { + float2 center = offset; + float dist = distance(coord, center); + float intensity = smoothstep(radius, radius * 0.5, dist); + return color * intensity; +}""" + ) + } else { + null + } + } + + TextButton( + onClick = onClick, + shape = RoundedCornerShape(56.dp), + modifier = Modifier + .padding(horizontal = 12.dp) + .drawBackdrop( + backdrop = rememberLayerBackdrop(), + shape = { RoundedCornerShape(56.dp) }, + highlight = { + val progress = progressAnimation.value + Highlight.AmbientDefault.copy(alpha = progress.coerceIn(0.45f, 1f)) + }, + shadow = { + Shadow( + radius = 4f.dp, + color = Color.Black.copy(0.08f) + ) + }, + layer = { + val width = size.width + val height = size.height + + val progress = progressAnimation.value + val maxScale = 0.1f + val scale = lerp(1f, 1f + maxScale, progress) + + val maxOffset = size.minDimension + val initialDerivative = 0.05f + val offset = offsetAnimation.value + translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) + translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) + + val maxDragScale = 0.1f + val offsetAngle = atan2(offset.y, offset.x) + scaleX = + scale + + maxDragScale * abs(cos(offsetAngle) * offset.x / size.maxDimension) * + (width / height).fastCoerceAtMost(1f) + scaleY = + scale + + maxDragScale * abs(sin(offsetAngle) * offset.y / size.maxDimension) * + (height / width).fastCoerceAtMost(1f) + }, + onDrawSurface = { + val progress = progressAnimation.value.coerceIn(0f, 1f) + + val shape = RoundedCornerShape(56.dp) + val outline = shape.createOutline(size, layoutDirection, this) + val innerShadowOffset = 4f.dp.toPx() + val innerShadowBlurRadius = 4f.dp.toPx() + + innerShadowLayer.alpha = progress + innerShadowLayer.renderEffect = + BlurEffect( + innerShadowBlurRadius, + innerShadowBlurRadius, + TileMode.Decal + ) + innerShadowLayer.record { + drawOutline(outline, Color.Black.copy(0.2f)) + translate(0f, innerShadowOffset) { + drawOutline( + outline, + Color.Transparent, + blendMode = BlendMode.Clear + ) + } + } + drawLayer(innerShadowLayer) + + drawRect( + Color.White.copy(progress.coerceIn(0.15f, 0.35f)) + ) + }, + onDrawFront = { + val progress = progressAnimation.value.fastCoerceIn(0f, 1f) + if (progress > 0f) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader != null) { + drawRect( + Color.White.copy(0.1f * progress), + blendMode = BlendMode.Plus + ) + interactiveHighlightShader.apply { + val offset = pressStartPosition + offsetAnimation.value + setFloatUniform("size", size.width, size.height) + setColorUniform("color", Color.White.copy(0.15f * progress).toArgb()) + setFloatUniform("radius", size.maxDimension) + setFloatUniform( + "offset", + offset.x.fastCoerceIn(0f, size.width), + offset.y.fastCoerceIn(0f, size.height) + ) + } + drawRect( + ShaderBrush(interactiveHighlightShader), + blendMode = BlendMode.Plus + ) + } else { + drawRect( + Color.White.copy(0.25f * progress), + blendMode = BlendMode.Plus + ) + } + } + }, + effects = { + refractionWithDispersion(6f.dp.toPx(), size.height / 2f) + }, + ) + .pointerInput(animationScope) { + val onDragStop: () -> Unit = { + animationScope.launch { + launch { progressAnimation.animateTo(0f, progressAnimationSpec) } + launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) } + } + } + inspectDragGestures( + onDragStart = { down -> + pressStartPosition = down.position + animationScope.launch { + launch { progressAnimation.animateTo(1f, progressAnimationSpec) } + launch { offsetAnimation.snapTo(Offset.Zero) } + } + }, + onDragEnd = { onDragStop() }, + onDragCancel = onDragStop + ) { _, dragAmount -> + animationScope.launch { + offsetAnimation.snapTo(offsetAnimation.value + dragAmount) + } + } + } + .size(48.dp), + ) { + Text( + text = icon, + style = TextStyle( + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = if (tint.isSpecified) tint else if (darkMode) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt new file mode 100644 index 00000000..acf70850 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt @@ -0,0 +1,166 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.composables + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalLayoutDirection +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.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import dev.chrisbanes.haze.HazeProgressive +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.HazeTint +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import dev.chrisbanes.haze.rememberHazeState +import me.kavishdevar.librepods.R + +@ExperimentalHazeMaterialsApi +@Composable +fun StyledScaffold( + title: String, + navigationButton: @Composable () -> Unit = {}, + actionButtons: List<@Composable () -> Unit> = emptyList(), + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + content: @Composable (spacerValue: Dp, hazeState: HazeState) -> Unit +) { + val isDarkTheme = isSystemInDarkTheme() + val hazeState = rememberHazeState(blurEnabled = true) + + Scaffold( + containerColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7), + snackbarHost = { SnackbarHost(snackbarHostState) } + ) { paddingValues -> + val topPadding = paddingValues.calculateTopPadding() + val bottomPadding = paddingValues.calculateBottomPadding() + val startPadding = paddingValues.calculateLeftPadding(LocalLayoutDirection.current) + val endPadding = paddingValues.calculateRightPadding(LocalLayoutDirection.current) + + Box( + modifier = Modifier + .fillMaxSize() + .padding(start = startPadding, end = endPadding, bottom = bottomPadding) + ) { + Box( + modifier = Modifier + .zIndex(2f) + .height(64.dp + topPadding) + .fillMaxWidth() + .hazeEffect(state = hazeState) { + tints = listOf(HazeTint(color = if (isDarkTheme) Color.Black else Color.White)) + progressive = HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f) + } + ) { + Column(modifier = Modifier.fillMaxSize()) { + Spacer(modifier = Modifier.height(topPadding)) + Box( + modifier = Modifier.fillMaxWidth() + ) { + navigationButton() + Text( + text = title, + style = TextStyle( + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + color = if (isDarkTheme) Color.White else Color.Black, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.align(Alignment.Center), + textAlign = TextAlign.Center + ) + Row( + modifier = Modifier.align(Alignment.CenterEnd) + ) { + actionButtons.forEach { it() } + } + } + } + } + + Box( + modifier = Modifier + .padding(horizontal = 16.dp) + ) { + content(topPadding + 64.dp, hazeState) + } + } + } +} + + +@ExperimentalHazeMaterialsApi +@Composable +fun StyledScaffold( + title: String, + navigationButton: @Composable () -> Unit = {}, + actionButtons: List<@Composable () -> Unit> = emptyList(), + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + content: @Composable () -> Unit +) { + StyledScaffold( + title = title, + navigationButton = navigationButton, + actionButtons = actionButtons, + snackbarHostState = snackbarHostState + ) { _, _ -> + content() + } +} + +@ExperimentalHazeMaterialsApi +@Composable +fun StyledScaffold( + title: String, + navigationButton: @Composable () -> Unit = {}, + actionButtons: List<@Composable () -> Unit> = emptyList(), + snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, + content: @Composable (spacerValue: Dp) -> Unit +) { + StyledScaffold( + title = title, + navigationButton = navigationButton, + actionButtons = actionButtons, + snackbarHostState = snackbarHostState + ) { spacerValue, _ -> + content(spacerValue) + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt index 523eb2c2..b0544cdd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -75,12 +75,12 @@ import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastRoundToInt import androidx.compose.ui.util.lerp import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.backdrop +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberCombinedBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.refractionWithDispersion import com.kyant.backdrop.highlight.Highlight -import com.kyant.backdrop.rememberBackdrop -import com.kyant.backdrop.rememberCombinedBackdropDrawer import com.kyant.backdrop.shadow.Shadow import kotlinx.coroutines.launch import me.kavishdevar.librepods.R @@ -88,18 +88,19 @@ import kotlin.math.roundToInt @Composable fun StyledSlider( - label: String? = null, // New optional parameter for the label + label: String? = null, mutableFloatState: MutableFloatState, onValueChange: (Float) -> Unit, valueRange: ClosedFloatingPointRange, - backdrop: Backdrop = rememberBackdrop(), + backdrop: Backdrop = rememberLayerBackdrop(), snapPoints: List = emptyList(), snapThreshold: Float = 0.05f, startIcon: String? = null, endIcon: String? = null, startLabel: String? = null, endLabel: String? = null, - independent: Boolean = false + independent: Boolean = false, + description: String? = null ) { val backgroundColor = if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val isLightTheme = !isSystemInDarkTheme() @@ -126,7 +127,7 @@ fun StyledSlider( compositingStrategy = CompositingStrategy.Offscreen } - val sliderBackdrop = rememberBackdrop() + val sliderBackdrop = rememberLayerBackdrop() val trackWidthState = remember { mutableFloatStateOf(0f) } val trackPositionState = remember { mutableFloatStateOf(0f) } val startIconWidthState = remember { mutableFloatStateOf(0f) } @@ -137,8 +138,9 @@ fun StyledSlider( Box( Modifier.fillMaxWidth(if (startIcon == null && endIcon == null) 0.95f else 1f) ) { - Box(Modifier - .backdrop(sliderBackdrop) + Box( + Modifier + .layerBackdrop(sliderBackdrop) .fillMaxWidth()) { Column( modifier = Modifier @@ -188,7 +190,7 @@ fun StyledSlider( style = TextStyle( fontSize = 18.sp, fontWeight = FontWeight.Normal, - color = labelTextColor, + color = accentColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier @@ -237,7 +239,7 @@ fun StyledSlider( style = TextStyle( fontSize = 18.sp, fontWeight = FontWeight.Normal, - color = labelTextColor, + color = accentColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier @@ -291,7 +293,7 @@ fun StyledSlider( } ) .drawBackdrop( - rememberCombinedBackdropDrawer(backdrop, sliderBackdrop), + rememberCombinedBackdrop(backdrop, sliderBackdrop), { RoundedCornerShape(28.dp) }, highlight = { val progress = progressAnimation.value @@ -299,8 +301,8 @@ fun StyledSlider( }, shadow = { Shadow( - elevation = 4f.dp, - color = Color.Black.copy(0.08f) + radius = 4f.dp, + color = Color.Black.copy(0.05f) ) }, layer = { @@ -337,10 +339,11 @@ fun StyledSlider( drawLayer(innerShadowLayer) drawRect(Color.White.copy(1f - progress)) + }, + effects = { + refractionWithDispersion(6f.dp.toPx(), size.height / 2f) } - ) { - refractionWithDispersion(6f.dp.toPx(), size.height / 2f) - } + ) .size(40f.dp, 24f.dp) ) } @@ -365,7 +368,7 @@ fun StyledSlider( modifier = Modifier.padding(8.dp) ) } - + Box( modifier = Modifier .fillMaxWidth() @@ -376,9 +379,24 @@ fun StyledSlider( ) { content() } + + if (description != null) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp, vertical = 4.dp) + ) + } } } else { if (label != null) Log.w("StyledSlider", "Label is ignored when independent is false") + if (description != null) Log.w("StyledSlider", "Description is ignored when independent is false") content() } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt new file mode 100644 index 00000000..896c4a3d --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt @@ -0,0 +1,416 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +@file:OptIn(ExperimentalEncodingApi::class) + +package me.kavishdevar.librepods.composables + +import android.content.SharedPreferences +import android.util.Log +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.edit +import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.services.ServiceManager +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi + +@Composable +fun StyledToggle( + title: String? = null, + label: String, + description: String? = null, + checkedState: MutableState = remember { mutableStateOf(false) } , + sharedPreferenceKey: String? = null, + sharedPreferences: SharedPreferences? = null, + independent: Boolean = true +) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + var checked by checkedState + var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) + + fun cb() { + if (sharedPreferences != null) { + if (sharedPreferenceKey == null) { + Log.e("StyledToggle", "SharedPreferenceKey is null but SharedPreferences is provided.") + return + } + sharedPreferences.edit { putBoolean(sharedPreferenceKey, checked) } + } + } + + if (independent) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + if (title != null) { + Text( + text = title, + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + } + Box( + modifier = Modifier + .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = + if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + checked = !checked + cb() + } + ) + } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(55.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + modifier = Modifier.weight(1f), + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) + ) + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + } + ) + } + } + if (description != null) { + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .padding(horizontal = 8.dp) + .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) + ) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + } + } + } else { + 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() } + ) { + checked = !checked + cb() + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = label, + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + if (description != null) { + Text( + text = description, + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ) + } + } + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + } + ) + } + } +} + +@Composable +fun StyledToggle( + title: String? = null, + label: String, + description: String? = null, + controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers, + independent: Boolean = true +) { + val service = ServiceManager.getService() ?: return + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val checkedValue = service.aacpManager.controlCommandStatusList.find { + it.identifier == controlCommandIdentifier + }?.value?.takeIf { it.isNotEmpty() }?.get(0) + var checked by remember { mutableStateOf(checkedValue == 1.toByte()) } + var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) + + fun cb() { + service.aacpManager.sendControlCommand(identifier = controlCommandIdentifier.value, value = checked) + } + + val listener = remember { + object : AACPManager.ControlCommandListener { + override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { + if (controlCommand.identifier == controlCommandIdentifier.value) { + Log.d("StyledToggle", "Received control command for $label: ${controlCommand.value}") + checked = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) == 1.toByte() + } + } + } + } + LaunchedEffect(Unit) { + service.aacpManager.registerControlCommandListener(controlCommandIdentifier, listener) + } + DisposableEffect(Unit) { + onDispose { + service.aacpManager.unregisterControlCommandListener(controlCommandIdentifier, listener) + } + } + + if (independent) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + if (title != null) { + Text( + text = title, + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f) + ), + modifier = Modifier.padding(8.dp, bottom = 2.dp) + ) + } + Box( + modifier = Modifier + .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = + if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + checked = !checked + cb() + } + ) + } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(55.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + modifier = Modifier.weight(1f), + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) + ) + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + } + ) + } + } + if (description != null) { + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .padding(horizontal = 8.dp) + .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) + ) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + } + } + } else { + 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() } + ) { + checked = !checked + cb() + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = label, + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + if (description != null) { + Text( + text = description, + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ) + } + } + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + } + ) + } + } +} + +@Preview +@Composable +fun StyledTogglePreview() { + val context = LocalContext.current + val sharedPrefs = context.getSharedPreferences("preview", 0) + StyledToggle( + label = "Example Toggle", + description = "This is an example description for the styled toggle.", + sharedPreferences = sharedPrefs + ) +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt b/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt index 91f79f47..943f52b8 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/constants/Packets.kt @@ -182,21 +182,31 @@ class AirPodsNotifications { if (data.size != 22) { return } - first = if (data[10].toInt() == BatteryStatus.DISCONNECTED) { - Battery(first.component, first.level, data[10].toInt()) - } else { - Battery(data[7].toInt(), data[9].toInt(), data[10].toInt()) - } - second = if (data[15].toInt() == BatteryStatus.DISCONNECTED) { - Battery(second.component, second.level, data[15].toInt()) - } else { - Battery(data[12].toInt(), data[14].toInt(), data[15].toInt()) - } - case = if (data[20].toInt() == BatteryStatus.DISCONNECTED && case.status != BatteryStatus.DISCONNECTED) { - Battery(case.component, case.level, data[20].toInt()) - } else { - Battery(data[17].toInt(), data[19].toInt(), data[20].toInt()) - } +// first = if (data[10].toInt() == BatteryStatus.DISCONNECTED) { +// Battery(first.component, first.level, data[10].toInt()) +// } else { +// Battery(data[7].toInt(), data[9].toInt(), data[10].toInt()) +// } +// second = if (data[15].toInt() == BatteryStatus.DISCONNECTED) { +// Battery(second.component, second.level, data[15].toInt()) +// } else { +// Battery(data[12].toInt(), data[14].toInt(), data[15].toInt()) +// } +// case = if (data[20].toInt() == BatteryStatus.DISCONNECTED && case.status != BatteryStatus.DISCONNECTED) { +// Battery(case.component, case.level, data[20].toInt()) +// } else { +// Battery(data[17].toInt(), data[19].toInt(), data[20].toInt()) +// } +// sometimes it shows battery as -1%, just skip all that and set it normally + first = Battery( + data[7].toInt(), data[9].toInt(), data[10].toInt() + ) + second = Battery( + data[12].toInt(), data[14].toInt(), data[15].toInt() + ) + case = Battery( + data[17].toInt(), data[19].toInt(), data[20].toInt() + ) } fun getBattery(): List { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 95df8362..e39798cd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -19,12 +19,10 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint -import android.bluetooth.BluetoothDevice import android.util.Log import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme @@ -44,35 +42,26 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf 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.drawBehind import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset @@ -86,15 +75,11 @@ 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 dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -105,18 +90,15 @@ import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.SinglePodANCSwitch -import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.composables.StyledDropdown +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold +import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.composables.StyledSwitch import me.kavishdevar.librepods.composables.VolumeControlSwitch import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager -import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.RadareOffsetFinder -import me.kavishdevar.librepods.utils.TransparencySettings -import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse -import me.kavishdevar.librepods.utils.sendTransparencySettings -import java.io.IOException import kotlin.io.encoding.ExperimentalEncodingApi private var phoneMediaDebounceJob: Job? = null @@ -130,9 +112,6 @@ private const val TAG = "AccessibilitySettings" fun AccessibilitySettingsScreen(navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black - val verticalScrollState = rememberScrollState() - val hazeState = remember { HazeState() } - val snackbarHostState = remember { SnackbarHostState() } val aacpManager = remember { ServiceManager.getService()?.aacpManager } val isSdpOffsetAvailable = remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } @@ -143,7 +122,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { val hearingAidEnabled = remember { mutableStateOf( aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID }?.value?.getOrNull(1) == 0x01.toByte() && - aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG }?.value?.getOrNull(0) == 0x01.toByte() + aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG }?.value?.getOrNull(0) == 0x01.toByte() ) } val hearingAidListener = remember { @@ -171,125 +150,30 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } - Scaffold( - containerColor = if (isSystemInDarkTheme()) Color( - 0xFF000000 - ) else Color( - 0xFFF2F2F7 - ), - topBar = { - val darkMode = isSystemInDarkTheme() - val mDensity = remember { mutableFloatStateOf(1f) } - - CenterAlignedTopAppBar( - title = { - Text( - text = stringResource(R.string.accessibility), - style = TextStyle( - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - color = if (darkMode) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - }, - modifier = Modifier - .hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = - if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f - }) - .drawBehind { - mDensity.floatValue = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (verticalScrollState.value > 60.dp.value * density) { - drawLine( - if (darkMode) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) + StyledScaffold( + title = stringResource(R.string.accessibility), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) }, - snackbarHost = { SnackbarHost(snackbarHostState) } - ) { paddingValues -> + ) { spacerHeight, hazeState -> Column( modifier = Modifier - .hazeSource(hazeState) .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) - .verticalScroll(verticalScrollState), + .hazeSource(hazeState) + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { + Spacer(modifier = Modifier.height(spacerHeight)) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - val enabled = remember { mutableStateOf(false) } - val amplificationSliderValue = remember { mutableFloatStateOf(0.5f) } - val balanceSliderValue = remember { mutableFloatStateOf(0.5f) } - val toneSliderValue = remember { mutableFloatStateOf(0.5f) } - val ambientNoiseReductionSliderValue = remember { mutableFloatStateOf(0.0f) } - val conversationBoostEnabled = remember { mutableStateOf(false) } - val eq = remember { mutableStateOf(FloatArray(8)) } - val phoneMediaEQ = remember { mutableStateOf(FloatArray(8) { 0.5f }) } val phoneEQEnabled = remember { mutableStateOf(false) } val mediaEQEnabled = remember { mutableStateOf(false) } - val initialLoadComplete = remember { mutableStateOf(false) } - - val initialReadSucceeded = remember { mutableStateOf(false) } - val initialReadAttempts = remember { mutableIntStateOf(0) } - - val transparencySettings = remember { - mutableStateOf( - TransparencySettings( - enabled = enabled.value, - leftEQ = eq.value, - rightEQ = eq.value, - leftAmplification = amplificationSliderValue.floatValue + (0.5f - balanceSliderValue.floatValue) * amplificationSliderValue.floatValue * 2, - rightAmplification = amplificationSliderValue.floatValue + (balanceSliderValue.floatValue - 0.5f) * amplificationSliderValue.floatValue * 2, - leftTone = toneSliderValue.floatValue, - rightTone = toneSliderValue.floatValue, - leftConversationBoost = conversationBoostEnabled.value, - rightConversationBoost = conversationBoostEnabled.value, - leftAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, - rightAmbientNoiseReduction = ambientNoiseReductionSliderValue.floatValue, - netAmplification = amplificationSliderValue.floatValue, - balance = balanceSliderValue.floatValue - ) - ) - } - - val transparencyListener = remember { - object : (ByteArray) -> Unit { - override fun invoke(value: ByteArray) { - val parsed = parseTransparencySettingsResponse(value) - if (parsed != null) { - enabled.value = parsed.enabled - amplificationSliderValue.floatValue = parsed.netAmplification - balanceSliderValue.floatValue = parsed.balance - toneSliderValue.floatValue = parsed.leftTone - ambientNoiseReductionSliderValue.floatValue = - parsed.leftAmbientNoiseReduction - conversationBoostEnabled.value = parsed.leftConversationBoost - eq.value = parsed.leftEQ.copyOf() - Log.d(TAG, "Updated transparency settings from notification") - } else { - Log.w(TAG, "Failed to parse transparency settings from notification") - } - } - } - } - val pressSpeedOptions = mapOf( 0.toByte() to "Default", 1.toByte() to "Slower", @@ -402,7 +286,6 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } - // Debounced write for phone/media EQ using AACP manager when values/toggles change LaunchedEffect(phoneMediaEQ.value, phoneEQEnabled.value, mediaEQEnabled.value) { phoneMediaDebounceJob?.cancel() phoneMediaDebounceJob = CoroutineScope(Dispatchers.IO).launch { @@ -541,7 +424,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { color = Color(0x40888888), modifier = Modifier.padding(start = 12.dp, end = 0.dp) ) - + DropdownMenuComponent( label = stringResource(R.string.volume_swipe_speed), options = listOf( @@ -563,7 +446,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { ) } - if (!hearingAidEnabled.value) { + if (!hearingAidEnabled.value&& isSdpOffsetAvailable.value) { NavigationButton( to = "transparency_customization", name = stringResource(R.string.customize_transparency_mode), @@ -881,22 +764,29 @@ fun AccessibilityToggle( } if (description != null) { Spacer(modifier = Modifier.height(8.dp)) - Text( - text = description, - style = TextStyle( - fontSize = 12.sp, - fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), + Box ( // for some reason, haze and backdrop don't work for uncontained text modifier = Modifier - .padding(horizontal = 8.dp) - ) + .fillMaxWidth() + .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7), cornerShape) + ) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + // modifier = Modifier + // .padding(horizontal = 8.dp) + ) + } } } } +@ExperimentalHazeMaterialsApi @Composable private fun DropdownMenuComponent( label: String, @@ -904,7 +794,8 @@ private fun DropdownMenuComponent( selectedOption: String, onOptionSelected: (String) -> Unit, textColor: Color, - hazeState: HazeState + hazeState: HazeState, + description: String? = null, ) { val density = LocalDensity.current val itemHeightPx = with(density) { 48.dp.toPx() } @@ -1020,5 +911,21 @@ private fun DropdownMenuComponent( hazeState = hazeState ) } + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .background(if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7)) + ){ + Text( + text = description ?: "", + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt similarity index 53% rename from android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt rename to android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt index 7e5c8a4e..bd0bbcf8 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/AdaptiveStrengthSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt @@ -16,40 +16,51 @@ * along with this program. If not, see . */ -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables +package me.kavishdevar.librepods.screens +import android.annotation.SuppressLint 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.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.DisposableEffect +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.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -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 dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold +import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.io.encoding.ExperimentalEncodingApi -import kotlin.math.roundToInt +private var debounceJob: Job? = null + +@SuppressLint("DefaultLocale") +@ExperimentalHazeMaterialsApi +@OptIn(ExperimentalMaterial3Api::class, ExperimentalEncodingApi::class) @Composable -fun AdaptiveStrengthSlider() { +fun AdaptiveStrengthScreen(navController: NavController) { + val isDarkTheme = isSystemInDarkTheme() + val sliderValue = remember { mutableFloatStateOf(0f) } val service = ServiceManager.getService()!! + LaunchedEffect(sliderValue) { val sliderValueFromAACP = service.aacpManager.controlCommandStatusList.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH @@ -82,38 +93,44 @@ fun AdaptiveStrengthSlider() { } } - val isDarkTheme = isSystemInDarkTheme() - 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 - - Column( - modifier = Modifier - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - StyledSlider( - mutableFloatState = sliderValue, - onValueChange = { - sliderValue.floatValue = snapIfClose(it, listOf(0f, 50f, 100f)) - }, - valueRange = 0f..100f, - snapPoints = listOf(0f, 50f, 100f), - startLabel = stringResource(R.string.less_noise), - endLabel = stringResource(R.string.more_noise), - independent = false - ) + StyledScaffold( + title = stringResource(R.string.customize_adaptive_audio), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme + ) + } + ) { spacerHeight -> + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Spacer(modifier = Modifier.height(spacerHeight)) + StyledSlider( + label = stringResource(R.string.customize_adaptive_audio).uppercase(), + mutableFloatState = sliderValue, + onValueChange = { + sliderValue.floatValue = it + debounceJob?.cancel() + debounceJob = CoroutineScope(Dispatchers.Default).launch { + delay(300) + service.aacpManager.sendControlCommand( + AACPManager.Companion.ControlCommandIdentifiers.AUTO_ANC_STRENGTH.value, + (100 - it).toInt() + ) + } + }, + valueRange = 0f..100f, + snapPoints = listOf(0f, 50f, 100f), + startIcon = "􀊥", + endIcon = "􀊩", + independent = true, + description = stringResource(R.string.adaptive_audio_description) + ) + } } } - -@Preview -@Composable -fun AdaptiveStrengthSliderPreview() { - AdaptiveStrengthSlider() -} - -private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { - val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value - return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value -} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 56ec24fe..5dd112dc 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -41,34 +41,19 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -83,24 +68,26 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController -import dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.hazeEffect +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.highlight.Highlight import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -import dev.chrisbanes.haze.rememberHazeState import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.AudioSettings import me.kavishdevar.librepods.composables.BatteryView import me.kavishdevar.librepods.composables.CallControlSettings import me.kavishdevar.librepods.composables.ConnectionSettings -import me.kavishdevar.librepods.composables.IndependentToggle import me.kavishdevar.librepods.composables.MicrophoneSettings import me.kavishdevar.librepods.composables.NameField import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.NoiseControlSettings import me.kavishdevar.librepods.composables.PressAndHoldSettings +import me.kavishdevar.librepods.composables.StyledButton +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.constants.AirPodsNotifications import me.kavishdevar.librepods.services.AirPodsService import me.kavishdevar.librepods.ui.theme.LibrePodsTheme @@ -144,8 +131,6 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } } - val verticalScrollState = rememberScrollState() - val hazeState = rememberHazeState( blurEnabled = true ) val snackbarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() @@ -153,12 +138,6 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, isRemotelyConnected = connected } - fun showSnackbar(message: String) { - coroutineScope.launch { - snackbarHostState.showSnackbar(message) - } - } - val context = LocalContext.current val connectionReceiver = remember { @@ -219,118 +198,38 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } } } - - @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") - Scaffold( - containerColor = if (isSystemInDarkTheme()) Color( - 0xFF000000 - ) else Color( - 0xFFF2F2F7 - ), - topBar = { - val darkMode = isSystemInDarkTheme() - val mDensity = remember { mutableFloatStateOf(1f) } - CenterAlignedTopAppBar( - title = { - Text( - text = deviceName.text, - style = TextStyle( - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - color = if (darkMode) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - }, - modifier = Modifier - .hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = - if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f - } - ) - .drawBehind { - mDensity.floatValue = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (verticalScrollState.value > 60.dp.value * density) { - drawLine( - if (darkMode) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ), - actions = { - if (isRemotelyConnected) { - IconButton( - onClick = { - showSnackbar("Connected remotely to AirPods via Linux.") - }, - colors = IconButtonDefaults.iconButtonColors( - containerColor = Color.Transparent, - contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black - ) - ) { - Icon( - imageVector = Icons.Default.Info, - contentDescription = "Info", - ) - } - } - IconButton( - onClick = { - navController.navigate("app_settings") - }, - colors = IconButtonDefaults.iconButtonColors( - containerColor = Color.Transparent, - contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black - ) - ) { - Icon( - imageVector = Icons.Default.Settings, - contentDescription = "Settings", - ) - } - } + val darkMode = isSystemInDarkTheme() + StyledScaffold( + title = deviceName.text, + actionButtons = listOf { + StyledIconButton( + onClick = { navController.navigate("app_settings") }, + icon = "􀍟", + darkMode = darkMode ) }, - snackbarHost = { SnackbarHost(snackbarHostState) } - ) { paddingValues -> + snackbarHostState = snackbarHostState + ) { spacerHeight, hazeState -> if (isLocallyConnected || isRemotelyConnected) { Column( modifier = Modifier - .hazeSource(hazeState) .fillMaxSize() - .padding(horizontal = 16.dp) - .verticalScroll( - state = verticalScrollState, - enabled = true, - ) + .hazeSource(hazeState) + .verticalScroll(rememberScrollState()) ) { - Spacer(Modifier.height(75.dp)) + Spacer(modifier = Modifier.height(spacerHeight)) LaunchedEffect(service) { service.let { - it.sendBroadcast(Intent(AirPodsNotifications.Companion.BATTERY_DATA).apply { + it.sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { putParcelableArrayListExtra("data", ArrayList(it.getBattery())) }) - it.sendBroadcast(Intent(AirPodsNotifications.Companion.ANC_DATA).apply { + it.sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply { putExtra("data", it.getANC()) }) } } - val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) - - Spacer(modifier = Modifier.height(64.dp)) BatteryView(service = service) - Spacer(modifier = Modifier.height(32.dp)) // Show BLE-only mode indicator @@ -372,7 +271,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, PressAndHoldSettings(navController = navController) Spacer(modifier = Modifier.height(16.dp)) - AudioSettings() + AudioSettings(navController = navController) Spacer(modifier = Modifier.height(16.dp)) ConnectionSettings() @@ -381,11 +280,8 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, MicrophoneSettings(hazeState) Spacer(modifier = Modifier.height(16.dp)) - IndependentToggle( - name = stringResource(R.string.sleep_detection), - service = service, - sharedPreferences = sharedPreferences, - default = false, + StyledToggle( + label = stringResource(R.string.sleep_detection), controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG ) @@ -396,11 +292,8 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, NavigationButton(to = "accessibility", "Accessibility", navController = navController) Spacer(modifier = Modifier.height(16.dp)) - IndependentToggle( - name = stringResource(R.string.off_listening_mode), - service = service, - sharedPreferences = sharedPreferences, - default = false, + StyledToggle( + label = stringResource(R.string.off_listening_mode).uppercase(), controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, description = stringResource(R.string.off_listening_mode_description) ) @@ -415,19 +308,24 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } } else { + val backdrop = rememberLayerBackdrop() Column( modifier = Modifier .fillMaxSize() - .padding(horizontal = 8.dp) - .verticalScroll( - state = verticalScrollState, - enabled = true, - ), + .drawBackdrop( + backdrop = rememberLayerBackdrop(), + exportedBackdrop = backdrop, + shape = { RoundedCornerShape(0.dp) }, + highlight = { + Highlight.AmbientDefault.copy(alpha = 0f) + } + ) + .padding(horizontal = 8.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Text( - text = "AirPods not connected", + text = stringResource(R.string.airpods_not_connected), style = TextStyle( fontSize = 24.sp, fontWeight = FontWeight.Medium, @@ -439,7 +337,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, ) Spacer(Modifier.height(24.dp)) Text( - text = "Please connect your AirPods to access settings.", + text = stringResource(R.string.airpods_not_connected_description), style = TextStyle( fontSize = 16.sp, fontWeight = FontWeight.Light, @@ -450,13 +348,9 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, modifier = Modifier.fillMaxWidth() ) Spacer(Modifier.height(32.dp)) - Button( + StyledButton( onClick = { navController.navigate("troubleshooting") }, - shape = RoundedCornerShape(10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFF2F2F7), - contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black, - ) + backdrop = backdrop ) { Text( text = "Troubleshoot Connection", @@ -472,7 +366,6 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } } - @Preview @Composable fun AirPodsSettingsScreenPreview() { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index bbe332f5..de81d468 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -42,38 +42,31 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -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.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -82,39 +75,32 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController -import dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi +import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledSwitch import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.RadareOffsetFinder import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.math.roundToInt -import androidx.compose.runtime.rememberCoroutineScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class, ExperimentalEncodingApi::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() val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val scrollState = rememberScrollState() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val hazeState = remember { HazeState() } var showResetDialog by remember { mutableStateOf(false) } var showIrkDialog by remember { mutableStateOf(false) } @@ -134,6 +120,7 @@ fun AppSettingsScreen(navController: NavController) { irkValue = decoded.joinToString("") { "%02x".format(it) } } catch (e: Exception) { irkValue = "" + e.printStackTrace() } } @@ -143,6 +130,7 @@ fun AppSettingsScreen(navController: NavController) { encKeyValue = decoded.joinToString("") { "%02x".format(it) } } catch (e: Exception) { encKeyValue = "" + e.printStackTrace() } } } @@ -198,8 +186,6 @@ fun AppSettingsScreen(navController: NavController) { } } - var mDensity by remember { mutableFloatStateOf(0f) } - fun validateHexInput(input: String): Boolean { val hexPattern = Regex("^[0-9a-fA-F]{32}$") return hexPattern.matches(input) @@ -210,84 +196,24 @@ fun AppSettingsScreen(navController: NavController) { BackHandler(enabled = isProcessingSdp) {} - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - CenterAlignedTopAppBar( - modifier = Modifier.hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = - if (scrollState.value > 60.dp.value * mDensity) 1f else 0f - }) - .drawBehind { - mDensity = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (scrollState.value > 60.dp.value * density) { - drawLine( - if (isDarkTheme) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - title = { - Text( - text = stringResource(R.string.app_settings), - fontFamily = FontFamily(Font(R.font.sf_pro)), - ) - }, - navigationIcon = { - TextButton( - onClick = { - if (!isProcessingSdp) { - navController.popBackStack() - } - }, - enabled = !isProcessingSdp, - shape = RoundedCornerShape(8.dp), - modifier = Modifier.width(180.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)) - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f) - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ), - scrollBehavior = scrollBehavior + StyledScaffold( + title = stringResource(R.string.app_settings), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) - }, - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) - else Color(0xFFF2F2F7), - ) { paddingValues -> - Column ( + } + ) { spacerHeight, hazeState -> + Column( modifier = Modifier .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) .verticalScroll(scrollState) .hazeSource(state = hazeState) ) { + Spacer(modifier = Modifier.height(spacerHeight)) + val isDarkTheme = isSystemInDarkTheme() val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black @@ -295,7 +221,7 @@ fun AppSettingsScreen(navController: NavController) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Widget".uppercase(), + text = stringResource(R.string.widget).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -335,13 +261,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Show phone battery in widget", + text = stringResource(R.string.show_phone_battery_in_widget), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Display your phone's battery level in the widget alongside AirPods battery", + text = stringResource(R.string.show_phone_battery_in_widget_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -359,7 +285,7 @@ fun AppSettingsScreen(navController: NavController) { } Text( - text = "Connection Mode".uppercase(), + text = stringResource(R.string.connection_mode).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -399,12 +325,12 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "BLE Only Mode", + text = stringResource(R.string.ble_only_mode), fontSize = 16.sp, color = textColor ) Text( - text = "Only use Bluetooth Low Energy for battery data and ear detection. Disables advanced features requiring L2CAP connection.", + text = stringResource(R.string.ble_only_mode_description), fontSize = 13.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -422,7 +348,7 @@ fun AppSettingsScreen(navController: NavController) { } Text( - text = "Conversational Awareness".uppercase(), + text = stringResource(R.string.conversational_awareness).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -539,7 +465,7 @@ fun AppSettingsScreen(navController: NavController) { } Text( - text = "Conversational Awareness Volume", + text = stringResource(R.string.conversational_awareness_volume), fontSize = 16.sp, color = textColor, modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) @@ -628,7 +554,7 @@ fun AppSettingsScreen(navController: NavController) { } Text( - text = "Quick Settings Tile".uppercase(), + text = stringResource(R.string.quick_settings_tile).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -672,15 +598,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Open dialog for controlling", + text = stringResource(R.string.open_dialog_for_controlling), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = if (openDialogForControlling) - "If disabled, clicking on the QS will cycle through modes" - else "If enabled, it will show a dialog for controlling noise control mode and conversational awareness", + text = stringResource(R.string.open_dialog_for_controlling_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -697,7 +621,7 @@ fun AppSettingsScreen(navController: NavController) { } Text( - text = "Ear Detection".uppercase(), + text = stringResource(R.string.ear_detection).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -741,13 +665,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Disconnect AirPods when not wearing", + text = stringResource(R.string.disconnect_when_not_wearing), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "You will still be able to control them with the app - this just disconnects the audio.", + text = stringResource(R.string.disconnect_when_not_wearing_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -1051,7 +975,7 @@ fun AppSettingsScreen(navController: NavController) { } Text( - text = "Advanced Options".uppercase(), + text = stringResource(R.string.advanced_options).uppercase(), style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, @@ -1087,13 +1011,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Set Identity Resolving Key (IRK)", + text = stringResource(R.string.set_identity_resolving_key), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Manually set the IRK value used for resolving BLE random addresses", + text = stringResource(R.string.set_identity_resolving_key_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -1116,13 +1040,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Set Encryption Key", + text = stringResource(R.string.set_encryption_key), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Manually set the ENC_KEY value used for decrypting BLE advertisements", + text = stringResource(R.string.set_encryption_key_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -1152,13 +1076,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Use alternate head tracking packets", + text = stringResource(R.string.use_alternate_head_tracking_packets), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Enable this if head tracking doesn't work for you. This sends different data to AirPods for requesting/stopping head tracking data.", + text = stringResource(R.string.use_alternate_head_tracking_packets_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -1206,6 +1130,7 @@ fun AppSettingsScreen(navController: NavController) { LaunchedEffect(Unit) { actAsAppleDevice = RadareOffsetFinder.isSdpOffsetAvailable() } + val restartBluetoothText = stringResource(R.string.found_offset_restart_bluetooth) Row( modifier = Modifier @@ -1222,9 +1147,9 @@ fun AppSettingsScreen(navController: NavController) { coroutineScope.launch { if (newValue) { val radareOffsetFinder = RadareOffsetFinder(context) - val success = radareOffsetFinder.findSdpOffset() ?: false + val success = radareOffsetFinder.findSdpOffset() if (success) { - Toast.makeText(context, "Found offset please restart the Bluetooth process", Toast.LENGTH_LONG).show() + Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() } } else { RadareOffsetFinder.clearSdpOffset() @@ -1242,13 +1167,13 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = "Act as an Apple device", + text = stringResource(R.string.act_as_an_apple_device), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Enables multi-device connectivity and Accessibility features like customizing transparency mode (amplification, tone, ambient noise reduction, conversation boost, and EQ)", + text = stringResource(R.string.act_as_an_apple_device_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, @@ -1256,14 +1181,13 @@ fun AppSettingsScreen(navController: NavController) { if (actAsAppleDevice) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Might be unstable!! A maximum of two devices can be connected to your AirPods. If you are using with an Apple device like an iPad or Mac, then please connect that device first and then your Android.", + text = stringResource(R.string.act_as_an_apple_device_warning), fontSize = 12.sp, color = MaterialTheme.colorScheme.error, lineHeight = 14.sp, ) } } - StyledSwitch( checked = actAsAppleDevice, onCheckedChange = { @@ -1273,9 +1197,9 @@ fun AppSettingsScreen(navController: NavController) { coroutineScope.launch { if (it) { val radareOffsetFinder = RadareOffsetFinder(context) - val success = radareOffsetFinder.findSdpOffset() ?: false + val success = radareOffsetFinder.findSdpOffset() if (success) { - Toast.makeText(context, "Found offset please restart the Bluetooth process", Toast.LENGTH_LONG).show() + Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() } } else { RadareOffsetFinder.clearSdpOffset() @@ -1313,7 +1237,7 @@ fun AppSettingsScreen(navController: NavController) { ) Spacer(modifier = Modifier.width(8.dp)) Text( - text = "Reset Hook Offset", + text = stringResource(R.string.reset_hook_offset), color = MaterialTheme.colorScheme.onErrorContainer, style = TextStyle( fontSize = 16.sp, @@ -1338,17 +1262,19 @@ fun AppSettingsScreen(navController: NavController) { }, text = { Text( - "This will clear the current hook offset and require you to go through the setup process again. Are you sure you want to continue?", + stringResource(R.string.reset_hook_offset_description), fontFamily = FontFamily(Font(R.font.sf_pro)) ) }, confirmButton = { + val successText = stringResource(R.string.hook_offset_reset_success) + val failureText = stringResource(R.string.hook_offset_reset_failure) TextButton( onClick = { if (RadareOffsetFinder.clearHookOffsets()) { Toast.makeText( context, - "Hook offset has been reset. Redirecting to setup...", + successText, Toast.LENGTH_LONG ).show() @@ -1358,7 +1284,7 @@ fun AppSettingsScreen(navController: NavController) { } else { Toast.makeText( context, - "Failed to reset hook offset", + failureText, Toast.LENGTH_SHORT ).show() } @@ -1369,7 +1295,7 @@ fun AppSettingsScreen(navController: NavController) { ) ) { Text( - "Reset", + stringResource(R.string.reset), fontFamily = FontFamily(Font(R.font.sf_pro)), fontWeight = FontWeight.Medium ) @@ -1394,7 +1320,7 @@ fun AppSettingsScreen(navController: NavController) { onDismissRequest = { showIrkDialog = false }, title = { Text( - "Set Identity Resolving Key (IRK)", + stringResource(R.string.set_identity_resolving_key), fontFamily = FontFamily(Font(R.font.sf_pro)), fontWeight = FontWeight.Medium ) @@ -1402,7 +1328,7 @@ fun AppSettingsScreen(navController: NavController) { text = { Column { Text( - "Enter 16-byte IRK as hex string (32 characters):", + stringResource(R.string.enter_irk_hex), fontFamily = FontFamily(Font(R.font.sf_pro)), modifier = Modifier.padding(bottom = 8.dp) ) @@ -1425,14 +1351,16 @@ fun AppSettingsScreen(navController: NavController) { ), supportingText = { if (irkError != null) { - Text(irkError!!, color = MaterialTheme.colorScheme.error) + Text(stringResource(R.string.must_be_32_hex_chars), color = MaterialTheme.colorScheme.error) } }, - label = { Text("IRK Hex Value") } + label = { Text(stringResource(R.string.irk_hex_value)) } ) } }, confirmButton = { + val successText = stringResource(R.string.irk_set_success) + val errorText = stringResource(R.string.error_converting_hex) TextButton( onClick = { if (!validateHexInput(irkValue)) { @@ -1450,10 +1378,10 @@ fun AppSettingsScreen(navController: NavController) { val base64Value = Base64.encode(hexBytes) sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value)} - Toast.makeText(context, "IRK has been set successfully", Toast.LENGTH_SHORT).show() + Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() showIrkDialog = false } catch (e: Exception) { - irkError = "Error converting hex: ${e.message}" + irkError = errorText + " " + (e.message ?: "Unknown error") } } ) { @@ -1483,7 +1411,7 @@ fun AppSettingsScreen(navController: NavController) { onDismissRequest = { showEncKeyDialog = false }, title = { Text( - "Set Encryption Key", + stringResource(R.string.set_encryption_key), fontFamily = FontFamily(Font(R.font.sf_pro)), fontWeight = FontWeight.Medium ) @@ -1491,7 +1419,7 @@ fun AppSettingsScreen(navController: NavController) { text = { Column { Text( - "Enter 16-byte ENC_KEY as hex string (32 characters):", + stringResource(R.string.enter_enc_key_hex), fontFamily = FontFamily(Font(R.font.sf_pro)), modifier = Modifier.padding(bottom = 8.dp) ) @@ -1514,14 +1442,16 @@ fun AppSettingsScreen(navController: NavController) { ), supportingText = { if (encKeyError != null) { - Text(encKeyError!!, color = MaterialTheme.colorScheme.error) + Text(stringResource(R.string.must_be_32_hex_chars), color = MaterialTheme.colorScheme.error) } }, - label = { Text("ENC_KEY Hex Value") } + label = { Text(stringResource(R.string.enc_key_hex_value)) } ) } }, confirmButton = { + val successText = stringResource(R.string.encryption_key_set_success) + val errorText = stringResource(R.string.error_converting_hex) TextButton( onClick = { if (!validateHexInput(encKeyValue)) { @@ -1539,10 +1469,10 @@ fun AppSettingsScreen(navController: NavController) { val base64Value = Base64.encode(hexBytes) sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value)} - Toast.makeText(context, "Encryption key has been set successfully", Toast.LENGTH_SHORT).show() + Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() showEncKeyDialog = false } catch (e: Exception) { - encKeyError = "Error converting hex: ${e.message}" + encKeyError = errorText + " " + (e.message ?: "Unknown error") } } ) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt index 94fff3c2..cdacf701 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt @@ -29,10 +29,8 @@ import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row @@ -50,37 +48,25 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Send import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider 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.TextField import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager @@ -92,15 +78,13 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.constants.BatteryStatus import me.kavishdevar.librepods.constants.isHeadTrackingData import me.kavishdevar.librepods.services.ServiceManager @@ -304,52 +288,24 @@ fun parseOutgoingPacket(bytes: ByteArray, rawData: String): PacketInfo { } } -@Composable -fun IOSCheckbox( - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - modifier: Modifier = Modifier -) { - Box( - modifier = modifier - .size(24.dp) - .clickable { onCheckedChange(!checked) }, - contentAlignment = Alignment.Center - ) { - if (checked) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = "Checked", - tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5), - modifier = Modifier.size(20.dp) - ) - } - } -} - @RequiresApi(Build.VERSION_CODES.Q) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalFoundationApi::class) @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnspecifiedRegisterReceiverFlag") @Composable fun DebugScreen(navController: NavController) { - val hazeState = remember { HazeState() } val context = LocalContext.current val listState = rememberLazyListState() - val scrollOffset by remember { derivedStateOf { listState.firstVisibleItemScrollOffset } } val focusManager = LocalFocusManager.current val coroutineScope = rememberCoroutineScope() - val showMenu = remember { mutableStateOf(false) } - val airPodsService = remember { ServiceManager.getService() } val packetLogs = airPodsService?.packetLogsFlow?.collectAsState(emptySet())?.value ?: emptySet() - val shouldScrollToBottom = remember { mutableStateOf(true) } val refreshTrigger = remember { mutableIntStateOf(0) } LaunchedEffect(refreshTrigger.intValue) { while(true) { delay(1000) - refreshTrigger.intValue = refreshTrigger.intValue + 1 + refreshTrigger.intValue += 1 } } @@ -363,137 +319,42 @@ fun DebugScreen(navController: NavController) { } LaunchedEffect(packetLogs.size, refreshTrigger.intValue) { - if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) { + if (packetLogs.isNotEmpty()) { listState.animateScrollToItem(packetLogs.size - 1) } } - Scaffold( - topBar = { - CenterAlignedTopAppBar( - title = { Text("Debug") }, - navigationIcon = { - TextButton( - onClick = { navController.popBackStack() }, - shape = RoundedCornerShape(8.dp), - ) { - val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - Icon( - Icons.AutoMirrored.Filled.KeyboardArrowLeft, - contentDescription = "Back", - tint = if (isSystemInDarkTheme()) 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 (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - } - }, - actions = { - Box { - IconButton(onClick = { showMenu.value = true }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "More Options", - tint = if (isSystemInDarkTheme()) Color.White else Color.Black - ) - } + val isDarkTheme = isSystemInDarkTheme() - DropdownMenu( - expanded = showMenu.value, - onDismissRequest = { showMenu.value = false }, - modifier = Modifier - .width(250.dp) - .background( - if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7) - ) - .padding(vertical = 4.dp) - ) { - DropdownMenuItem( - text = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text( - "Auto-scroll", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal - ) - ) - Spacer(modifier = Modifier.weight(1f)) - IOSCheckbox( - checked = shouldScrollToBottom.value, - onCheckedChange = { shouldScrollToBottom.value = it } - ) - } - }, - onClick = { - shouldScrollToBottom.value = !shouldScrollToBottom.value - showMenu.value = false - } - ) - - HorizontalDivider( - color = if (isSystemInDarkTheme()) Color(0xFF3A3A3C) else Color(0xFFE5E5EA), - thickness = 0.5.dp - ) - - DropdownMenuItem( - text = { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text( - "Clear logs", - style = TextStyle( - fontSize = 16.sp, - fontWeight = FontWeight.Normal - ) - ) - Spacer(modifier = Modifier.weight(1f)) - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Clear logs", - tint = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5) - ) - } - }, - onClick = { - ServiceManager.getService()?.clearLogs() - expandedItems.value = emptySet() - showMenu.value = false - } - ) - } - } - }, - modifier = Modifier.hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = if (scrollOffset > 0) 1f else 0f - }), - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + StyledScaffold( + title = "Debug", + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) }, - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7), - ) { paddingValues -> + actionButtons = listOf( + { + StyledIconButton( + onClick = { + airPodsService?.clearLogs() + expandedItems.value = emptySet() + }, + icon = "􀈑", + darkMode = isDarkTheme, + ) + } + ), + ) { spacerHeight, hazeState -> Column( modifier = Modifier .fillMaxSize() .hazeSource(hazeState) - .padding(top = paddingValues.calculateTopPadding()) .navigationBarsPadding() ) { + Spacer(modifier = Modifier.height(spacerHeight)) LazyColumn( state = listState, modifier = Modifier @@ -509,7 +370,7 @@ fun DebugScreen(navController: NavController) { Card( modifier = Modifier .fillMaxWidth() - .padding(vertical = 2.dp, horizontal = 4.dp) + .padding(vertical = 2.dp) .combinedClickable( onClick = { expandedItems.value = if (isExpanded) { @@ -528,67 +389,65 @@ fun DebugScreen(navController: NavController) { containerColor = if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7), ) ) { - Column(modifier = Modifier.padding(8.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = if (isSent) Icons.AutoMirrored.Filled.KeyboardArrowLeft else Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = null, - tint = if (isSent) Color.Green else Color.Red, - modifier = Modifier.size(24.dp) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = if (isSent) Icons.AutoMirrored.Filled.KeyboardArrowLeft else Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = null, + tint = if (isSent) Color.Green else Color.Red, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Column { + Text( + text = if (packetInfo.isUnknown) { + val shortenedData = packetInfo.rawData.take(60) + + (if (packetInfo.rawData.length > 60) "..." else "") + shortenedData + } else { + "${packetInfo.type}: ${packetInfo.description}" + }, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + fontFamily = FontFamily(Font(R.font.hack)) + ) ) - Spacer(modifier = Modifier.width(4.dp)) - Column { + if (isExpanded) { + Spacer(modifier = Modifier.height(4.dp)) + + if (packetInfo.parsedData.isNotEmpty()) { + packetInfo.parsedData.forEach { (key, value) -> + Row { + Text( + text = "$key: ", + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + fontFamily = FontFamily(Font(R.font.hack)) + ), + color = Color.Gray + ) + Text( + text = value, + style = TextStyle( + fontSize = 12.sp, + fontFamily = FontFamily(Font(R.font.hack)) + ), + color = Color.Gray + ) + } + } + Spacer(modifier = Modifier.height(4.dp)) + } + Text( - text = if (packetInfo.isUnknown) { - val shortenedData = packetInfo.rawData.take(60) + - (if (packetInfo.rawData.length > 60) "..." else "") - shortenedData - } else { - "${packetInfo.type}: ${packetInfo.description}" - }, + text = "Raw: ${packetInfo.rawData}", style = TextStyle( fontSize = 12.sp, - fontWeight = FontWeight.Medium, fontFamily = FontFamily(Font(R.font.hack)) - ) + ), + color = Color.Gray ) - if (isExpanded) { - Spacer(modifier = Modifier.height(4.dp)) - - if (packetInfo.parsedData.isNotEmpty()) { - packetInfo.parsedData.forEach { (key, value) -> - Row { - Text( - text = "$key: ", - style = TextStyle( - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily(Font(R.font.hack)) - ), - color = Color.Gray - ) - Text( - text = value, - style = TextStyle( - fontSize = 12.sp, - fontFamily = FontFamily(Font(R.font.hack)) - ), - color = Color.Gray - ) - } - } - Spacer(modifier = Modifier.height(4.dp)) - } - - Text( - text = "Raw: ${packetInfo.rawData}", - style = TextStyle( - fontSize = 12.sp, - fontFamily = FontFamily(Font(R.font.hack)) - ), - color = Color.Gray - ) - } } } } @@ -627,7 +486,7 @@ fun DebugScreen(navController: NavController) { packet.value = TextFieldValue("") focusManager.clearFocus() - if (shouldScrollToBottom.value && packetLogs.isNotEmpty()) { + if (packetLogs.isNotEmpty()) { coroutineScope.launch { try { delay(100) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt index e7039dea..3ec0ac34 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt @@ -41,25 +41,15 @@ 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.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -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.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -74,22 +64,16 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.asAndroidPath import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.path -import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -99,22 +83,19 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.IndependentToggle +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.HeadTracking import kotlin.io.encoding.ExperimentalEncodingApi @@ -134,147 +115,59 @@ fun HeadTrackingScreen(navController: NavController) { ServiceManager.getService()?.stopHeadTracking() } } - val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isDarkTheme = isSystemInDarkTheme() val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black val scrollState = rememberScrollState() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val hazeState = remember { HazeState() } - - var mDensity by remember { mutableFloatStateOf(0f) } - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - CenterAlignedTopAppBar( - modifier = Modifier.hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = - if (scrollState.value > 60.dp.value * mDensity) 1f else 0f - }) - .drawBehind { - mDensity = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (scrollState.value > 60.dp.value * density) { - drawLine( - if (isDarkTheme) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - title = { - Text( - stringResource(R.string.head_tracking), - fontFamily = FontFamily(Font(R.font.sf_pro)), - ) - }, - navigationIcon = { - TextButton( - onClick = { - navController.popBackStack() - if (ServiceManager.getService()?.isHeadTrackingActive == true) ServiceManager.getService()?.stopHeadTracking() - }, - shape = RoundedCornerShape(8.dp), - modifier = Modifier.width(180.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)) - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f) - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ), - actions = { - var isActive by remember { mutableStateOf(ServiceManager.getService()?.isHeadTrackingActive == true) } - IconButton( - onClick = { - if (ServiceManager.getService()?.isHeadTrackingActive == false) { - ServiceManager.getService()?.startHeadTracking() - Log.d("HeadTrackingScreen", "Head tracking started") - isActive = true - } else { - ServiceManager.getService()?.stopHeadTracking() - Log.d("HeadTrackingScreen", "Head tracking stopped") - isActive = false - } - }, - ) { - Icon( - if (isActive) { - ImageVector.Builder( - name = "Pause", - defaultWidth = 24.dp, - defaultHeight = 24.dp, - viewportWidth = 24f, - viewportHeight = 24f - ).apply { - path( - fill = SolidColor(Color.Black), - pathBuilder = { - moveTo(6f, 5f) - lineTo(10f, 5f) - lineTo(10f, 19f) - lineTo(6f, 19f) - lineTo(6f, 5f) - moveTo(14f, 5f) - lineTo(18f, 5f) - lineTo(18f, 19f) - lineTo(14f, 19f) - lineTo(14f, 5f) - } - ) - }.build() - } else Icons.Filled.PlayArrow, - contentDescription = "Start", - tint = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5), - modifier = Modifier.scale(1.5f) - ) - } - }, - scrollBehavior = scrollBehavior + + StyledScaffold ( + title = stringResource(R.string.head_tracking), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) }, - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) - else Color(0xFFF2F2F7), - ) { paddingValues -> + actionButtons = listOf( + { + var isActive by remember { mutableStateOf(ServiceManager.getService()?.isHeadTrackingActive == true) } + StyledIconButton( + onClick = { + if (ServiceManager.getService()?.isHeadTrackingActive == false) { + ServiceManager.getService()?.startHeadTracking() + Log.d("HeadTrackingScreen", "Head tracking started") + } else { + ServiceManager.getService()?.stopHeadTracking() + Log.d("HeadTrackingScreen", "Head tracking stopped") + } + }, + icon = if (isActive) "􀊅" else "􀊃", + darkMode = isDarkTheme + ) + } + ), + ) { spacerHeight, hazeState -> Column ( modifier = Modifier .fillMaxSize() - .padding(paddingValues = paddingValues) - .padding(horizontal = 16.dp) .padding(top = 8.dp) .verticalScroll(scrollState) .hazeSource(state = hazeState) ) { + Spacer(modifier = Modifier.height(spacerHeight)) val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) var gestureText by remember { mutableStateOf("") } val coroutineScope = rememberCoroutineScope() - IndependentToggle(name = "Head Gestures", sharedPreferences = sharedPreferences) + StyledToggle( + label = "Head Gestures", + sharedPreferences = sharedPreferences, + sharedPreferenceKey = "head_gestures", + ) Spacer(modifier = Modifier.height(2.dp)) Text( stringResource(R.string.head_gestures_details), @@ -302,7 +195,7 @@ fun HeadTrackingScreen(navController: NavController) { Spacer(modifier = Modifier.height(16.dp)) Text( - "Acceleration", + "Velocity", style = TextStyle( fontSize = 18.sp, fontWeight = FontWeight.Medium, @@ -441,14 +334,13 @@ private fun ParticleText( if (particles.isEmpty()) { val random = Random(System.currentTimeMillis()) - for (i in 0..100) { + for (@Suppress("Unused")i in 0..100) { val x = centerX + random.nextFloat() * textBounds.width val y = centerY - textBounds.height / 2 + random.nextFloat() * textBounds.height val vx = (random.nextFloat() - 0.5f) * 20 val vy = (random.nextFloat() - 0.5f) * 20 particles.add(Particle(Offset(x, y), Offset(vx, vy))) } - textVisible = false } particles.forEach { particle -> @@ -518,14 +410,12 @@ private fun HeadVisualization() { fun rotate3D(point: Triple): Triple { val (x, y, z) = point val x1 = x * cosY - z * sinY - val y1 = y val z1 = x * sinY + z * cosY - val x2 = x1 - val y2 = y1 * cosP - z1 * sinP - val z2 = y1 * sinP + z1 * cosP + val y2 = y * cosP - z1 * sinP + val z2 = y * sinP + z1 * cosP - return Triple(x2, y2, z2) + return Triple(x1, y2, z2) } fun project(point: Triple): Pair { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 1dacdf65..122b3649 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -19,22 +19,16 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint -import android.content.Context import android.util.Log import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.height import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -43,23 +37,11 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset -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.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import dev.chrisbanes.haze.HazeEffectScope import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -67,13 +49,14 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.IndependentToggle +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledSlider +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.ATTManager -import me.kavishdevar.librepods.utils.RadareOffsetFinder import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder @@ -88,78 +71,31 @@ private const val TAG = "HearingAidAdjustments" @Composable fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController) { val isDarkTheme = isSystemInDarkTheme() - val textColor = if (isDarkTheme) Color.White else Color.Black val verticalScrollState = rememberScrollState() val hazeState = remember { HazeState() } - val snackbarHostState = remember { SnackbarHostState() } val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") val aacpManager = remember { ServiceManager.getService()?.aacpManager } - val context = LocalContext.current - remember { RadareOffsetFinder(context) } - remember { mutableStateOf(RadareOffsetFinder.isSdpOffsetAvailable()) } - val service = ServiceManager.getService() - - if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491) - if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) - if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) - if (isDarkTheme) Color.White else Color.Black - - Scaffold( - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7), - topBar = { - val darkMode = isSystemInDarkTheme() - val mDensity = remember { mutableFloatStateOf(1f) } - - CenterAlignedTopAppBar( - title = { - Text( - text = stringResource(R.string.adjustments), - style = TextStyle( - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - color = if (darkMode) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - }, - modifier = Modifier - .hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f - }) - .drawBehind { - mDensity.floatValue = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (verticalScrollState.value > 60.dp.value * density) { - drawLine( - if (darkMode) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent) + + StyledScaffold( + title = stringResource(R.string.adjustments), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) - }, - snackbarHost = { SnackbarHost(snackbarHostState) } - ) { paddingValues -> + } + ) { spacerHeight -> Column( modifier = Modifier .hazeSource(hazeState) .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) .verticalScroll(verticalScrollState), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + Spacer(modifier = Modifier.height(spacerHeight)) - remember { mutableStateOf(false) } val amplificationSliderValue = remember { mutableFloatStateOf(0.5f) } val balanceSliderValue = remember { mutableFloatStateOf(0.5f) } val toneSliderValue = remember { mutableFloatStateOf(0.5f) } @@ -355,12 +291,9 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController independent = true, ) - val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - IndependentToggle( - name = stringResource(R.string.swipe_to_control_amplification), - service = service, - sharedPreferences = sharedPreferences, + StyledToggle( + label = stringResource(R.string.swipe_to_control_amplification), controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.HPS_GAIN_SWIPE, description = stringResource(R.string.swipe_amplification_description) ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index cf022a43..7153af4a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -39,27 +39,21 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect 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.drawBehind -import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource @@ -70,20 +64,20 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController -import com.kyant.backdrop.backdrop -import com.kyant.backdrop.rememberBackdrop -import dev.chrisbanes.haze.HazeEffectScope +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.ConfirmationDialog +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledSwitch +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.ATTHandles @@ -108,7 +102,8 @@ fun HearingAidScreen(navController: NavController) { val aacpManager = remember { ServiceManager.getService()?.aacpManager } val showDialog = remember { mutableStateOf(false) } - val backdrop = rememberBackdrop() + val backdrop = rememberLayerBackdrop() + val initialLoad = remember { mutableStateOf(true) } val hearingAidEnabled = remember { val aidStatus = aacpManager?.controlCommandStatusList?.find { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID } @@ -116,67 +111,28 @@ fun HearingAidScreen(navController: NavController) { mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte())) } - Scaffold( - containerColor = if (isSystemInDarkTheme()) Color( - 0xFF000000 - ) else Color( - 0xFFF2F2F7 - ), - topBar = { - val darkMode = isSystemInDarkTheme() - val mDensity = remember { mutableFloatStateOf(1f) } - - CenterAlignedTopAppBar( - title = { - Text( - text = stringResource(R.string.hearing_aid), - style = TextStyle( - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - color = if (darkMode) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - }, - modifier = Modifier - .hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = - if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f - }) - .drawBehind { - mDensity.floatValue = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (verticalScrollState.value > 60.dp.value * density) { - drawLine( - if (darkMode) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) + StyledScaffold( + title = stringResource(R.string.hearing_aid), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) }, - snackbarHost = { SnackbarHost(snackbarHostState) }, - modifier = Modifier - .backdrop(backdrop) - ) { paddingValues -> + actionButtons = emptyList(), + snackbarHostState = snackbarHostState, + ) { spacerHeight -> Column( modifier = Modifier + .layerBackdrop(backdrop) .hazeSource(hazeState) .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) .verticalScroll(verticalScrollState), verticalArrangement = Arrangement.spacedBy(8.dp) ) { + Spacer(modifier = Modifier.height(spacerHeight)) + val hearingAidListener = remember { object : AACPManager.ControlCommandListener { override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { @@ -206,14 +162,15 @@ fun HearingAidScreen(navController: NavController) { } } - fun onChange(value: Boolean) { - if (value) { + LaunchedEffect(hearingAidEnabled.value) { + if (hearingAidEnabled.value && !initialLoad.value) { showDialog.value = true - } else { + } else if (!hearingAidEnabled.value) { aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x02)) aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x02.toByte()) hearingAidEnabled.value = false } + initialLoad.value = false } fun onAdjustPhoneChange(value: Boolean) { @@ -241,37 +198,15 @@ fun HearingAidScreen(navController: NavController) { modifier = Modifier .fillMaxWidth() .background(backgroundColor, RoundedCornerShape(14.dp)) - ) { - val isDarkThemeLocal = isSystemInDarkTheme() - var backgroundColorHA by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColorHA by animateColorAsState(targetValue = backgroundColorHA, animationSpec = tween(durationMillis = 500)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColorHA = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColorHA = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - onChange(value = !hearingAidEnabled.value) - } - ) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = stringResource(R.string.hearing_aid), modifier = Modifier.weight(1f), fontSize = 16.sp, color = textColor) - StyledSwitch( - checked = hearingAidEnabled.value, - onCheckedChange = { - onChange(value = it) - }, + .clip( + RoundedCornerShape(14.dp) ) - } - + ) { + StyledToggle( + label = stringResource(R.string.hearing_aid), + checkedState = hearingAidEnabled, + independent = false + ) HorizontalDivider( thickness = 1.5.dp, color = Color(0x40888888), @@ -299,7 +234,6 @@ fun HearingAidScreen(navController: NavController) { ) } } - Text( text = stringResource(R.string.hearing_aid_description), style = TextStyle( @@ -310,7 +244,6 @@ fun HearingAidScreen(navController: NavController) { ), modifier = Modifier.padding(horizontal = 8.dp) ) - Spacer(modifier = Modifier.height(16.dp)) AccessibilityToggle( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt index 34592668..95879e36 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt @@ -39,25 +39,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Clear -import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator -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.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -78,14 +71,18 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.edit import androidx.navigation.NavController +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.utils.RadareOffsetFinder -import androidx.core.content.edit +@ExperimentalHazeMaterialsApi @OptIn(ExperimentalMaterial3Api::class) @Composable fun Onboarding(navController: NavController, activityContext: Context) { @@ -104,7 +101,6 @@ fun Onboarding(navController: NavController, activityContext: Context) { var moduleEnabled by remember { mutableStateOf(false) } var bluetoothToggled by remember { mutableStateOf(false) } - var showMenu by remember { mutableStateOf(false) } var showSkipDialog by remember { mutableStateOf(false) } fun checkRootAccess() { @@ -155,55 +151,27 @@ fun Onboarding(navController: NavController, activityContext: Context) { isComplete = true } } - - Scaffold( - topBar = { - CenterAlignedTopAppBar( - title = { - Text( - "Setting Up", - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Medium - ) - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent - ), - actions = { - Box { - IconButton(onClick = { showMenu = true }) { - Icon( - imageVector = Icons.Default.MoreVert, - contentDescription = "More Options" - ) - } - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false } - ) { - DropdownMenuItem( - text = { Text("Skip Setup") }, - onClick = { - showMenu = false - showSkipDialog = true - } - ) - } - } - } - ) - }, - containerColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) - ) { paddingValues -> + StyledScaffold( + title = "Setting Up", + actionButtons = listOf( + { + StyledIconButton( + onClick = { + showSkipDialog = true + }, + icon = "􀊋", + darkMode = isDarkTheme + ) + } + ) + ) { spacerHeight -> Column( modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(16.dp), + .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(spacerHeight)) Card( modifier = Modifier.fillMaxWidth(), @@ -300,7 +268,8 @@ fun Onboarding(navController: NavController, activityContext: Context) { Spacer(modifier = Modifier.height(24.dp)) AnimatedContent( - targetState = if (hasStarted) getStatusTitle(progressState, isComplete, moduleEnabled, bluetoothToggled) else "Setup Required", + targetState = if (hasStarted) getStatusTitle(progressState, + moduleEnabled, bluetoothToggled) else "Setup Required", transitionSpec = { fadeIn() togetherWith fadeOut() } ) { text -> Text( @@ -319,7 +288,7 @@ fun Onboarding(navController: NavController, activityContext: Context) { AnimatedContent( targetState = if (hasStarted) - getStatusDescription(progressState, isComplete, moduleEnabled, bluetoothToggled) + getStatusDescription(progressState, moduleEnabled, bluetoothToggled) else "AirPods functionality requires one-time setup for hooking into Bluetooth library", transitionSpec = { fadeIn() togetherWith fadeOut() } @@ -608,7 +577,6 @@ private fun StatusIcon( private fun getStatusTitle( state: RadareOffsetFinder.ProgressState, - isComplete: Boolean, moduleEnabled: Boolean, bluetoothToggled: Boolean ): String { @@ -635,7 +603,6 @@ private fun getStatusTitle( private fun getStatusDescription( state: RadareOffsetFinder.ProgressState, - isComplete: Boolean, moduleEnabled: Boolean, bluetoothToggled: Boolean ): String { @@ -660,6 +627,7 @@ private fun getStatusDescription( } } +@ExperimentalHazeMaterialsApi @Preview @Composable fun OnboardingPreview() { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index 457a5963..37cff810 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -30,24 +30,19 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth 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.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -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 @@ -68,14 +63,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.constants.StemAction import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager import kotlin.experimental.and import kotlin.io.encoding.ExperimentalEncodingApi -@Composable() +@Composable fun RightDivider() { HorizontalDivider( thickness = 1.5.dp, @@ -85,7 +83,7 @@ fun RightDivider() { ) } -@Composable() +@Composable fun RightDividerNoIcon() { HorizontalDivider( thickness = 1.5.dp, @@ -95,6 +93,7 @@ fun RightDividerNoIcon() { ) } +@ExperimentalHazeMaterialsApi @OptIn(ExperimentalMaterial3Api::class) @Composable fun LongPress(navController: NavController, name: String) { @@ -114,60 +113,27 @@ fun LongPress(navController: NavController, name: String) { } val context = LocalContext.current val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) - val deviceName = sharedPreferences.getString("name", "AirPods Pro") val prefKey = if (name.lowercase() == "left") "left_long_press_action" else "right_long_press_action" val longPressActionPref = sharedPreferences.getString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name) Log.d("PressAndHoldSettingsScreen", "Long press action preference ($prefKey): $longPressActionPref") var longPressAction by remember { mutableStateOf(StemAction.valueOf(longPressActionPref ?: StemAction.CYCLE_NOISE_CONTROL_MODES.name)) } - Scaffold( - topBar = { - CenterAlignedTopAppBar( - title = { - Text( - 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( - deviceName?: "AirPods Pro", - 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 - ) + StyledScaffold( + title = name, + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) - }, - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) - else Color(0xFFF2F2F7), - ) { paddingValues -> + } + ) { spacerHeight -> val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Column ( modifier = Modifier .fillMaxSize() - .padding(paddingValues = paddingValues) - .padding(horizontal = 16.dp) .padding(top = 8.dp) ) { + Spacer(modifier = Modifier.height(spacerHeight)) Column( modifier = Modifier .fillMaxWidth() @@ -221,33 +187,37 @@ fun LongPress(navController: NavController, name: String) { it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION }?.value?.takeIf { it.isNotEmpty() }?.get(0) val offListeningMode = offListeningModeValue == 1.toByte() - LongPressElement( + ListeningModeElement( name = "Off", enabled = offListeningMode, resourceId = R.drawable.noise_cancellation, isFirst = true) if (offListeningMode) RightDivider() - LongPressElement( + ListeningModeElement( name = "Transparency", resourceId = R.drawable.transparency, isFirst = !offListeningMode) RightDivider() - LongPressElement( + ListeningModeElement( name = "Adaptive", resourceId = R.drawable.adaptive) RightDivider() - LongPressElement( + ListeningModeElement( name = "Noise Cancellation", resourceId = R.drawable.noise_cancellation, isLast = true) } + Spacer(modifier = Modifier.height(4.dp)) 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), + text = "Press and hold the stem to cycle between the selected noise control modes.", + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), modifier = Modifier - .padding(start = 16.dp, top = 4.dp) + .padding(horizontal = 8.dp) ) } } @@ -258,7 +228,7 @@ fun LongPress(navController: NavController, name: String) { } @Composable -fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isFirst: Boolean = false, isLast: Boolean = false) { +fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, isFirst: Boolean = false, isLast: Boolean = false) { val bit = when (name) { "Off" -> 0x01 "Transparency" -> 0x02 @@ -280,7 +250,7 @@ fun LongPressElement(name: String, enabled: Boolean = true, resourceId: Int, isF val isChecked = (byteValue.toInt() and bit) != 0 val checked = remember { mutableStateOf(isChecked) } - Log.d("PressAndHoldSettingsScreen", "LongPressElement: $name, checked: ${checked.value}, byteValue: ${byteValue.toInt()}, in bits: ${byteValue.toInt().toString(2)}") + Log.d("PressAndHoldSettingsScreen", "ListeningModeElement: $name, checked: ${checked.value}, byteValue: ${byteValue.toInt()}, in bits: ${byteValue.toInt().toString(2)}") val darkMode = isSystemInDarkTheme() val textColor = if (darkMode) Color.White else Color.Black val desc = when (name) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt index bcb4dc53..8d47612f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt @@ -25,6 +25,7 @@ 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.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -32,23 +33,16 @@ 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.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.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color @@ -58,21 +52,21 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.content.edit import androidx.navigation.NavController +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.services.ServiceManager import kotlin.io.encoding.ExperimentalEncodingApi -import androidx.core.content.edit -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) @Composable fun RenameScreen(navController: NavController) { val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) @@ -87,54 +81,21 @@ fun RenameScreen(navController: NavController) { name.value = name.value.copy(selection = TextRange(name.value.text.length)) } - 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.text, - 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 - ) + StyledScaffold( + title = stringResource(R.string.name), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) }, - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) - else Color(0xFFF2F2F7), - ) { paddingValues -> - Column ( + ) { spacerHeight -> + Column( modifier = Modifier .fillMaxSize() - .padding(paddingValues = paddingValues) - .padding(horizontal = 16.dp) - .padding(top = 8.dp) ) { + Spacer(modifier = Modifier.height(spacerHeight)) val isDarkTheme = isSystemInDarkTheme() val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt index e47e417f..25ef6cd5 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -20,10 +20,7 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint import android.util.Log -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -39,61 +36,38 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.CenterAlignedTopAppBar -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf 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.drawBehind -import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font 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 dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledSlider import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.ATTHandles -import me.kavishdevar.librepods.utils.ATTManager import me.kavishdevar.librepods.utils.RadareOffsetFinder import me.kavishdevar.librepods.utils.TransparencySettings import me.kavishdevar.librepods.utils.parseTransparencySettingsResponse @@ -111,8 +85,6 @@ fun TransparencySettingsScreen(navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val verticalScrollState = rememberScrollState() - val hazeState = remember { HazeState() } - val snackbarHostState = remember { SnackbarHostState() } val attManager = ServiceManager.getService()?.attManager ?: return val aacpManager = remember { ServiceManager.getService()?.aacpManager } val isSdpOffsetAvailable = @@ -122,65 +94,24 @@ fun TransparencySettingsScreen(navController: NavController) { val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) - Scaffold( - containerColor = if (isSystemInDarkTheme()) Color( - 0xFF000000 - ) else Color( - 0xFFF2F2F7 - ), - topBar = { - val darkMode = isSystemInDarkTheme() - val mDensity = remember { mutableFloatStateOf(1f) } - - CenterAlignedTopAppBar( - title = { - Text( - text = stringResource(R.string.customize_transparency_mode), - style = TextStyle( - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - color = if (darkMode) Color.White else Color.Black, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ) - ) - }, - modifier = Modifier - .hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = - if (verticalScrollState.value > 60.dp.value * mDensity.floatValue) 1f else 0f - }) - .drawBehind { - mDensity.floatValue = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (verticalScrollState.value > 60.dp.value * density) { - drawLine( - if (darkMode) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ) + StyledScaffold( + title = stringResource(R.string.customize_transparency_mode), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) - }, - snackbarHost = { SnackbarHost(snackbarHostState) } - ) { paddingValues -> + } + ){ spacerHeight, hazeState -> Column( modifier = Modifier .hazeSource(hazeState) .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) .verticalScroll(verticalScrollState), verticalArrangement = Arrangement.spacedBy(16.dp) ) { + Spacer(modifier = Modifier.height(spacerHeight)) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val enabled = remember { mutableStateOf(false) } @@ -523,4 +454,4 @@ fun TransparencySettingsScreen(navController: NavController) { } } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt index 64bf6ffc..25485b91 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt @@ -1,5 +1,5 @@ /* - * LibrePods - AirPods liberated from Apple's ecosystem + * LibrePods - AirPods liberated from Apple’s ecosystem * * Copyright (C) 2025 LibrePods contributors * @@ -23,9 +23,7 @@ import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -49,29 +47,23 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Share import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -80,11 +72,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.scale -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -97,17 +85,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.FileProvider import androidx.navigation.NavController -import dev.chrisbanes.haze.HazeEffectScope -import dev.chrisbanes.haze.HazeState -import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource -import dev.chrisbanes.haze.materials.CupertinoMaterials import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledIconButton +import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.utils.LogCollector import java.io.File import java.text.SimpleDateFormat @@ -134,8 +120,6 @@ fun CustomIconButton( fun TroubleshootingScreen(navController: NavController) { val context = LocalContext.current val scrollState = rememberScrollState() - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() - val hazeState = remember { HazeState() } val coroutineScope = rememberCoroutineScope() val logCollector = remember { LogCollector(context) } @@ -161,27 +145,6 @@ fun TroubleshootingScreen(navController: NavController) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false) var showBottomSheet by remember { mutableStateOf(false) } - val sheetProgress by remember { - derivedStateOf { - if (!showBottomSheet) 0f else sheetState.targetValue.ordinal.toFloat() / 2f - } - } - - val contentScaleFactor by remember { - derivedStateOf { - 1.0f - (0.12f * sheetProgress) - } - } - - val contentScale by animateFloatAsState( - targetValue = contentScaleFactor, - animationSpec = spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessMedium - ), - label = "contentScale" - ) - val backgroundColor = if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isSystemInDarkTheme()) Color.White else Color.Black val accentColor = if (isSystemInDarkTheme()) Color(0xFF007AFF) else Color(0xFF3C6DF5) @@ -189,7 +152,6 @@ fun TroubleshootingScreen(navController: NavController) { var instructionText by remember { mutableStateOf("") } val isDarkTheme = isSystemInDarkTheme() - var mDensity by remember { mutableFloatStateOf(0f) } LaunchedEffect(Unit) { withContext(Dispatchers.IO) { @@ -249,75 +211,23 @@ fun TroubleshootingScreen(navController: NavController) { Box( modifier = Modifier.fillMaxSize() ) { - Scaffold( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - scaleX = contentScale - scaleY = contentScale - transformOrigin = androidx.compose.ui.graphics.TransformOrigin(0.5f, 0.3f) - }, - topBar = { - CenterAlignedTopAppBar( - modifier = Modifier.hazeEffect( - state = hazeState, - style = CupertinoMaterials.thick(), - block = fun HazeEffectScope.() { - alpha = if (scrollState.value > 60.dp.value * mDensity) 1f else 0f - }) - .drawBehind { - mDensity = density - val strokeWidth = 0.7.dp.value * density - val y = size.height - strokeWidth / 2 - if (scrollState.value > 60.dp.value * density) { - drawLine( - if (isDarkTheme) Color.DarkGray else Color.LightGray, - Offset(0f, y), - Offset(size.width, y), - strokeWidth - ) - } - }, - title = { - Text( - text = stringResource(R.string.troubleshooting), - fontFamily = FontFamily(Font(R.font.sf_pro)), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - }, - navigationIcon = { - TextButton( - onClick = { - navController.popBackStack() - }, - shape = RoundedCornerShape(8.dp), - ) { - Icon( - Icons.AutoMirrored.Filled.KeyboardArrowLeft, - contentDescription = "Back", - tint = accentColor, - modifier = Modifier.scale(1.5f) - ) - } - }, - colors = TopAppBarDefaults.centerAlignedTopAppBarColors( - containerColor = Color.Transparent - ), - scrollBehavior = scrollBehavior + StyledScaffold( + title = stringResource(R.string.troubleshooting), + navigationButton = { + StyledIconButton( + onClick = { navController.popBackStack() }, + icon = "􀯶", + darkMode = isDarkTheme ) - }, - containerColor = if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7), - ) { paddingValues -> + } + ){ spacerHeight, hazeState -> Column( modifier = Modifier .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp) .verticalScroll(scrollState) .hazeSource(state = hazeState) ) { - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(spacerHeight)) Text( text = stringResource(R.string.saved_logs).uppercase(), @@ -706,7 +616,9 @@ fun TroubleshootingScreen(navController: NavController) { Button( onClick = { selectedLogFile?.let { file -> - saveLauncher.launch("airpods_log_${System.currentTimeMillis()}.txt") + saveLauncher.launch( + file.absolutePath + ) } }, shape = RoundedCornerShape(10.dp), @@ -977,7 +889,7 @@ fun TroubleshootingScreen(navController: NavController) { Button( onClick = { selectedLogFile?.let { file -> - saveLauncher.launch("airpods_log_${System.currentTimeMillis()}.txt") + saveLauncher.launch(file.absolutePath) } }, shape = RoundedCornerShape(10.dp), diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 6200348d..73613fbb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -1,5 +1,5 @@ /* - * LibrePods - AirPods liberated from Apple's ecosystem + * LibrePods - AirPods liberated from Apple’s ecosystem * * Copyright (C) 2025 LibrePods contributors * @@ -202,10 +202,10 @@ class AACPManager { var eqData = FloatArray(8) { 0.0f } private set - + var eqOnPhone: Boolean = false private set - + var eqOnMedia: Boolean = false private set @@ -528,12 +528,23 @@ class AACPManager { val packetString = packet.decodeToString() val sender = packet.sliceArray(6..11).reversedArray().joinToString(":") { "%02X".format(it) } - if (connectedDevices.find { it.mac == sender }?.type == null && packetString.contains("btName")) { - val nameStartIndex = packetString.indexOf("btName") + 7 - val nameEndIndex = if (packetString.contains("other")) (packetString.indexOf("otherDevice") - 2) else (packetString.indexOf("nearbyAudio") - 2) - val name = packet.sliceArray(nameStartIndex..nameEndIndex).decodeToString() - connectedDevices.find { it.mac == sender }?.type = name - Log.d(TAG, "Device $sender is named $name") + // if (connectedDevices.find { it.mac == sender }?.type == null && packetString.contains("btName")) { + // val nameStartIndex = packetString.indexOf("btName") + 8 + // val nameEndIndex = if (packetString.contains("other")) (packetString.indexOf("otherDevice") - 1) else (packetString.indexOf("nearbyAudio") - 1) + // val name = packet.sliceArray(nameStartIndex..nameEndIndex).decodeToString() + // connectedDevices.find { it.mac == sender }?.type = name + // Log.d(TAG, "Device $sender is named $name") + // } // doesn't work, it's different for Mac and iPad. just hardcoding for now + if ("iPad" in packetString) { + connectedDevices.find { it.mac == sender }?.type = "iPad" + } else if ("Mac" in packetString) { + connectedDevices.find { it.mac == sender }?.type = "Mac" + } else if ("iPhone" in packetString) { // not sure if this is it - don't have an iphone + connectedDevices.find { it.mac == sender }?.type = "iPhone" + } else if ("Linux" in packetString) { + connectedDevices.find { it.mac == sender }?.type = "Linux" + } else if ("Android" in packetString) { + connectedDevices.find { it.mac == sender }?.type = "Android" } Log.d(TAG, "Smart Routing Response from $sender: $packetString, type: ${connectedDevices.find { it.mac == sender }?.type}") if (packetString.contains("SetOwnershipToFalse")) { @@ -568,7 +579,7 @@ class AACPManager { val eq2 = ByteBuffer.wrap(packet, 44, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() val eq3 = ByteBuffer.wrap(packet, 76, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() val eq4 = ByteBuffer.wrap(packet, 108, 32).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer() - + // for now, taking just the first EQ eqData = FloatArray(8) { i -> eq1.get(i) } Log.d(TAG, "EQ Data set to: ${eqData.toList()}, eqOnPhone: $eqOnPhone, eqOnMedia: $eqOnMedia") @@ -580,17 +591,6 @@ class AACPManager { } } - fun createEqualizerDataPacket(eqData: FloatArray, eqOnPhone: Boolean, eqOnMedia: Boolean): ByteArray { - val opcode = byteArrayOf(Opcodes.EQ_DATA, 0x00) - val identifier = byteArrayOf(0x84.toByte(), 0x00) - val something = byteArrayOf(0x02, 0x02) - val phoneFlag = if (eqOnPhone) 0x01.toByte() else 0x00.toByte() - val mediaFlag = if (eqOnMedia) 0x01.toByte() else 0x00.toByte() - val buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN) - eqData.forEach { buffer.putFloat(it) } - return opcode + identifier + something + byteArrayOf(phoneFlag, mediaFlag) + buffer.array() + buffer.array() + buffer.array() + buffer.array() - } - fun sendNotificationRequest(): Boolean { return sendDataPacket(createRequestNotificationPacket()) } @@ -853,11 +853,11 @@ class AACPManager { Log.w(TAG, "Cannot send Media Information packet: No connected device found") return false } - Log.d(TAG, "Sending Media Information packet to ${targetMac ?: "unknown device"}") + Log.d(TAG, "Sending Media Information packet to $targetMac") return sendDataPacket( createMediaInformationPacket( selfMacAddress, - targetMac ?: return false, + targetMac, streamingState ) ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt index 15b6144c..41c6116f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/ATTManager.kt @@ -186,9 +186,7 @@ class ATTManager(private val device: BluetoothDevice) { private fun readResponse(timeoutMs: Long = 2000): ByteArray { try { val resp = responses.poll(timeoutMs, TimeUnit.MILLISECONDS) - if (resp == null) { - throw IllegalStateException("No response read from ATT socket within $timeoutMs ms") - } + ?: throw IllegalStateException("No response read from ATT socket within $timeoutMs ms") Log.d(TAG, "readResponse: ${resp.joinToString(" ") { String.format("%02X", it) }}") return resp.copyOfRange(1, resp.size) } catch (e: InterruptedException) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt index 4f0ed98e..633ee466 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/BluetoothCryptography.kt @@ -1,7 +1,7 @@ /* - * LibrePods - AirPods liberated from Apple's ecosystem + * LibrePods - AirPods liberated from Apple’s ecosystem * - * Copyright (C) 2025 LibrePods Contributors + * Copyright (C) 2025 LibrePods contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/DragUtils.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/DragUtils.kt new file mode 100644 index 00000000..55b1eef2 --- /dev/null +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/DragUtils.kt @@ -0,0 +1,102 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package me.kavishdevar.librepods.utils + +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.AwaitPointerEventScope +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerId +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed +import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.util.fastFirstOrNull + +suspend fun PointerInputScope.inspectDragGestures( + onDragStart: (down: PointerInputChange) -> Unit = {}, + onDragEnd: (change: PointerInputChange) -> Unit = {}, + onDragCancel: () -> Unit = {}, + onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit +) { + awaitEachGesture { + val initialDown = awaitFirstDown(false, PointerEventPass.Initial) + + val down = awaitFirstDown(false) + + onDragStart(down) + onDrag(initialDown, Offset.Zero) + val upEvent = + drag( + pointerId = initialDown.id, + onDrag = { onDrag(it, it.positionChange()) } + ) + if (upEvent == null) { + onDragCancel() + } else { + onDragEnd(upEvent) + } + } +} + +private suspend inline fun AwaitPointerEventScope.drag( + pointerId: PointerId, + onDrag: (PointerInputChange) -> Unit +): PointerInputChange? { + val isPointerUp = currentEvent.changes.fastFirstOrNull { it.id == pointerId }?.pressed != true + if (isPointerUp) { + return null + } + var pointer = pointerId + while (true) { + val change = awaitDragOrUp(pointer) ?: return null + if (change.isConsumed) { + return null + } + if (change.changedToUpIgnoreConsumed()) { + return change + } + onDrag(change) + pointer = change.id + } +} + +private suspend inline fun AwaitPointerEventScope.awaitDragOrUp( + pointerId: PointerId +): PointerInputChange? { + var pointer = pointerId + while (true) { + val event = awaitPointerEvent() + val dragEvent = event.changes.fastFirstOrNull { it.id == pointer } ?: return null + if (dragEvent.changedToUpIgnoreConsumed()) { + val otherDown = event.changes.fastFirstOrNull { it.pressed } + if (otherDown == null) { + return dragEvent + } else { + pointer = otherDown.id + } + } else { + val hasDragged = dragEvent.previousPosition != dragEvent.position + if (hasDragged) { + return dragEvent + } + } + } +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt index b0f2e245..804d4cb6 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureDetector.kt @@ -1,3 +1,21 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + @file:OptIn(ExperimentalEncodingApi::class) package me.kavishdevar.librepods.utils diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt index 6d2d0b2e..b7d40684 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/GestureFeedback.kt @@ -1,3 +1,21 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + @file:Suppress("PrivatePropertyName") package me.kavishdevar.librepods.utils diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/HeadOrientation.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/HeadOrientation.kt index 859f49b7..ad2d4184 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/HeadOrientation.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/HeadOrientation.kt @@ -1,3 +1,21 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package me.kavishdevar.librepods.utils import kotlinx.coroutines.flow.MutableStateFlow diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt index 0e57c4b1..bd6df5de 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/IslandWindow.kt @@ -53,7 +53,6 @@ import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.TextView import android.widget.VideoView -import androidx.core.content.ContextCompat.getString import androidx.core.net.toUri import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.SpringAnimation diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt index cc59214e..02c9235d 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt @@ -1,5 +1,5 @@ /* - * LibrePods - AirPods liberated from Apple's ecosystem + * LibrePods - AirPods liberated from Apple’s ecosystem * * Copyright (C) 2025 LibrePods contributors * diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt index 6cc06b52..afc33af2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/RadareOffsetFinder.kt @@ -462,7 +462,8 @@ class RadareOffsetFinder(context: Context) { // findAndSaveL2cuProcessCfgReqOffset(libraryPath, envSetup) // findAndSaveL2cCsmConfigOffset(libraryPath, envSetup) // findAndSaveL2cuSendPeerInfoReqOffset(libraryPath, envSetup) - findAndSaveSdpOffset(libraryPath, envSetup) + + // findAndSaveSdpOffset(libraryPath, envSetup) Should not be run by default, only when user asks for it. } catch (e: Exception) { Log.e(TAG, "Failed to find function offset", e) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt index fdf84bc0..0ceaa9ea 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/TransparencyUtils.kt @@ -1,6 +1,23 @@ +/* + * LibrePods - AirPods liberated from Apple’s ecosystem + * + * Copyright (C) 2025 LibrePods contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package me.kavishdevar.librepods.utils -import android.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -10,8 +27,6 @@ import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder -private const val TAG = "TransparencyUtils" - data class TransparencySettings( val enabled: Boolean, val leftEQ: FloatArray, @@ -67,9 +82,8 @@ data class TransparencySettings( } } -fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings? { - val settingsData = data - val buffer = ByteBuffer.wrap(settingsData).order(ByteOrder.LITTLE_ENDIAN) +fun parseTransparencySettingsResponse(data: ByteArray): TransparencySettings { + val buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN) val enabled = buffer.float diff --git a/android/app/src/main/res/drawable/pro_2_case.png b/android/app/src/main/res/drawable/pro_2_case.png index f104605c43453fa5551523f7dbd2ed799377e74b..d904a2fede6ff666a389719067b4094c4b351275 100644 GIT binary patch literal 53180 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%0DH38UI3U242 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 RoaoyVamnan+4<}D{s*PWd6WPE diff --git a/android/app/src/main/res/values-zh-rCN/strings.xml b/android/app/src/main/res/values-zh-rCN/strings.xml index 3af7d48a..99720ca6 100644 --- a/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/android/app/src/main/res/values-zh-rCN/strings.xml @@ -23,8 +23,6 @@ 当你开始与他人交谈时,会降低媒体音量并减少背景噪音。 个性化音量 根据环境自动调整媒体音量。 - 减少噪音 - 增加噪音 单只 AirPod 主动降噪 仅佩戴一只 AirPod 时也能开启主动降噪。 音量控制 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 8634953d..b89fbf9f 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -8,6 +8,7 @@ Tone Volume Audio Adaptive Audio + Customize Adaptive Audio Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise. Buds Case @@ -26,14 +27,12 @@ Lowers media volume and reduces background noise when you start speaking to other people. Personalized Volume Adjusts the volume of media in response to your environment. - Less Noise - More Noise Noise Cancellation with Single AirPod Allow AirPods to be put in noise cancellation mode when only one AirPods is in your ear. Volume Control Adjust the volume by swiping up or down on the sensor located on the AirPods Pro stem. AirPods not connected - 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!) + Please connect your AirPods to access settings. Back Customizations Relative volume @@ -135,4 +134,40 @@ AirPods Pro can use the results of a hearing test to make adjustments that improve the clarity of music, video, and calls. Adjust Music and Video Adjust Calls + Widget + Show phone battery in widget + Display your phone\'s battery level in the widget alongside AirPods battery + Connection Mode + BLE Only Mode + Only use Bluetooth Low Energy for battery data and ear detection. Disables advanced features requiring L2CAP connection. + Conversational Awareness Volume + Quick Settings Tile + Open dialog for controlling + If disabled, clicking on the QS will cycle through modes. If enabled, it will show a dialog for controlling noise control mode and conversational awareness + Disconnect AirPods when not wearing + You will still be able to control them with the app - this just disconnects the audio. + Advanced Options + Set Identity Resolving Key (IRK) + Manually set the IRK value used for resolving BLE random addresses + Set Encryption Key + Manually set the ENC_KEY value used for decrypting BLE advertisements + Use alternate head tracking packets + Enable this if head tracking doesn\'t work for you. This sends different data to AirPods for requesting/stopping head tracking data. + Act as an Apple device + Enables multi-device connectivity and Accessibility features like customizing transparency mode (amplification, tone, ambient noise reduction, conversation boost, and EQ) + Might be unstable!! A maximum of two devices can be connected to your AirPods. If you are using with an Apple device like an iPad or Mac, then please connect that device first and then your Android. + Reset Hook Offset + This will clear the current hook offset and require you to go through the setup process again. Are you sure you want to continue? + Reset + Hook offset has been reset. Redirecting to setup... + Failed to reset hook offset + IRK has been set successfully + Encryption key has been set successfully + IRK Hex Value + ENC_KEY Hex Value + Enter 16-byte IRK as hex string (32 characters): + Enter 16-byte ENC_KEY as hex string (32 characters): + Must be exactly 32 hex characters + Error converting hex: + Found offset please restart the Bluetooth process From ab550960516f4de60037ee504b184e1bcb9236b0 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 26 Sep 2025 03:26:25 +0530 Subject: [PATCH 38/72] android: move padding to StyledScaffold's content because haze needs it --- .../me/kavishdevar/librepods/composables/StyledScaffold.kt | 7 +------ .../librepods/screens/AccessibilitySettingsScreen.kt | 3 ++- .../librepods/screens/AdaptiveStrengthScreen.kt | 4 +++- .../kavishdevar/librepods/screens/AirPodsSettingsScreen.kt | 1 + .../me/kavishdevar/librepods/screens/AppSettingsScreen.kt | 1 + .../java/me/kavishdevar/librepods/screens/DebugScreen.kt | 1 + .../me/kavishdevar/librepods/screens/HeadTrackingScreen.kt | 1 + .../librepods/screens/HearingAidAdjustmentsScreen.kt | 4 +++- .../me/kavishdevar/librepods/screens/HearingAidScreen.kt | 3 ++- .../java/me/kavishdevar/librepods/screens/Onboarding.kt | 3 ++- .../librepods/screens/PressAndHoldSettingsScreen.kt | 1 + .../java/me/kavishdevar/librepods/screens/RenameScreen.kt | 1 + .../librepods/screens/TransparencySettingsScreen.kt | 3 ++- .../kavishdevar/librepods/screens/TroubleshootingScreen.kt | 1 + 14 files changed, 22 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt index acf70850..a72f1523 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledScaffold.kt @@ -116,12 +116,7 @@ fun StyledScaffold( } } - Box( - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - content(topPadding + 64.dp, hazeState) - } + content(topPadding + 64.dp, hazeState) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index e39798cd..d5615c33 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -164,7 +164,8 @@ fun AccessibilitySettingsScreen(navController: NavController) { modifier = Modifier .fillMaxSize() .hazeSource(hazeState) - .verticalScroll(rememberScrollState()), + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt index bd0bbcf8..da0605a2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -106,7 +107,8 @@ fun AdaptiveStrengthScreen(navController: NavController) { ) { spacerHeight -> Column( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 5dd112dc..68041a29 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -215,6 +215,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, modifier = Modifier .fillMaxSize() .hazeSource(hazeState) + .padding(horizontal = 16.dp) .verticalScroll(rememberScrollState()) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index de81d468..353e9c93 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -211,6 +211,7 @@ fun AppSettingsScreen(navController: NavController) { .fillMaxSize() .verticalScroll(scrollState) .hazeSource(state = hazeState) + .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt index cdacf701..f9dbdab8 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt @@ -353,6 +353,7 @@ fun DebugScreen(navController: NavController) { .fillMaxSize() .hazeSource(hazeState) .navigationBarsPadding() + .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) LazyColumn( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt index 3ec0ac34..f4621d8e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt @@ -153,6 +153,7 @@ fun HeadTrackingScreen(navController: NavController) { modifier = Modifier .fillMaxSize() .padding(top = 8.dp) + .padding(horizontal = 16.dp) .verticalScroll(scrollState) .hazeSource(state = hazeState) ) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 122b3649..1b265649 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -91,7 +92,8 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController modifier = Modifier .hazeSource(hazeState) .fillMaxSize() - .verticalScroll(verticalScrollState), + .verticalScroll(verticalScrollState) + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 7153af4a..d14fc940 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -128,7 +128,8 @@ fun HearingAidScreen(navController: NavController) { .layerBackdrop(backdrop) .hazeSource(hazeState) .fillMaxSize() - .verticalScroll(verticalScrollState), + .verticalScroll(verticalScrollState) + .padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt index 95879e36..e6420bbe 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt @@ -167,7 +167,8 @@ fun Onboarding(navController: NavController, activityContext: Context) { ) { spacerHeight -> Column( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index 37cff810..032cbc47 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -132,6 +132,7 @@ fun LongPress(navController: NavController, name: String) { modifier = Modifier .fillMaxSize() .padding(top = 8.dp) + .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) Column( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt index 8d47612f..9dd1f959 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt @@ -94,6 +94,7 @@ fun RenameScreen(navController: NavController) { Column( modifier = Modifier .fillMaxSize() + .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) val isDarkTheme = isSystemInDarkTheme() diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt index 25ef6cd5..565039e6 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -108,7 +108,8 @@ fun TransparencySettingsScreen(navController: NavController) { modifier = Modifier .hazeSource(hazeState) .fillMaxSize() - .verticalScroll(verticalScrollState), + .verticalScroll(verticalScrollState) + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt index 25485b91..1124ad53 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt @@ -226,6 +226,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxSize() .verticalScroll(scrollState) .hazeSource(state = hazeState) + .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) From 56307c98e35e4649ba96c8fa40a04f52ac95020a Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 26 Sep 2025 03:27:32 +0530 Subject: [PATCH 39/72] android: revert accidental capitalization on toggle label --- .../me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 68041a29..c8242eaf 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -294,7 +294,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, Spacer(modifier = Modifier.height(16.dp)) StyledToggle( - label = stringResource(R.string.off_listening_mode).uppercase(), + label = stringResource(R.string.off_listening_mode), controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, description = stringResource(R.string.off_listening_mode_description) ) From 8dc7a97c430bef25fbfbaf2380a924ec7831cf17 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Fri, 26 Sep 2025 14:03:47 +0530 Subject: [PATCH 40/72] android: update usages for toggle --- android/app/build.gradle.kts | 9 +- android/app/libs/backdrop-debug.aar | Bin 0 -> 109112 bytes android/app/libs/backdrop-release.aar | Bin 0 -> 103294 bytes .../librepods/composables/AudioSettings.kt | 8 +- .../composables/AutomaticConnectionSwitch.kt | 182 ------ .../composables/ConnectionSettings.kt | 23 +- .../composables/EarDetectionSwitch.kt | 173 ------ .../composables/LoudSoundReductionSwitch.kt | 169 ------ .../composables/PersonalizedVolumeSwitch.kt | 163 ------ .../composables/SinglePodANCSwitch.kt | 151 ----- .../librepods/composables/StyledSwitch.kt | 218 ++++++- .../librepods/composables/StyledToggle.kt | 245 +++++++- .../composables/VolumeControlSwitch.kt | 150 ----- .../screens/AccessibilitySettingsScreen.kt | 554 +++++++----------- .../librepods/screens/HeadTrackingScreen.kt | 3 +- .../screens/HearingAidAdjustmentsScreen.kt | 6 +- .../librepods/screens/HearingAidScreen.kt | 18 +- .../screens/TransparencySettingsScreen.kt | 13 +- android/app/src/main/res/values/strings.xml | 6 +- 19 files changed, 717 insertions(+), 1374 deletions(-) create mode 100644 android/app/libs/backdrop-debug.aar create mode 100644 android/app/libs/backdrop-release.aar delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/AutomaticConnectionSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2e7067ec..07a253ac 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -62,8 +62,11 @@ dependencies { implementation(libs.haze) implementation(libs.haze.materials) implementation(libs.androidx.dynamicanimation) - implementation(libs.androidx.compose.foundation.layout) - compileOnly(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) - implementation(fileTree(mapOf("dir" to "lib", "include" to listOf("*.aar")))) debugImplementation(libs.androidx.compose.ui.tooling) + implementation(libs.androidx.compose.foundation.layout) + // compileOnly(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) + // implementation(fileTree(mapOf("dir" to "lib", "include" to listOf("*.aar")))) + compileOnly(files("libs/libxposed-api-100.aar")) + debugImplementation(files("libs/backdrop-debug.aar")) + releaseImplementation(files("libs/backdrop-release.aar")) } diff --git a/android/app/libs/backdrop-debug.aar b/android/app/libs/backdrop-debug.aar new file mode 100644 index 0000000000000000000000000000000000000000..d8d5bff72349c2436a89444a4e0fdb2aaf0fd8dc GIT binary patch literal 109112 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 literal 0 HcmV?d00001 diff --git a/android/app/libs/backdrop-release.aar b/android/app/libs/backdrop-release.aar new file mode 100644 index 0000000000000000000000000000000000000000..306297bdaccf99790fcb1236582a6a86efd9e72d GIT binary patch literal 103294 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 =zV@SU#{Thd?`San@ z7AMy4RBBUH>N?&vd;`%^mRx!401AwDAnt&46{R$my3%a4 z1$4+#F`pjp;^|ta_>p)dSkf#FInJR;wq|^ICYyf7AERpKd8_dGYDQ=F&;ctKAxWbs z%&wSaNd>k%2AIEGu?TGyxdml~{ZzR%xRje8_Y6ZLeio*MEq}B*zC$)*)nlZZ&U-%o z_9G@ZBiP!plwiGpPg(DNGnv$YsU$iJOvb5j^tLO7v=IWTGP!!&*U}1`;URJe&2J|s zxH%HEaW6*)kh!IuEq$?+Hmo+M`5ac-CFU1QoKydbyTz6azw-`zSidn%>uiLEZ* zZhOE{3XzWz346E{M(;NrfRY#IxUyxnBpecE`VGUJ6--bi&Nw}U0X9qR_=h%lIxQ9) zFUG6?rgNmrcFt>a#8W%gd$HmXNO<2&@SGuc;Yhglk0Pw<5%>w)M9|tIa^dKWX~k!! zS8AuHwLg!+?Ycsq6XQGcbHSHe16jT_S_f#odw`DK{JMR@bbVHIWrM?9wAM$B<-&tS zS_T#A5CT0rD4SQ4B^WtJ7eEeQ#)3*^^OKCu#Z3DHJzhgcmaTFcEDA0!9You>Ld%vy z(OrEi#SDaDzzJ2*fMWN!>W?{K)h@U^3`~l ztbq~om5YdVz8VmQvCxLd)67dH%qp4NRqIt_vu4%QhAI@hDMCM`KhO;jxABt(IlnJc z>wMt_3O_vV68i$vM~D&euWhf1>{RR^x^ZiI?>PmEK@~!KgOdsv3Lc!9&m5t3fl11b zZc)R4q;-Z7FkOEb>ECKVm}X!`!t$>)e^_|08FsENly8T;i+6%%UNzTc;09IoibL|j z>F(7ar*LO=RAFZ8lH;I<4()(0jJTOiKB=iiu*~7>txcz;KX4!!uk^X1&wdX?TYz47?`h7mr(+NOl#A! z&Rb$RUAP3BA|>sq3?uIjIM=0B&mh5%fvaJmr(VC)_79L*_6rA_Q5ANX1KZrlrbKFpg-DyGinrT{#EV)+J* zvqolzc8fI}bdj0{4k~gBgX>vc?}9j|Ur^wMwq5>(3;)eXe&?fVa4i9^FM_IHihBoN zfrGgFvs23Qot}Hq{=l(QBn$G6|C7JBV~i>O&h_mbp1zn6<;Q_lf%S>cr~2c?bJ}I} zL0}hWl4r6jP7N4Wn|`1B!(-_=Wmm5Zq>&Ha&W0@v5lIUXc0G8KLyy9}dN}4h!a07w zz#XHtzpuiv)T{QpeK_V4;oOIWOX8O){@;u{m8+eZ+k3N*pFK9iwYL|paag{AJ2eC4 zaIjduBMz)0!~>ck#vylbM{qsnFQ5a2*!*T`lT5*V6StHhC?NxIer7MQ1GW!eBTNx{ zwQ#K1X4W6@aDFCfBZek*tXb1_aYo1k*l>~98D~QQ`N2le11up&aI{$3*y=1*#+p+O z2m{(7u5d6SfA%m!_~00sIxGBd7IOT)3I90=GQQ)h+QEW=P*H$@F#g|zpq#y_rI{tb z>3_{#ue7XPbk)$mrcGJg2{^`FGLmF}+Af5!*RqU(1XB<<$t|$QmCPmy>*Q@tZjvp? zn(b#vSt#ZIRaNSxfr5&FQWh&#EiR^7E+`ATdDgiL?$MhNs3G@#p3ZimXd9Cy+?_wt zPWOIV^Un2o+BVPindbLAGLSF;tAp@EwTL1c5iay@7UgCu6B92iKlHHBlkRY=URq0v z8V)h`I{(}BlX!QhTFxpTn1Y31OR!lj5k`-!Sja^hji5?FKEv*btg93s>BaFs+ilNp@42F zL##JY%Jubd#)EUBFS*D2MZw@|-dKIPm&DGG5;;2C8^&Ja?t

;5?)&{Qw8g905rp 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%0D8v}EqPQdd>vlz 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. - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -import android.content.Context.MODE_PRIVATE -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.isSystemInDarkTheme -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun AutomaticConnectionSwitch() { - val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) - val service = ServiceManager.getService()!! - - val sharedPreferenceKey = "automatic_connection_ctrl_cmd" - - val automaticConnectionEnabledValue = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - - var automaticConnectionEnabled by remember { - mutableStateOf( - if (automaticConnectionEnabledValue != null) { - automaticConnectionEnabledValue == 1.toByte() - } else { - sharedPreferences.getBoolean(sharedPreferenceKey, false) - } - ) - } - - fun updateAutomaticConnection(enabled: Boolean) { - automaticConnectionEnabled = enabled - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG.value, - enabled - ) - // todo: send other connected devices smartAudioRoutingDisabled or something, check packets again. - - sharedPreferences.edit() - .putBoolean(sharedPreferenceKey, enabled) - .apply() - } - - val automaticConnectionListener = object: AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - val enabled = newValue == 1.toByte() - automaticConnectionEnabled = enabled - - sharedPreferences.edit() - .putBoolean(sharedPreferenceKey, enabled) - .apply() - } - } - } - - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG, - automaticConnectionListener - ) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG, - automaticConnectionListener - ) - } - } - - val isDarkTheme = isSystemInDarkTheme() - 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() } - ) { - updateAutomaticConnection(!automaticConnectionEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.automatically_connect), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.automatically_connect_description), - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, - ) - } - StyledSwitch( - checked = automaticConnectionEnabled, - onCheckedChange = { - updateAutomaticConnection(it) - }, - ) - } -} - -@Preview -@Composable -fun AutomaticConnectionSwitchPreview() { - AutomaticConnectionSwitch() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt index d0e386cb..90106e4c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt @@ -30,9 +30,15 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlin.io.encoding.ExperimentalEncodingApi +import android.content.Context.MODE_PRIVATE +import me.kavishdevar.librepods.composables.StyledToggle +import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.R @Composable fun ConnectionSettings() { @@ -45,7 +51,13 @@ fun ConnectionSettings() { .background(backgroundColor, RoundedCornerShape(14.dp)) .padding(top = 2.dp) ) { - EarDetectionSwitch() + StyledToggle( + label = stringResource(R.string.ear_detection), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG, + sharedPreferenceKey = "automatic_ear_detection", + sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE), + independent = false + ) HorizontalDivider( thickness = 1.5.dp, color = Color(0x40888888), @@ -53,7 +65,14 @@ fun ConnectionSettings() { .padding(start = 12.dp, end = 0.dp) ) - AutomaticConnectionSwitch() + StyledToggle( + label = stringResource(R.string.automatically_connect), + description = stringResource(R.string.automatically_connect_description), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.AUTOMATIC_CONNECTION_CONFIG, + sharedPreferenceKey = "automatic_connection_ctrl_cmd", + sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE), + independent = false + ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt deleted file mode 100644 index 52eafc1f..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/EarDetectionSwitch.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -import android.content.Context.MODE_PRIVATE -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.isSystemInDarkTheme -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun EarDetectionSwitch() { - val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) - val service = ServiceManager.getService()!! - - val sharedPreferenceKey = "automatic_ear_detection" - - val earDetectionEnabledValue = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - - var earDetectionEnabled by remember { - mutableStateOf( - if (earDetectionEnabledValue != null) { - earDetectionEnabledValue == 1.toByte() - } else { - sharedPreferences.getBoolean(sharedPreferenceKey, false) - } - ) - } - - fun updateEarDetection(enabled: Boolean) { - earDetectionEnabled = enabled - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG.value, - enabled - ) - service.setEarDetection(enabled) - - sharedPreferences.edit() - .putBoolean(sharedPreferenceKey, enabled) - .apply() - } - - val earDetectionListener = object: AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - val enabled = newValue == 1.toByte() - earDetectionEnabled = enabled - - sharedPreferences.edit() - .putBoolean(sharedPreferenceKey, enabled) - .apply() - } - } - } - - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG, - earDetectionListener - ) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.EAR_DETECTION_CONFIG, - earDetectionListener - ) - } - } - - val isDarkTheme = isSystemInDarkTheme() - 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() } - ) { - updateEarDetection(!earDetectionEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.ear_detection), - fontSize = 16.sp, - color = textColor - ) - } - StyledSwitch( - checked = earDetectionEnabled, - onCheckedChange = { - updateEarDetection(it) - } - ) - } -} - -@Preview -@Composable -fun EarDetectionSwitchPreview() { - EarDetectionSwitch() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt deleted file mode 100644 index 5d319634..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/LoudSoundReductionSwitch.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -import android.util.Log -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.isSystemInDarkTheme -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import kotlinx.coroutines.delay -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.ATTHandles -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun LoudSoundReductionSwitch() { - var loudSoundReductionEnabled by remember { - mutableStateOf( - false - ) - } - val attManager = ServiceManager.getService()?.attManager ?: return - LaunchedEffect(Unit) { - attManager.enableNotifications(ATTHandles.LOUD_SOUND_REDUCTION) - - var parsed = false - for (attempt in 1..3) { - try { - val data = attManager.read(ATTHandles.LOUD_SOUND_REDUCTION) - loudSoundReductionEnabled = data[0].toInt() != 0 - Log.d("LoudSoundReduction", "Read attempt $attempt: enabled=${loudSoundReductionEnabled}") - parsed = true - break - } catch (e: Exception) { - Log.w("LoudSoundReduction", "Read attempt $attempt failed: ${e.message}") - } - delay(200) - } - if (!parsed) { - Log.d("LoudSoundReduction", "Failed to read loud sound reduction state after 3 attempts") - } - } - - LaunchedEffect(loudSoundReductionEnabled) { - if (attManager.socket?.isConnected != true) return@LaunchedEffect - attManager.write(ATTHandles.LOUD_SOUND_REDUCTION, if (loudSoundReductionEnabled) byteArrayOf(1) else byteArrayOf(0)) - } - - val loudSoundListener = remember { - object : (ByteArray) -> Unit { - override fun invoke(value: ByteArray) { - if (value.isNotEmpty()) { - loudSoundReductionEnabled = value[0].toInt() != 0 - Log.d("LoudSoundReduction", "Updated from notification: enabled=$loudSoundReductionEnabled") - } else { - Log.w("LoudSoundReduction", "Empty value in notification") - } - } - } - } - - LaunchedEffect(Unit) { - attManager.registerListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener) - } - - DisposableEffect(Unit) { - onDispose { - attManager.unregisterListener(ATTHandles.LOUD_SOUND_REDUCTION, loudSoundListener) - } - } - - val isDarkTheme = isSystemInDarkTheme() - 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() } - ) { - loudSoundReductionEnabled = !loudSoundReductionEnabled - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.loud_sound_reduction), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.loud_sound_reduction_description), - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, - ) - } - StyledSwitch( - checked = loudSoundReductionEnabled, - onCheckedChange = { - loudSoundReductionEnabled = it - }, - ) - } -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt deleted file mode 100644 index ef72c926..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/PersonalizedVolumeSwitch.kt +++ /dev/null @@ -1,163 +0,0 @@ - /* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -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.isSystemInDarkTheme -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - - @Composable -fun PersonalizedVolumeSwitch() { - val service = ServiceManager.getService()!! - - val adaptiveVolumeEnabledValue = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - - var adaptiveVolumeEnabled by remember { - mutableStateOf( - adaptiveVolumeEnabledValue == 1.toByte() - ) - } - - fun updatePersonalizedVolume(enabled: Boolean) { - adaptiveVolumeEnabled = enabled - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG.value, - enabled - ) - } - - val adaptiveVolumeListener = object: AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - adaptiveVolumeEnabled = newValue == 1.toByte() - } - } - } - - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, - adaptiveVolumeListener - ) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener( - AACPManager.Companion.ControlCommandIdentifiers.ADAPTIVE_VOLUME_CONFIG, - adaptiveVolumeListener - ) - } - } - - val isDarkTheme = isSystemInDarkTheme() - 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() } - ) { - updatePersonalizedVolume(!adaptiveVolumeEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.personalized_volume), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.personalized_volume_description), - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, - ) - } - StyledSwitch( - checked = adaptiveVolumeEnabled, - onCheckedChange = { - updatePersonalizedVolume(it) - }, - ) - } -} - -@Preview -@Composable -fun PersonalizedVolumeSwitchPreview() { - PersonalizedVolumeSwitch() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt deleted file mode 100644 index 4818bf8c..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/SinglePodANCSwitch.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -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.isSystemInDarkTheme -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun SinglePodANCSwitch() { - val service = ServiceManager.getService()!! - val singleANCEnabledValue = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var singleANCEnabled by remember { - mutableStateOf( - singleANCEnabledValue == 1.toByte() - ) - } - val listener = object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - singleANCEnabled = newValue == 1.toByte() - } - } - } - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, listener) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, listener) - } - } - - fun updateSingleEnabled(enabled: Boolean) { - singleANCEnabled = enabled - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE.value, - enabled - ) - } - - val isDarkTheme = isSystemInDarkTheme() - 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) - }, - ) - } -} - -@Preview -@Composable -fun SinglePodANCSwitchPreview() { - SinglePodANCSwitch() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt index 5b01cc58..4786c0f0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt @@ -18,26 +18,58 @@ package me.kavishdevar.librepods.composables -import androidx.compose.animation.core.animateDpAsState +import android.content.res.Configuration +import androidx.compose.animation.Animatable +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.isSystemInDarkTheme 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.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.BlurEffect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.layer.CompositingStrategy +import androidx.compose.ui.graphics.layer.drawLayer +import androidx.compose.ui.graphics.rememberGraphicsLayer +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceIn +import androidx.compose.ui.util.lerp +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberCombinedBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop +import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.refractionWithDispersion +import com.kyant.backdrop.highlight.Highlight +import com.kyant.backdrop.shadow.Shadow +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch @Composable fun StyledSwitch( @@ -47,42 +79,172 @@ fun StyledSwitch( ) { val isDarkTheme = isSystemInDarkTheme() - val thumbColor = Color.White - 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) - } + val onColor = if (enabled) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) + val offColor = if (enabled) if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) + + val trackWidth = 70.dp + val trackHeight = 31.dp + val thumbHeight = 27.dp + val thumbWidth = 36.dp + val backdrop = rememberLayerBackdrop() + val switchBackdrop = rememberLayerBackdrop() + val fraction by remember { + derivedStateOf { if (checked) 1f else 0f } + } + val animatedFraction = remember { Animatable(fraction) } + val trackWidthPx = remember { mutableFloatStateOf(0f) } + val density = LocalDensity.current + val animationScope = rememberCoroutineScope() + val progressAnimationSpec = spring(0.5f, 300f, 0.001f) + val colorAnimationSpec = tween(300, easing = FastOutSlowInEasing) + val progressAnimation = remember { Animatable(0f) } + val innerShadowLayer = rememberGraphicsLayer().apply { + compositingStrategy = CompositingStrategy.Offscreen + } + val animatedTrackColor = remember { Animatable(if (checked) onColor else offColor) } - val thumbOffsetX by animateDpAsState(targetValue = if (checked) 20.dp else 0.dp, label = "Test") + LaunchedEffect(checked) { + val targetColor = if (checked) onColor else offColor + animatedTrackColor.animateTo(targetColor, colorAnimationSpec) + val targetFrac = if (checked) 1f else 0f + animatedFraction.animateTo(targetFrac, progressAnimationSpec) + } Box( modifier = Modifier - .width(51.dp) - .height(31.dp) - .clip(RoundedCornerShape(15.dp)) - .background(trackColor) // Dynamic track background - .padding(horizontal = 3.dp), + .width(trackWidth) + .height(trackHeight), contentAlignment = Alignment.CenterStart ) { Box( modifier = Modifier - .offset(x = thumbOffsetX) - .size(27.dp) - .clip(CircleShape) - .background(thumbColor) - .clickable { if (enabled) onCheckedChange(!checked) } + .layerBackdrop(switchBackdrop) + .clip(RoundedCornerShape(trackHeight / 2)) + .background(animatedTrackColor.value) + .width(trackWidth) + .height(trackHeight) + .onSizeChanged { trackWidthPx.floatValue = it.width.toFloat() } + ) + + Box( + modifier = Modifier + .padding(horizontal = 2.dp) + .graphicsLayer { + translationX = animatedFraction.value * (trackWidthPx.floatValue - with(density) { thumbWidth.toPx() + 4.dp.toPx() }) + } + .then(if (enabled) Modifier.draggable( + rememberDraggableState { delta -> + if (trackWidthPx.floatValue > 0f) { + val newFraction = (animatedFraction.value + delta / trackWidthPx.floatValue).fastCoerceIn(0f, 1f) + animationScope.launch { + animatedFraction.snapTo(newFraction) + } + val newChecked = newFraction >= 0.5f + if (newChecked != checked) { + onCheckedChange(newChecked) + } + } + }, + Orientation.Horizontal, + startDragImmediately = true, + onDragStarted = { + animationScope.launch { + progressAnimation.animateTo(1f, progressAnimationSpec) + } + }, + onDragStopped = { + animationScope.launch { + progressAnimation.animateTo(0f, progressAnimationSpec) + val snappedFraction = if (animatedFraction.value >= 0.5f) 1f else 0f + animatedFraction.animateTo(snappedFraction, progressAnimationSpec) + onCheckedChange(snappedFraction >= 0.5f) + } + } + ) else Modifier) + .drawBackdrop( + rememberCombinedBackdrop(backdrop, switchBackdrop), + { RoundedCornerShape(thumbHeight / 2) }, + highlight = { + val progress = progressAnimation.value + Highlight.AmbientDefault.copy(alpha = progress) + }, + shadow = { + Shadow( + radius = 4f.dp, + color = Color.Black.copy(0.05f) + ) + }, + layer = { + val progress = progressAnimation.value + val scale = lerp(1f, 2f, progress) + scaleX = scale + scaleY = scale + }, + onDrawSurface = { + val progress = progressAnimation.value.fastCoerceIn(0f, 1f) + + val shape = RoundedCornerShape(thumbHeight / 2) + val outline = shape.createOutline(size, layoutDirection, this) + val innerShadowOffset = 4f.dp.toPx() + val innerShadowBlurRadius = 4f.dp.toPx() + + innerShadowLayer.alpha = progress + innerShadowLayer.renderEffect = + BlurEffect( + innerShadowBlurRadius, + innerShadowBlurRadius, + TileMode.Decal + ) + innerShadowLayer.record { + drawOutline(outline, Color.Black.copy(0.2f)) + translate(0f, innerShadowOffset) { + drawOutline( + outline, + Color.Transparent, + blendMode = BlendMode.Clear + ) + } + } + drawLayer(innerShadowLayer) + + drawRect(Color.White.copy(1f - progress)) + }, + effects = { + refractionWithDispersion(6f.dp.toPx(), size.height / 2f) + } + ) + .width(thumbWidth) + .height(thumbHeight) ) } } -@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun StyledSwitchPreview() { - StyledSwitch(checked = true, onCheckedChange = {}) + val isDarkTheme = isSystemInDarkTheme() + val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + Box( + modifier = Modifier + .background(backgroundColor) + .width(100.dp) + .height(100.dp), + contentAlignment = Alignment.Center + ) { + val checked = remember { mutableStateOf(true) } + StyledSwitch( + checked = checked.value, + onCheckedChange = { + checked.value = it + }, + enabled = true + ) + LaunchedEffect(Unit) { + delay(1000) + checked.value = false + delay(1000) + checked.value = true + } + } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt index 896c4a3d..99567b9b 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt @@ -59,9 +59,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit +import kotlinx.coroutines.delay import me.kavishdevar.librepods.R import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.ATTHandles import kotlin.io.encoding.ExperimentalEncodingApi @Composable @@ -72,7 +74,8 @@ fun StyledToggle( checkedState: MutableState = remember { mutableStateOf(false) } , sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, - independent: Boolean = true + independent: Boolean = true, + onCheckedChange: ((Boolean) -> Unit)? = null, ) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black @@ -88,6 +91,7 @@ fun StyledToggle( } sharedPreferences.edit { putBoolean(sharedPreferenceKey, checked) } } + onCheckedChange?.invoke(checked) } if (independent) { @@ -100,7 +104,7 @@ fun StyledToggle( fontWeight = FontWeight.Light, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(8.dp, bottom = 4.dp) ) } Box( @@ -232,7 +236,10 @@ fun StyledToggle( label: String, description: String? = null, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers, - independent: Boolean = true + independent: Boolean = true, + sharedPreferenceKey: String? = null, + sharedPreferences: SharedPreferences? = null, + onCheckedChange: ((Boolean) -> Unit)? = null, ) { val service = ServiceManager.getService() ?: return val isDarkTheme = isSystemInDarkTheme() @@ -243,9 +250,19 @@ fun StyledToggle( var checked by remember { mutableStateOf(checkedValue == 1.toByte()) } var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - + if (sharedPreferenceKey != null && sharedPreferences != null) { + checked = sharedPreferences.getBoolean(sharedPreferenceKey, checked) + } fun cb() { service.aacpManager.sendControlCommand(identifier = controlCommandIdentifier.value, value = checked) + if (sharedPreferences != null) { + if (sharedPreferenceKey == null) { + Log.e("StyledToggle", "SharedPreferenceKey is null but SharedPreferences is provided.") + return + } + sharedPreferences.edit { putBoolean(sharedPreferenceKey, checked) } + } + onCheckedChange?.invoke(checked) } val listener = remember { @@ -277,7 +294,225 @@ fun StyledToggle( fontWeight = FontWeight.Light, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(8.dp, bottom = 4.dp) + ) + } + Box( + modifier = Modifier + .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = + if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + checked = !checked + cb() + } + ) + } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .height(55.dp) + .padding(horizontal = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + modifier = Modifier.weight(1f), + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) + ) + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + } + ) + } + } + if (description != null) { + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .padding(horizontal = 8.dp) + .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) + ) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) + ) + } + } + } + } else { + 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() } + ) { + checked = !checked + cb() + }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .weight(1f) + .padding(end = 4.dp) + ) { + Text( + text = label, + fontSize = 16.sp, + color = textColor + ) + Spacer(modifier = Modifier.height(4.dp)) + if (description != null) { + Text( + text = description, + fontSize = 12.sp, + color = textColor.copy(0.6f), + lineHeight = 14.sp, + ) + } + } + StyledSwitch( + checked = checked, + onCheckedChange = { + checked = it + cb() + } + ) + } + } +} + +@Composable +fun StyledToggle( + title: String? = null, + label: String, + description: String? = null, + attHandle: ATTHandles, + independent: Boolean = true, + sharedPreferenceKey: String? = null, + sharedPreferences: SharedPreferences? = null, + onCheckedChange: ((Boolean) -> Unit)? = null, +) { + val attManager = ServiceManager.getService()?.attManager ?: return + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + var checked by remember { mutableStateOf(false) } + var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } + val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) + + LaunchedEffect(Unit) { + attManager.enableNotifications(attHandle) + + var parsed = false + for (attempt in 1..3) { + try { + val data = attManager.read(attHandle) + checked = data[0].toInt() != 0 + Log.d("StyledToggle", "Read attempt $attempt for $label: enabled=$checked") + parsed = true + break + } catch (e: Exception) { + Log.w("StyledToggle", "Read attempt $attempt for $label failed: ${e.message}") + } + delay(200) + } + if (!parsed) { + Log.d("StyledToggle", "Failed to read state for $label after 3 attempts") + } + } + + if (sharedPreferenceKey != null && sharedPreferences != null) { + checked = sharedPreferences.getBoolean(sharedPreferenceKey, checked) + } + + fun cb() { + if (sharedPreferences != null) { + if (sharedPreferenceKey == null) { + Log.e("StyledToggle", "SharedPreferenceKey is null but SharedPreferences is provided.") + return + } + sharedPreferences.edit { putBoolean(sharedPreferenceKey, checked) } + } + onCheckedChange?.invoke(checked) + } + + LaunchedEffect(checked) { + if (attManager.socket?.isConnected != true) return@LaunchedEffect + attManager.write(attHandle, if (checked) byteArrayOf(1) else byteArrayOf(0)) + } + + val listener = remember { + object : (ByteArray) -> Unit { + override fun invoke(value: ByteArray) { + if (value.isNotEmpty()) { + checked = value[0].toInt() != 0 + Log.d("StyledToggle", "Updated from notification for $label: enabled=$checked") + } else { + Log.w("StyledToggle", "Empty value in notification for $label") + } + } + } + } + + LaunchedEffect(Unit) { + attManager.registerListener(attHandle, listener) + } + + DisposableEffect(Unit) { + onDispose { + attManager.unregisterListener(attHandle, listener) + } + } + + if (independent) { + Column(modifier = Modifier.padding(vertical = 8.dp)) { + if (title != null) { + Text( + text = title, + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f) + ), + modifier = Modifier.padding(8.dp, bottom = 4.dp) ) } Box( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt deleted file mode 100644 index 1c8b6229..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/VolumeControlSwitch.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -@file:OptIn(ExperimentalEncodingApi::class) - -package me.kavishdevar.librepods.composables - -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.isSystemInDarkTheme -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.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import me.kavishdevar.librepods.services.ServiceManager -import me.kavishdevar.librepods.utils.AACPManager -import kotlin.io.encoding.ExperimentalEncodingApi - -@Composable -fun VolumeControlSwitch() { - val service = ServiceManager.getService()!! - val volumeControlEnabledValue = service.aacpManager.controlCommandStatusList.find { - it.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE - }?.value?.takeIf { it.isNotEmpty() }?.get(0) - var volumeControlEnabled by remember { - mutableStateOf( - volumeControlEnabledValue == 1.toByte() - ) - } - val listener = object : AACPManager.ControlCommandListener { - override fun onControlCommandReceived(controlCommand: AACPManager.ControlCommand) { - if (controlCommand.identifier == AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE.value) { - val newValue = controlCommand.value.takeIf { it.isNotEmpty() }?.get(0) - volumeControlEnabled = newValue == 1.toByte() - } - } - } - LaunchedEffect(Unit) { - service.aacpManager.registerControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE, listener) - } - DisposableEffect(Unit) { - onDispose { - service.aacpManager.unregisterControlCommandListener(AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE, listener) - } - } - fun updateVolumeControlEnabled(enabled: Boolean) { - volumeControlEnabled = enabled - service.aacpManager.sendControlCommand( - AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE.value, - enabled - ) - } - - val isDarkTheme = isSystemInDarkTheme() - 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) - }, - ) - } -} - -@Preview -@Composable -fun VolumeControlSwitchPreview() { - VolumeControlSwitch() -} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index d5615c33..1e8a1c0a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -62,6 +62,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset @@ -87,17 +88,15 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.composables.LoudSoundReductionSwitch import me.kavishdevar.librepods.composables.NavigationButton -import me.kavishdevar.librepods.composables.SinglePodANCSwitch import me.kavishdevar.librepods.composables.StyledDropdown import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledSlider -import me.kavishdevar.librepods.composables.StyledSwitch -import me.kavishdevar.librepods.composables.VolumeControlSwitch +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager +import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.RadareOffsetFinder import kotlin.io.encoding.ExperimentalEncodingApi @@ -334,8 +333,67 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } + DropdownMenuComponent( + label = stringResource(R.string.press_speed), + description = stringResource(R.string.press_speed_description), + options = pressSpeedOptions.values.toList(), + selectedOption = selectedPressSpeed?: "Default", + onOptionSelected = { newValue -> + selectedPressSpeed = newValue + aacpManager?.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value, + value = pressSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() + ?: 0.toByte() + ) + }, + textColor = textColor, + hazeState = hazeState, + independent = true + ) + + DropdownMenuComponent( + label = stringResource(R.string.press_and_hold_duration), + description = stringResource(R.string.press_and_hold_duration_description), + options = pressAndHoldDurationOptions.values.toList(), + selectedOption = selectedPressAndHoldDuration?: "Default", + onOptionSelected = { newValue -> + selectedPressAndHoldDuration = newValue + aacpManager?.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value, + value = pressAndHoldDurationOptions.filterValues { it == newValue }.keys.firstOrNull() + ?: 0.toByte() + ) + }, + textColor = textColor, + hazeState = hazeState, + independent = true + ) + + StyledToggle( + title = stringResource(R.string.noise_control).uppercase(), + label = stringResource(R.string.noise_cancellation_single_airpod), + description = stringResource(R.string.noise_cancellation_single_airpod_description), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, + independent = true, + ) + + StyledToggle( + label = stringResource(R.string.loud_sound_reduction), + description = stringResource(R.string.loud_sound_reduction_description), + attHandle = ATTHandles.LOUD_SOUND_REDUCTION + ) + + if (!hearingAidEnabled.value&& isSdpOffsetAvailable.value) { + NavigationButton( + to = "transparency_customization", + name = stringResource(R.string.customize_transparency_mode), + navController = navController + ) + } + StyledSlider( label = stringResource(R.string.tone_volume).uppercase(), + description = stringResource(R.string.tone_volume_description), mutableFloatState = toneVolumeValue, onValueChange = { toneVolumeValue.floatValue = it @@ -347,114 +405,25 @@ fun AccessibilitySettingsScreen(navController: NavController) { independent = true ) - Column( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) - .padding(top = 2.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.SpaceBetween - ) { - SinglePodANCSwitch() - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888), - modifier = Modifier.padding(start = 12.dp, end = 0.dp) - ) - - VolumeControlSwitch() - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888), - modifier = Modifier.padding(start = 12.dp, end = 0.dp) - ) - - LoudSoundReductionSwitch() - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888), - modifier = Modifier.padding(start = 12.dp, end = 0.dp) - ) - - DropdownMenuComponent( - label = stringResource(R.string.press_speed), - options = listOf( - stringResource(R.string.default_option), - stringResource(R.string.slower), - stringResource(R.string.slowest) - ), - selectedOption = selectedPressSpeed.toString(), - onOptionSelected = { newValue -> - selectedPressSpeed = newValue - aacpManager?.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.DOUBLE_CLICK_INTERVAL.value, - value = pressSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() - ?: 0.toByte() - ) - }, - textColor = textColor, - hazeState = hazeState - ) - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888), - modifier = Modifier.padding(start = 12.dp, end = 0.dp) - ) - - DropdownMenuComponent( - label = stringResource(R.string.press_and_hold_duration), - options = listOf( - stringResource(R.string.default_option), - stringResource(R.string.slower), - stringResource(R.string.slowest) - ), - selectedOption = selectedPressAndHoldDuration.toString(), - onOptionSelected = { newValue -> - selectedPressAndHoldDuration = newValue - aacpManager?.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.CLICK_HOLD_INTERVAL.value, - value = pressAndHoldDurationOptions.filterValues { it == newValue }.keys.firstOrNull() - ?: 0.toByte() - ) - }, - textColor = textColor, - hazeState = hazeState - ) - HorizontalDivider( - thickness = 1.5.dp, - color = Color(0x40888888), - modifier = Modifier.padding(start = 12.dp, end = 0.dp) - ) - - DropdownMenuComponent( - label = stringResource(R.string.volume_swipe_speed), - options = listOf( - stringResource(R.string.default_option), - stringResource(R.string.longer), - stringResource(R.string.longest) - ), - selectedOption = selectedVolumeSwipeSpeed.toString(), - onOptionSelected = { newValue -> - selectedVolumeSwipeSpeed = newValue - aacpManager?.sendControlCommand( - identifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value, - value = volumeSwipeSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() - ?: 1.toByte() - ) - }, - textColor = textColor, - hazeState = hazeState - ) - } + DropdownMenuComponent( + label = stringResource(R.string.volume_swipe_speed), + description = stringResource(R.string.volume_swipe_speed_description), + options = volumeSwipeSpeedOptions.values.toList(), + selectedOption = selectedVolumeSwipeSpeed?: "Default", + onOptionSelected = { newValue -> + selectedVolumeSwipeSpeed = newValue + aacpManager?.sendControlCommand( + identifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_INTERVAL.value, + value = volumeSwipeSpeedOptions.filterValues { it == newValue }.keys.firstOrNull() + ?: 1.toByte() + ) + }, + textColor = textColor, + hazeState = hazeState, + independent = true + ) if (!hearingAidEnabled.value&& isSdpOffsetAvailable.value) { - NavigationButton( - to = "transparency_customization", - name = stringResource(R.string.customize_transparency_mode), - navController = navController - ) - - Spacer(modifier = Modifier.height(2.dp)) Text( text = stringResource(R.string.apply_eq_to).uppercase(), style = TextStyle( @@ -680,113 +649,6 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } - -@Composable -fun AccessibilityToggle( - text: String, - mutableState: MutableState, - independent: Boolean = false, - description: String? = null, - title: String? = null -) { - val isDarkTheme = isSystemInDarkTheme() - var backgroundColor by remember { - mutableStateOf( - if (isDarkTheme) Color(0xFF1C1C1E) else Color( - 0xFFFFFFFF - ) - ) - } - val animatedBackgroundColor by animateColorAsState( - targetValue = backgroundColor, - animationSpec = tween(durationMillis = 500) - ) - val textColor = if (isDarkTheme) Color.White else Color.Black - val cornerShape = if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) - - Column( - modifier = Modifier - .padding(vertical = 8.dp) - ) { - if (title != null) { - Text( - text = title, - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) - ) - Spacer(modifier = Modifier.height(4.dp)) - } - Box( - modifier = Modifier - .background(animatedBackgroundColor, cornerShape) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = - if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = - if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - mutableState.value = !mutableState.value - } - ) - }, - ) - { - val rowHeight = if (independent) 55.dp else 50.dp - val rowPadding = if (independent) 12.dp else 4.dp - Row( - modifier = Modifier - .fillMaxWidth() - .height(rowHeight) - .padding(horizontal = rowPadding), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = text, - modifier = Modifier.weight(1f), - fontSize = 16.sp, - color = textColor - ) - StyledSwitch( - checked = mutableState.value, - onCheckedChange = { - mutableState.value = it - }, - ) - } - } - if (description != null) { - Spacer(modifier = Modifier.height(8.dp)) - Box ( // for some reason, haze and backdrop don't work for uncontained text - modifier = Modifier - .fillMaxWidth() - .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7), cornerShape) - ) { - Text( - text = description, - style = TextStyle( - fontSize = 12.sp, - fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - // modifier = Modifier - // .padding(horizontal = 8.dp) - ) - } - } - } -} - - @ExperimentalHazeMaterialsApi @Composable private fun DropdownMenuComponent( @@ -797,6 +659,7 @@ private fun DropdownMenuComponent( textColor: Color, hazeState: HazeState, description: String? = null, + independent: Boolean = true ) { val density = LocalDensity.current val itemHeightPx = with(density) { 48.dp.toPx() } @@ -808,125 +671,164 @@ private fun DropdownMenuComponent( var parentHoveredIndex by remember { mutableStateOf(null) } var parentDragActive by remember { mutableStateOf(false) } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(55.dp) - .pointerInput(Unit) { - detectTapGestures { offset -> - val now = System.currentTimeMillis() - if (expanded) { - expanded = false - lastDismissTime = now - } else { - if (now - lastDismissTime > 250L) { - touchOffset = offset - expanded = true - } - } - } - } - .pointerInput(Unit) { - detectDragGesturesAfterLongPress( - onDragStart = { offset -> - val now = System.currentTimeMillis() - touchOffset = offset - if (!expanded && now - lastDismissTime > 250L) { - expanded = true + Column(modifier = Modifier.fillMaxWidth()){ + Column( + modifier = Modifier + .fillMaxWidth() + .then( + if (independent) { + if (description != null) { + Modifier.padding(top = 8.dp, bottom = 4.dp) + } else { + Modifier.padding(vertical = 8.dp) } - lastDismissTime = now - parentDragActive = true - parentHoveredIndex = 0 - }, - onDrag = { change, _ -> - val current = change.position - val touch = touchOffset ?: current - val posInPopupY = current.y - touch.y - val idx = (posInPopupY / itemHeightPx).toInt() - parentHoveredIndex = idx - }, - onDragEnd = { - parentDragActive = false - parentHoveredIndex?.let { idx -> - if (idx in options.indices) { - onOptionSelected(options[idx]) + } else Modifier + ) + .background( + if (independent) (if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) else Color.Transparent, + if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) + ) + .clip(if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp)) + ){ + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp) + .height(55.dp) + .pointerInput(Unit) { + detectTapGestures { offset -> + val now = System.currentTimeMillis() + if (expanded) { expanded = false - lastDismissTime = System.currentTimeMillis() + lastDismissTime = now + } else { + if (now - lastDismissTime > 250L) { + touchOffset = offset + expanded = true + } } } - parentHoveredIndex = null - }, - onDragCancel = { - parentDragActive = false - parentHoveredIndex = null } - ) - }, - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = label, - fontSize = 16.sp, - color = textColor, - modifier = Modifier.padding(bottom = 4.dp) - ) - Box( - modifier = Modifier.onGloballyPositioned { coordinates -> - boxPosition = coordinates.positionInParent() - } - ) { - Row( + .pointerInput(Unit) { + detectDragGesturesAfterLongPress( + onDragStart = { offset -> + val now = System.currentTimeMillis() + touchOffset = offset + if (!expanded && now - lastDismissTime > 250L) { + expanded = true + } + lastDismissTime = now + parentDragActive = true + parentHoveredIndex = 0 + }, + onDrag = { change, _ -> + val current = change.position + val touch = touchOffset ?: current + val posInPopupY = current.y - touch.y + val idx = (posInPopupY / itemHeightPx).toInt() + parentHoveredIndex = idx + }, + onDragEnd = { + parentDragActive = false + parentHoveredIndex?.let { idx -> + if (idx in options.indices) { + onOptionSelected(options[idx]) + expanded = false + lastDismissTime = System.currentTimeMillis() + } + } + parentHoveredIndex = null + }, + onDragCancel = { + parentDragActive = false + parentHoveredIndex = null + } + ) + }, + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Text( - text = selectedOption, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) - ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) - ) - } + Column( + modifier = Modifier.weight(1f) + ){ + Text( + text = label, + fontSize = 16.sp, + color = textColor, + modifier = Modifier.padding(bottom = 4.dp) + ) + if (!independent && description != null){ + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(bottom = 2.dp) + ) + } + } + Box( + modifier = Modifier.onGloballyPositioned { coordinates -> + boxPosition = coordinates.positionInParent() + } + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = selectedOption, + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f) + ) + Icon( + Icons.Default.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = textColor.copy(alpha = 0.6f) + ) + } - StyledDropdown( - expanded = expanded, - onDismissRequest = { - expanded = false - lastDismissTime = System.currentTimeMillis() - }, - options = options, - selectedOption = selectedOption, - touchOffset = touchOffset, - boxPosition = boxPosition, - externalHoveredIndex = parentHoveredIndex, - externalDragActive = parentDragActive, - onOptionSelected = { option -> - onOptionSelected(option) - expanded = false - }, - hazeState = hazeState - ) + StyledDropdown( + expanded = expanded, + onDismissRequest = { + expanded = false + lastDismissTime = System.currentTimeMillis() + }, + options = options, + selectedOption = selectedOption, + touchOffset = touchOffset, + boxPosition = boxPosition, + externalHoveredIndex = parentHoveredIndex, + externalDragActive = parentDragActive, + onOptionSelected = { option -> + onOptionSelected(option) + expanded = false + }, + hazeState = hazeState + ) + } + } } - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .background(if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7)) - ){ - Text( - text = description ?: "", - style = TextStyle( - fontSize = 12.sp, - fontWeight = FontWeight.Light, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) + if (independent && description != null){ + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .background(if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7)) + ){ + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - ) + } } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt index f4621d8e..ad71bb45 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt @@ -168,7 +168,8 @@ fun HeadTrackingScreen(navController: NavController) { label = "Head Gestures", sharedPreferences = sharedPreferences, sharedPreferenceKey = "head_gestures", - ) + ) + Spacer(modifier = Modifier.height(2.dp)) Text( stringResource(R.string.head_gestures_details), diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 1b265649..6c642a52 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -337,9 +337,9 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController independent = true, ) - AccessibilityToggle( - text = stringResource(R.string.conversation_boost), - mutableState = conversationBoostEnabled, + StyledToggle( + label = stringResource(R.string.conversation_boost), + checkedState = conversationBoostEnabled, independent = true, description = stringResource(R.string.conversation_boost_description) ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index d14fc940..106ea482 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -246,13 +246,13 @@ fun HearingAidScreen(navController: NavController) { modifier = Modifier.padding(horizontal = 8.dp) ) Spacer(modifier = Modifier.height(16.dp)) - - AccessibilityToggle( - text = stringResource(R.string.media_assist), - mutableState = mediaAssistEnabled, + + StyledToggle( + title = stringResource(R.string.media_assist).uppercase(), + label = stringResource(R.string.media_assist), + checkedState = mediaAssistEnabled, independent = true, - description = stringResource(R.string.media_assist_description), - title = stringResource(R.string.media_assist).uppercase() + description = stringResource(R.string.media_assist_description) ) Spacer(modifier = Modifier.height(8.dp)) @@ -377,10 +377,8 @@ fun HearingAidScreen(navController: NavController) { try { val data = attManager.read(ATTHandles.TRANSPARENCY) val parsed = parseTransparencySettingsResponse(data) - if (parsed != null) { - val disabledSettings = parsed.copy(enabled = false) - sendTransparencySettings(attManager, disabledSettings) - } + val disabledSettings = parsed.copy(enabled = false) + sendTransparencySettings(attManager, disabledSettings) } catch (e: Exception) { Log.e(TAG, "Error disabling transparency: ${e.message}") } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt index 565039e6..a0a8855a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -66,6 +66,7 @@ import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledSlider +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.ATTHandles import me.kavishdevar.librepods.utils.RadareOffsetFinder @@ -288,9 +289,9 @@ fun TransparencySettingsScreen(navController: NavController) { // Only show transparency mode section if SDP offset is available if (isSdpOffsetAvailable.value) { - AccessibilityToggle( - text = stringResource(R.string.transparency_mode), - mutableState = enabled, + StyledToggle( + label = stringResource(R.string.transparency_mode), + checkedState = enabled, independent = true, description = stringResource(R.string.customize_transparency_mode_description) ) @@ -344,9 +345,9 @@ fun TransparencySettingsScreen(navController: NavController) { independent = true, ) - AccessibilityToggle( - text = stringResource(R.string.conversation_boost), - mutableState = conversationBoostEnabled, + StyledToggle( + label = stringResource(R.string.conversation_boost), + checkedState = conversationBoostEnabled, independent = true, description = stringResource(R.string.conversation_boost_description) ) diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index b89fbf9f..2c499877 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ See your AirPods battery status right from your home screen! Accessibility Tone Volume + Adjust the tone volume of sound effects played by AirPods. Audio Adaptive Audio Customize Adaptive Audio @@ -28,7 +29,7 @@ Personalized Volume Adjusts the volume of media in response to your environment. Noise Cancellation with Single AirPod - Allow AirPods to be put in noise cancellation mode when only one AirPods is in your ear. + Allow AirPods to be put in noise cancellation mode when only one AirPod is in your ear. Volume Control Adjust the volume by swiping up or down on the sensor located on the AirPods Pro stem. AirPods not connected @@ -107,8 +108,11 @@ Transparency Mode Customize Transparency Mode Press Speed + Adjust the speed required to press two or three times on your AirPods. Press and Hold Duration + Adjust the duration required to press and hold on your AirPods Volume Swipe Speed + To prevent unintended volume adjustments, select preferred wait time between swipes. Equalizer Apply EQ to Phone From 08738a12938e53e11b5e12e3f364edaca36c6fe7 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 12:27:05 +0530 Subject: [PATCH 41/72] android: liquidglass, maybe? the switch and icon button took quite a while. i forgot the order of modifiers matters! --- android/app/libs/backdrop-debug.aar | Bin 109112 -> 108261 bytes android/app/libs/backdrop-release.aar | Bin 103294 -> 102401 bytes .../librepods/composables/AudioSettings.kt | 23 +- .../librepods/composables/BatteryIndicator.kt | 7 +- .../librepods/composables/BatteryView.kt | 4 +- .../composables/CallControlSettings.kt | 76 +- .../composables/ConfirmationDialog.kt | 27 +- .../composables/ConnectionSettings.kt | 13 +- .../composables/MicrophoneSettings.kt | 41 +- .../librepods/composables/NameField.kt | 154 -- .../librepods/composables/NavigationButton.kt | 128 +- .../composables/NoiseControlSettings.kt | 13 +- .../composables/PressAndHoldSettings.kt | 160 +-- .../librepods/composables/StyledButton.kt | 8 +- .../librepods/composables/StyledDropdown.kt | 2 +- .../librepods/composables/StyledIconButton.kt | 25 +- .../librepods/composables/StyledSlider.kt | 203 +-- .../librepods/composables/StyledSwitch.kt | 77 +- .../librepods/composables/StyledToggle.kt | 174 ++- .../screens/AccessibilitySettingsScreen.kt | 70 +- .../screens/AdaptiveStrengthScreen.kt | 9 +- .../screens/AirPodsSettingsScreen.kt | 23 +- .../librepods/screens/AppSettingsScreen.kt | 1247 +++++------------ .../librepods/screens/DebugScreen.kt | 24 +- .../librepods/screens/HeadTrackingScreen.kt | 171 +-- .../screens/HearingAidAdjustmentsScreen.kt | 18 +- .../librepods/screens/HearingAidScreen.kt | 171 +-- .../librepods/screens/Onboarding.kt | 7 +- .../screens/PressAndHoldSettingsScreen.kt | 123 +- .../librepods/screens/RenameScreen.kt | 34 +- .../screens/TransparencySettingsScreen.kt | 55 +- .../screens/TroubleshootingScreen.kt | 26 +- android/app/src/main/res/values/strings.xml | 8 +- 33 files changed, 1187 insertions(+), 1934 deletions(-) delete mode 100644 android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt diff --git a/android/app/libs/backdrop-debug.aar b/android/app/libs/backdrop-debug.aar index d8d5bff72349c2436a89444a4e0fdb2aaf0fd8dc..2a687c71893781e74d50b75d7985f0fd9151ca72 100644 GIT binary patch literal 108261 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 literal 109112 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 diff --git a/android/app/libs/backdrop-release.aar b/android/app/libs/backdrop-release.aar index 306297bdaccf99790fcb1236582a6a86efd9e72d..6aad2de262c8e20047bdfbdc102a0ec19113ded0 100644 GIT binary patch literal 102401 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%0Aobkv;Y_9YE0_Yx8G-CpaE3XE!YX(O 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 literal 103294 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 =zV@SU#{Thd?`San@ z7AMy4RBBUH>N?&vd;`%^mRx!401AwDAnt&46{R$my3%a4 z1$4+#F`pjp;^|ta_>p)dSkf#FInJR;wq|^ICYyf7AERpKd8_dGYDQ=F&;ctKAxWbs z%&wSaNd>k%2AIEGu?TGyxdml~{ZzR%xRje8_Y6ZLeio*MEq}B*zC$)*)nlZZ&U-%o z_9G@ZBiP!plwiGpPg(DNGnv$YsU$iJOvb5j^tLO7v=IWTGP!!&*U}1`;URJe&2J|s zxH%HEaW6*)kh!IuEq$?+Hmo+M`5ac-CFU1QoKydbyTz6azw-`zSidn%>uiLEZ* zZhOE{3XzWz346E{M(;NrfRY#IxUyxnBpecE`VGUJ6--bi&Nw}U0X9qR_=h%lIxQ9) zFUG6?rgNmrcFt>a#8W%gd$HmXNO<2&@SGuc;Yhglk0Pw<5%>w)M9|tIa^dKWX~k!! zS8AuHwLg!+?Ycsq6XQGcbHSHe16jT_S_f#odw`DK{JMR@bbVHIWrM?9wAM$B<-&tS zS_T#A5CT0rD4SQ4B^WtJ7eEeQ#)3*^^OKCu#Z3DHJzhgcmaTFcEDA0!9You>Ld%vy z(OrEi#SDaDzzJ2*fMWN!>W?{K)h@U^3`~l ztbq~om5YdVz8VmQvCxLd)67dH%qp4NRqIt_vu4%QhAI@hDMCM`KhO;jxABt(IlnJc z>wMt_3O_vV68i$vM~D&euWhf1>{RR^x^ZiI?>PmEK@~!KgOdsv3Lc!9&m5t3fl11b zZc)R4q;-Z7FkOEb>ECKVm}X!`!t$>)e^_|08FsENly8T;i+6%%UNzTc;09IoibL|j z>F(7ar*LO=RAFZ8lH;I<4()(0jJTOiKB=iiu*~7>txcz;KX4!!uk^X1&wdX?TYz47?`h7mr(+NOl#A! z&Rb$RUAP3BA|>sq3?uIjIM=0B&mh5%fvaJmr(VC)_79L*_6rA_Q5ANX1KZrlrbKFpg-DyGinrT{#EV)+J* zvqolzc8fI}bdj0{4k~gBgX>vc?}9j|Ur^wMwq5>(3;)eXe&?fVa4i9^FM_IHihBoN zfrGgFvs23Qot}Hq{=l(QBn$G6|C7JBV~i>O&h_mbp1zn6<;Q_lf%S>cr~2c?bJ}I} zL0}hWl4r6jP7N4Wn|`1B!(-_=Wmm5Zq>&Ha&W0@v5lIUXc0G8KLyy9}dN}4h!a07w zz#XHtzpuiv)T{QpeK_V4;oOIWOX8O){@;u{m8+eZ+k3N*pFK9iwYL|paag{AJ2eC4 zaIjduBMz)0!~>ck#vylbM{qsnFQ5a2*!*T`lT5*V6StHhC?NxIer7MQ1GW!eBTNx{ zwQ#K1X4W6@aDFCfBZek*tXb1_aYo1k*l>~98D~QQ`N2le11up&aI{$3*y=1*#+p+O z2m{(7u5d6SfA%m!_~00sIxGBd7IOT)3I90=GQQ)h+QEW=P*H$@F#g|zpq#y_rI{tb z>3_{#ue7XPbk)$mrcGJg2{^`FGLmF}+Af5!*RqU(1XB<<$t|$QmCPmy>*Q@tZjvp? zn(b#vSt#ZIRaNSxfr5&FQWh&#EiR^7E+`ATdDgiL?$MhNs3G@#p3ZimXd9Cy+?_wt zPWOIV^Un2o+BVPindbLAGLSF;tAp@EwTL1c5iay@7UgCu6B92iKlHHBlkRY=URq0v z8V)h`I{(}BlX!QhTFxpTn1Y31OR!lj5k`-!Sja^hji5?FKEv*btg93s>BaFs+ilNp@42F zL##JY%Jubd#)EUBFS*D2MZw@|-dKIPm&DGG5;;2C8^&Ja?t

;5?)&{Qw8g905rp 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%0D8v}EqPQdd>vlz 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 0 || case?.status != BatteryStatus.DISCONNECTED) { BatteryIndicator( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt index 3a06981b..e42b8499 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/CallControlSettings.kt @@ -32,12 +32,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -57,6 +53,8 @@ import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -76,19 +74,19 @@ fun CallControlSettings(hazeState: HazeState) { val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Text( - text = stringResource(R.string.call_controls).uppercase(), + text = stringResource(R.string.call_controls), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { val service = ServiceManager.getService()!! @@ -169,8 +167,8 @@ fun CallControlSettings(hazeState: HazeState) { Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(50.dp), + .padding(horizontal = 16.dp) + .height(58.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -187,17 +185,17 @@ fun CallControlSettings(hazeState: HazeState) { ) } HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) ) Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(50.dp) + .padding(horizontal = 16.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -276,14 +274,21 @@ fun CallControlSettings(hazeState: HazeState) { ) { Text( text = singlePressAction, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } @@ -315,17 +320,17 @@ fun CallControlSettings(hazeState: HazeState) { } } HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) ) Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(50.dp) + .padding(horizontal = 16.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -404,14 +409,21 @@ fun CallControlSettings(hazeState: HazeState) { ) { Text( text = doublePressAction, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index 18139ec4..f08f448a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -74,7 +74,6 @@ import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.colorControls import com.kyant.backdrop.effects.refraction import com.kyant.backdrop.highlight.Highlight -import com.kyant.backdrop.highlight.HighlightStyle import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.utils.inspectDragGestures @@ -143,7 +142,9 @@ half4 main(float2 coord) { .drawBackdrop( backdrop, { RoundedCornerShape(48f.dp) }, - highlight = { Highlight { HighlightStyle.Solid } }, + highlight = { + Highlight.SolidDefault + }, onDrawSurface = { drawRect(containerColor) }, effects = { colorControls( @@ -153,7 +154,7 @@ half4 main(float2 coord) { blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) refraction(24f.dp.toPx(), 48f.dp.toPx(), true) }, - layer = { + layerBlock = { val width = size.width val height = size.height @@ -273,16 +274,6 @@ half4 main(float2 coord) { horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { - // Box( - // Modifier - // .clip(RoundedCornerShape(50.dp)) - // .background(containerColor.copy(0.2f)) - // .clickable(onClick = onDismiss) - // .height(48.dp) - // .weight(1f) - // .padding(horizontal = 16.dp), - // contentAlignment = Alignment.Center - // ) { StyledButton( onClick = onDismiss, backdrop = backdrop, @@ -295,16 +286,6 @@ half4 main(float2 coord) { style = TextStyle(contentColor, 16.sp) ) } - // Box( - // Modifier - // .clip(RoundedCornerShape(50.dp)) - // .background(accentColor) - // .clickable(onClick = onConfirm) - // .height(48.dp) - // .weight(1f) - // .padding(horizontal = 16.dp), - // contentAlignment = Alignment.Center - // ) { StyledButton( onClick = onConfirm, backdrop = backdrop, diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt index 90106e4c..9bf84174 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConnectionSettings.kt @@ -20,6 +20,7 @@ package me.kavishdevar.librepods.composables +import android.content.Context.MODE_PRIVATE import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column @@ -34,11 +35,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import kotlin.io.encoding.ExperimentalEncodingApi -import android.content.Context.MODE_PRIVATE -import me.kavishdevar.librepods.composables.StyledToggle -import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.utils.AACPManager +import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun ConnectionSettings() { @@ -48,7 +47,7 @@ fun ConnectionSettings() { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { StyledToggle( @@ -59,10 +58,10 @@ fun ConnectionSettings() { independent = false ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal= 12.dp) ) StyledToggle( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt index 4f88af07..2c1b4a08 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/MicrophoneSettings.kt @@ -32,11 +32,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -55,6 +51,9 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInParent import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -75,7 +74,7 @@ fun MicrophoneSettings(hazeState: HazeState) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(top = 2.dp) ) { val service = ServiceManager.getService()!! @@ -141,8 +140,8 @@ fun MicrophoneSettings(hazeState: HazeState) { Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp) - .height(55.dp) + .padding(horizontal = 16.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -214,8 +213,11 @@ fun MicrophoneSettings(hazeState: HazeState) { ) { Text( text = stringResource(R.string.microphone_mode), - fontSize = 16.sp, - color = textColor, + style = TextStyle( + fontSize = 16.sp, + color = textColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), modifier = Modifier.padding(bottom = 4.dp) ) Box( @@ -228,14 +230,21 @@ fun MicrophoneSettings(hazeState: HazeState) { ) { Text( text = selectedMode, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(16.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt deleted file mode 100644 index 399adc44..00000000 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NameField.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * LibrePods - AirPods liberated from Apple’s ecosystem - * - * Copyright (C) 2025 LibrePods contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package me.kavishdevar.librepods.composables - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavController -import androidx.navigation.compose.rememberNavController - -@Composable -fun NameField( - name: String, - value: String, - navController: NavController -) { - var isFocused by remember { mutableStateOf(false) } - - val isDarkTheme = isSystemInDarkTheme() - - var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - - val textColor = if (isDarkTheme) Color.White else Color.Black - val cursorColor = if (isFocused) { - if (isDarkTheme) Color.White else Color.Black - } else { - Color.Transparent - } - - Box ( - modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - navController.navigate("rename") - } - ) - } - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(55.dp) - .background( - animatedBackgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 8.dp) - - ) { - Text( - text = name, - style = TextStyle( - fontSize = 16.sp, - color = textColor - ) - ) - BasicTextField( - value = value, - textStyle = TextStyle( - color = textColor.copy(alpha = 0.75f), - fontSize = 16.sp, - textAlign = TextAlign.End - ), - onValueChange = {}, - singleLine = true, - enabled = false, - cursorBrush = SolidColor(cursorColor), - modifier = Modifier - .fillMaxWidth() - .padding(start = 8.dp) - .onFocusChanged { focusState -> - isFocused = focusState.isFocused - }, - decorationBox = { innerTextField -> - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End - ) { - innerTextField() - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "Edit name", - tint = textColor.copy(alpha = 0.75f), - modifier = Modifier - .size(32.dp) - ) - } - } - ) - } - } -} - -@Preview -@Composable -fun StyledTextFieldPreview() { - NameField(name = "Name", value = "AirPods Pro", rememberNavController()) -} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt index 0baa894f..81756541 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NavigationButton.kt @@ -1,17 +1,17 @@ /* * LibrePods - AirPods liberated from Apple’s ecosystem - * + * * Copyright (C) 2025 LibrePods contributors - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -23,75 +23,105 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowRight -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController - +import me.kavishdevar.librepods.R @Composable -fun NavigationButton(to: String, name: String, navController: NavController, onClick: (() -> Unit)? = null, independent: Boolean = true) { +fun NavigationButton( + to: String, + name: String, + navController: NavController, onClick: (() -> Unit)? = null, + independent: Boolean = true, + description: String? = null, + currentState: String? = null +) { val isDarkTheme = isSystemInDarkTheme() var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - - Row( - modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(if (independent) 14.dp else 0.dp)) - .height(55.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - if (onClick != null) onClick() else navController.navigate(to) - } - ) - } - ) { - Text( - text = name, - modifier = Modifier.padding(16.dp), - color = if (isDarkTheme) Color.White else Color.Black - ) - Spacer(modifier = Modifier.weight(1f)) - IconButton( - onClick = { if (onClick != null) onClick() else navController.navigate(to) }, - colors = IconButtonDefaults.iconButtonColors( - containerColor = Color.Transparent, - contentColor = if (isDarkTheme) Color.White else Color.Black - ), + Column { + Row( modifier = Modifier - .padding(start = 16.dp) - .fillMaxHeight() + .background(animatedBackgroundColor, RoundedCornerShape(if (independent) 28.dp else 0.dp)) + .height(58.dp) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) + tryAwaitRelease() + backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + }, + onTap = { + if (onClick != null) onClick() else navController.navigate(to) + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, ) { - @Suppress("DEPRECATION") - Icon( - imageVector = Icons.Default.KeyboardArrowRight, - contentDescription = name + Text( + text = name, + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White else Color.Black, + ) + ) + Spacer(modifier = Modifier.weight(1f)) + if (currentState != null) { + Text( + text = currentState, + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.8f), + ) + ) + } + Text( + text = "􀯻", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f) + ), + modifier = Modifier + .padding(start = if (currentState != null) 6.dp else 0.dp) + ) + } + if (description != null) { + Text( + text = description, + style = TextStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp) ) } } @@ -101,4 +131,4 @@ fun NavigationButton(to: String, name: String, navController: NavController, onC @Composable fun NavigationButtonPreview() { NavigationButton("to", "Name", NavController(LocalContext.current)) -} \ No newline at end of file +} diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt index 648e610b..d031a8cc 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/NoiseControlSettings.kt @@ -181,10 +181,10 @@ fun NoiseControlSettings( } Text( - text = stringResource(R.string.noise_control).uppercase(), + text = stringResource(R.string.noise_control), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), ), modifier = Modifier.padding(8.dp, bottom = 2.dp) @@ -241,7 +241,7 @@ fun NoiseControlSettings( modifier = Modifier .fillMaxWidth() .height(60.dp) - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) ) { Row( modifier = Modifier.fillMaxWidth() @@ -334,7 +334,7 @@ fun NoiseControlSettings( modifier = Modifier .fillMaxSize() .padding(3.dp) - .background(selectedBackground, RoundedCornerShape(12.dp)) + .background(selectedBackground, RoundedCornerShape(26.dp)) ) } @@ -400,7 +400,6 @@ fun NoiseControlSettings( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 4.dp) .padding(top = 4.dp) ) { if (offListeningMode.value) { @@ -408,7 +407,6 @@ fun NoiseControlSettings( text = stringResource(R.string.off), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) } @@ -416,21 +414,18 @@ fun NoiseControlSettings( text = stringResource(R.string.transparency), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) Text( text = stringResource(R.string.adaptive), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) Text( text = stringResource(R.string.noise_cancellation), style = TextStyle(fontSize = 12.sp, color = textColor), textAlign = TextAlign.Center, - fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt index 53af7195..b4cb0816 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/PressAndHoldSettings.kt @@ -19,34 +19,21 @@ package me.kavishdevar.librepods.composables import android.content.Context -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween +import android.content.res.Configuration import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -65,12 +52,6 @@ fun PressAndHoldSettings(navController: NavController) { val isDarkTheme = isSystemInDarkTheme() val textColor = if (isDarkTheme) Color.White else Color.Black val dividerColor = Color(0x40888888) - var leftBackgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - var rightBackgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - - val animationSpec = tween(durationMillis = 500) - val animatedLeftBackgroundColor by animateColorAsState(targetValue = leftBackgroundColor, animationSpec = animationSpec) - val animatedRightBackgroundColor by animateColorAsState(targetValue = rightBackgroundColor, animationSpec = animationSpec) val context = LocalContext.current val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE) @@ -91,14 +72,14 @@ fun PressAndHoldSettings(navController: NavController) { } Text( - text = stringResource(R.string.press_and_hold_airpods).uppercase(), + text = stringResource(R.string.press_and_hold_airpods), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) Spacer(modifier = Modifier.height(1.dp)) @@ -106,126 +87,33 @@ fun PressAndHoldSettings(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF), RoundedCornerShape(14.dp)) + .background(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF), RoundedCornerShape(28.dp)) + .clip(RoundedCornerShape(28.dp)) ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(50.dp) - .background(animatedLeftBackgroundColor, RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - leftBackgroundColor = dividerColor - tryAwaitRelease() - leftBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - navController.navigate("long_press/Left") - } - ) - }, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .padding(start = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.left), - style = TextStyle( - fontSize = 16.sp, - color = textColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = leftActionText, - style = TextStyle( - fontSize = 16.sp, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - IconButton( - onClick = { - navController.navigate("long_press/Left") - } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "go", - tint = textColor - ) - } - } - } + NavigationButton( + to = "long_press/Left", + name = stringResource(R.string.left), + navController = navController, + independent = false, + currentState = leftActionText, + ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = dividerColor, modifier = Modifier - .padding(start = 16.dp) + .padding(horizontal = 16.dp) + ) + NavigationButton( + to = "long_press/Right", + name = stringResource(R.string.right), + navController = navController, + independent = false, + currentState = rightActionText, ) - Box( - modifier = Modifier - .fillMaxWidth() - .height(50.dp) - .background(animatedRightBackgroundColor, RoundedCornerShape(bottomEnd = 14.dp, bottomStart = 14.dp)) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - rightBackgroundColor = dividerColor - tryAwaitRelease() - rightBackgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - navController.navigate("long_press/Right") - } - ) - }, - contentAlignment = Alignment.Center - ) { - Row( - modifier = Modifier - .padding(start = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.right), - style = TextStyle( - fontSize = 16.sp, - color = textColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = rightActionText, - style = TextStyle( - fontSize = 16.sp, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - ) - IconButton( - onClick = { - navController.navigate("long_press/Right") - } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "go", - tint = textColor - ) - } - } - } } } -@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable fun PressAndHoldSettingsPreview() { PressAndHoldSettings(navController = NavController(LocalContext.current)) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt index ce683722..41258bf2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledButton.kt @@ -74,7 +74,8 @@ fun StyledButton( isInteractive: Boolean = true, tint: Color = Color.Unspecified, surfaceColor: Color = Color.Unspecified, - content: @Composable RowScope.() -> Unit + maxScale: Float = 0.1f, + content: @Composable RowScope.() -> Unit, ) { val animationScope = rememberCoroutineScope() val progressAnimation = remember { Animatable(0f) } @@ -113,7 +114,7 @@ half4 main(float2 coord) { effects = { blur(16f.dp.toPx()) }, - layer = null, + layerBlock = null, onDrawSurface = { if (tint.isSpecified) { drawRect(tint, blendMode = BlendMode.Hue) @@ -147,12 +148,11 @@ half4 main(float2 coord) { blur(2f.dp.toPx()) refraction(12f.dp.toPx(), 24f.dp.toPx()) }, - layer = { + layerBlock = { val width = size.width val height = size.height val progress = progressAnimation.value - val maxScale = 0.1f val scale = lerp(1f, 1f + maxScale, progress) val maxOffset = size.minDimension diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt index 46168b8d..73cf7449 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledDropdown.kt @@ -230,7 +230,7 @@ fun StyledDropdown( if (index != options.lastIndex) { HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier.padding(start = 12.dp, end = 0.dp) ) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt index 4e941934..2f59f728 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledIconButton.kt @@ -24,6 +24,7 @@ import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.spring +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -59,8 +60,10 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastCoerceAtMost import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.lerp +import com.kyant.backdrop.backdrops.LayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop +import com.kyant.backdrop.effects.blur import com.kyant.backdrop.effects.refractionWithDispersion import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.Shadow @@ -79,6 +82,8 @@ fun StyledIconButton( icon: String, darkMode: Boolean, tint: Color = Color.Unspecified, + backdrop: LayerBackdrop = rememberLayerBackdrop(), + modifier: Modifier = Modifier, ) { val animationScope = rememberCoroutineScope() val progressAnimationSpec = spring(0.5f, 300f, 0.001f) @@ -110,26 +115,23 @@ half4 main(float2 coord) { null } } - + val isDarkTheme = isSystemInDarkTheme() TextButton( onClick = onClick, shape = RoundedCornerShape(56.dp), - modifier = Modifier + modifier = modifier .padding(horizontal = 12.dp) .drawBackdrop( - backdrop = rememberLayerBackdrop(), + backdrop = backdrop, shape = { RoundedCornerShape(56.dp) }, - highlight = { - val progress = progressAnimation.value - Highlight.AmbientDefault.copy(alpha = progress.coerceIn(0.45f, 1f)) - }, + highlight = { Highlight.AmbientDefault.copy(alpha = if (isDarkTheme) 1f else 0f) }, shadow = { Shadow( - radius = 4f.dp, - color = Color.Black.copy(0.08f) + radius = 48f.dp, + color = Color.Black.copy(if (isDarkTheme) 0.08f else 0.4f) ) }, - layer = { + layerBlock = { val width = size.width val height = size.height @@ -182,7 +184,7 @@ half4 main(float2 coord) { drawLayer(innerShadowLayer) drawRect( - Color.White.copy(progress.coerceIn(0.15f, 0.35f)) + (if (isDarkTheme) Color(0xFFAFAFAF) else Color.White).copy(progress.coerceIn(0.15f, 0.35f)) ) }, onDrawFront = { @@ -218,6 +220,7 @@ half4 main(float2 coord) { }, effects = { refractionWithDispersion(6f.dp.toPx(), size.height / 2f) + blur(24f, TileMode.Decal) }, ) .pointerInput(animationScope) { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt index b0544cdd..caa16448 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSlider.kt @@ -31,12 +31,14 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -84,6 +86,7 @@ import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.Shadow import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import kotlin.math.abs import kotlin.math.roundToInt @Composable @@ -136,23 +139,26 @@ fun StyledSlider( val content = @Composable { Box( - Modifier.fillMaxWidth(if (startIcon == null && endIcon == null) 0.95f else 1f) - ) { + Modifier + .fillMaxWidth(if (startIcon == null && endIcon == null) 0.95f else 1f) + ) { Box( Modifier + .padding(vertical = 4.dp) .layerBackdrop(sliderBackdrop) - .fillMaxWidth()) { + .fillMaxWidth() + ) { Column( modifier = Modifier .fillMaxWidth(1f) .padding(vertical = 12.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp) ) { if (startLabel != null || endLabel != null) { Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .padding(horizontal = 8.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Text( @@ -174,80 +180,119 @@ fun StyledSlider( ) ) } + Spacer(modifier = Modifier.height(12.dp)) } - - Row( + Column( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp) - .then(if (startIcon == null && endIcon == null) Modifier.padding(horizontal = 12.dp) else Modifier), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(0.dp) + .then(if (startIcon == null && endIcon == null) Modifier.padding(horizontal = 8.dp) else Modifier), ) { - if (startIcon != null) { - Text( - text = startIcon, - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = accentColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier - .padding(horizontal = 12.dp) - .onGloballyPositioned { - startIconWidthState.floatValue = it.size.width.toFloat() - } - ) - } - Box( - Modifier - .weight(1f) - .onSizeChanged { trackWidthState.floatValue = it.width.toFloat() } - .onGloballyPositioned { - trackPositionState.floatValue = - it.positionInParent().y + it.size.height / 2f - } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(0.dp) ) { + if (startIcon != null) { + Text( + text = startIcon, + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = accentColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp) + .onGloballyPositioned { + startIconWidthState.floatValue = it.size.width.toFloat() + } + ) + } Box( Modifier - .clip(RoundedCornerShape(28.dp)) - .background(trackColor) - .height(6f.dp) - .fillMaxWidth() - ) + .weight(1f) + .onSizeChanged { trackWidthState.floatValue = it.width.toFloat() } + .onGloballyPositioned { + trackPositionState.floatValue = + it.positionInParent().y + it.size.height / 2f + } + ) { + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(trackColor) + .height(6f.dp) + .fillMaxWidth() + ) - Box( - Modifier - .clip(RoundedCornerShape(28.dp)) - .background(accentColor) - .height(6f.dp) - .layout { measurable, constraints -> - val placeable = measurable.measure(constraints) - val fraction = fraction - val width = - (fraction * constraints.maxWidth).fastRoundToInt() - layout(width, placeable.height) { - placeable.place(0, 0) + Box( + Modifier + .clip(RoundedCornerShape(28.dp)) + .background(accentColor) + .height(6f.dp) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + val fraction = fraction + val width = + (fraction * constraints.maxWidth).fastRoundToInt() + layout(width, placeable.height) { + placeable.place(0, 0) + } } - } - ) + ) + } + if (endIcon != null) { + Text( + text = endIcon, + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = accentColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(horizontal = 12.dp) + .onGloballyPositioned { + endIconWidthState.floatValue = it.size.width.toFloat() + } + ) + } } - if (endIcon != null) { - Text( - text = endIcon, - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - color = accentColor, - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier - .padding(horizontal = 12.dp) - .onGloballyPositioned { - endIconWidthState.floatValue = it.size.width.toFloat() + if (snapPoints.isNotEmpty() && startLabel != null && endLabel != null) Spacer(modifier = Modifier.height(4.dp)) + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center + ) { + if (snapPoints.isNotEmpty()) { + val trackWidth = if (startIcon != null && endIcon != null) trackWidthState.floatValue - with(density) { 6.dp.toPx() } * 2 else trackWidthState.floatValue- with(density) { 22.dp.toPx() } + val startOffset = + if (startIcon != null) startIconWidthState.floatValue + with( + density + ) { 34.dp.toPx() } else with(density) { 14.dp.toPx() } + Box( + Modifier + .fillMaxWidth() + ) { + snapPoints.forEach { point -> + val pointFraction = + ((point - valueRange.start) / (valueRange.endInclusive - valueRange.start)) + .fastCoerceIn(0f, 1f) + Box( + Modifier + .graphicsLayer { + translationX = + startOffset + pointFraction * trackWidth - 4.dp.toPx() + } + .size(2.dp) + .background( + trackColor, + CircleShape + ) + ) } - ) + } + } } } } @@ -257,10 +302,10 @@ fun StyledSlider( Modifier .graphicsLayer { val startOffset = - if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else with(density) { 8.dp.toPx() } + if (startIcon != null) startIconWidthState.floatValue + with(density) { 24.dp.toPx() } else with(density) { 12.dp.toPx() } translationX = startOffset + fraction * trackWidthState.floatValue - size.width / 2f - translationY = if (startLabel != null || endLabel != null) trackPositionState.floatValue + with(density) { 22.dp.toPx() } + size.height / 2f else trackPositionState.floatValue + with(density) { 4.dp.toPx() } + translationY = if (startLabel != null || endLabel != null) trackPositionState.floatValue + with(density) { 26.dp.toPx() } + size.height / 2f else trackPositionState.floatValue + with(density) { 8.dp.toPx() } } .draggable( rememberDraggableState { delta -> @@ -305,7 +350,7 @@ fun StyledSlider( color = Color.Black.copy(0.05f) ) }, - layer = { + layerBlock = { val progress = progressAnimation.value val scale = lerp(1f, 1.5f, progress) scaleX = scale @@ -361,20 +406,20 @@ fun StyledSlider( text = label, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = labelTextColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp) + modifier = Modifier.padding(horizontal = 18.dp, vertical = 4.dp) ) } Box( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(horizontal = 8.dp, vertical = 0.dp) - .heightIn(min = 55.dp), + .heightIn(min = 58.dp), contentAlignment = Alignment.Center ) { content() @@ -390,7 +435,7 @@ fun StyledSlider( fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier - .padding(horizontal = 12.dp, vertical = 4.dp) + .padding(horizontal = 18.dp, vertical = 4.dp) ) } } @@ -402,8 +447,8 @@ fun StyledSlider( } private fun snapIfClose(value: Float, points: List, threshold: Float = 0.05f): Float { - val nearest = points.minByOrNull { kotlin.math.abs(it - value) } ?: value - return if (kotlin.math.abs(nearest - value) <= threshold) nearest else value + val nearest = points.minByOrNull { abs(it - value) } ?: value + return if (abs(nearest - value) <= threshold) nearest else value } @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -426,9 +471,11 @@ fun StyledSliderPreview() { a.floatValue = it }, valueRange = 0f..2f, + snapPoints = listOf(0f, 0.5f, 1f, 1.5f, 2f), + snapThreshold = 0.1f, independent = true, startLabel = "A", - endLabel = "B" + endLabel = "B", ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt index 4786c0f0..94ea6903 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt @@ -48,8 +48,11 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.BlurEffect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.drawOutline +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.drawscope.translate import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.layer.CompositingStrategy @@ -68,7 +71,7 @@ import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.effects.refractionWithDispersion import com.kyant.backdrop.highlight.Highlight import com.kyant.backdrop.shadow.Shadow -import kotlinx.coroutines.delay +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @Composable @@ -76,16 +79,17 @@ fun StyledSwitch( checked: Boolean, onCheckedChange: (Boolean) -> Unit, enabled: Boolean = true, + indpendent: Boolean = true, ) { val isDarkTheme = isSystemInDarkTheme() val onColor = if (enabled) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) val offColor = if (enabled) if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6) - val trackWidth = 70.dp - val trackHeight = 31.dp - val thumbHeight = 27.dp - val thumbWidth = 36.dp + val trackWidth = 64.dp + val trackHeight = 28.dp + val thumbHeight = 24.dp + val thumbWidth = 39.dp val backdrop = rememberLayerBackdrop() val switchBackdrop = rememberLayerBackdrop() @@ -97,18 +101,23 @@ fun StyledSwitch( val density = LocalDensity.current val animationScope = rememberCoroutineScope() val progressAnimationSpec = spring(0.5f, 300f, 0.001f) - val colorAnimationSpec = tween(300, easing = FastOutSlowInEasing) + val colorAnimationSpec = tween(200, easing = FastOutSlowInEasing) val progressAnimation = remember { Animatable(0f) } val innerShadowLayer = rememberGraphicsLayer().apply { compositingStrategy = CompositingStrategy.Offscreen } val animatedTrackColor = remember { Animatable(if (checked) onColor else offColor) } - LaunchedEffect(checked) { - val targetColor = if (checked) onColor else offColor - animatedTrackColor.animateTo(targetColor, colorAnimationSpec) - val targetFrac = if (checked) 1f else 0f - animatedFraction.animateTo(targetFrac, progressAnimationSpec) + coroutineScope { + launch { + val targetColor = if (checked) onColor else offColor + animatedTrackColor.animateTo(targetColor, colorAnimationSpec) + } + launch { + val targetFrac = if (checked) 1f else 0f + animatedFraction.animateTo(targetFrac, progressAnimationSpec) + } + } } Box( @@ -136,7 +145,7 @@ fun StyledSwitch( .then(if (enabled) Modifier.draggable( rememberDraggableState { delta -> if (trackWidthPx.floatValue > 0f) { - val newFraction = (animatedFraction.value + delta / trackWidthPx.floatValue).fastCoerceIn(0f, 1f) + val newFraction = (animatedFraction.value + delta / trackWidthPx.floatValue).fastCoerceIn(-0.3f, 1.3f) animationScope.launch { animatedFraction.snapTo(newFraction) } @@ -155,10 +164,12 @@ fun StyledSwitch( }, onDragStopped = { animationScope.launch { - progressAnimation.animateTo(0f, progressAnimationSpec) val snappedFraction = if (animatedFraction.value >= 0.5f) 1f else 0f - animatedFraction.animateTo(snappedFraction, progressAnimationSpec) onCheckedChange(snappedFraction >= 0.5f) + coroutineScope { + launch { progressAnimation.animateTo(0f, progressAnimationSpec) } + launch { animatedFraction.animateTo(snappedFraction, progressAnimationSpec) } + } } } ) else Modifier) @@ -175,12 +186,27 @@ fun StyledSwitch( color = Color.Black.copy(0.05f) ) }, - layer = { + layerBlock = { val progress = progressAnimation.value - val scale = lerp(1f, 2f, progress) + val scale = lerp(1f, 1.6f, progress) scaleX = scale scaleY = scale }, + onDrawBackdrop = { drawScope -> + drawIntoCanvas { canvas -> + canvas.save() + canvas.drawRect(0f, 0f, size.width, size.height, Paint().apply { + color = if (indpendent) { + if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + } else { + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + } + }) + scale(0.75f) { + drawScope() + } + } + }, onDrawSurface = { val progress = progressAnimation.value.fastCoerceIn(0f, 1f) @@ -224,12 +250,12 @@ fun StyledSwitch( @Composable fun StyledSwitchPreview() { val isDarkTheme = isSystemInDarkTheme() - val backgroundColor = if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) + val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7) Box( modifier = Modifier .background(backgroundColor) .width(100.dp) - .height(100.dp), + .height(400.dp), contentAlignment = Alignment.Center ) { val checked = remember { mutableStateOf(true) } @@ -238,13 +264,14 @@ fun StyledSwitchPreview() { onCheckedChange = { checked.value = it }, - enabled = true + enabled = true, + indpendent = false ) - LaunchedEffect(Unit) { - delay(1000) - checked.value = false - delay(1000) - checked.value = true - } +// LaunchedEffect(Unit) { +// delay(1000) +// checked.value = false +// delay(1000) +// checked.value = true +// } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt index 99567b9b..920fe7f1 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt @@ -75,6 +75,7 @@ fun StyledToggle( sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, independent: Boolean = true, + enabled: Boolean = true, onCheckedChange: ((Boolean) -> Unit)? = null, ) { val isDarkTheme = isSystemInDarkTheme() @@ -82,7 +83,9 @@ fun StyledToggle( var checked by checkedState var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) - + if (sharedPreferenceKey != null && sharedPreferences != null) { + checked = sharedPreferences.getBoolean(sharedPreferenceKey, checked) + } fun cb() { if (sharedPreferences != null) { if (sharedPreferenceKey == null) { @@ -101,15 +104,16 @@ fun StyledToggle( text = title, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 4.dp) + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 4.dp) ) } Box( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(28.dp)) + .padding(4.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -120,8 +124,10 @@ fun StyledToggle( if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } } ) } @@ -145,10 +151,14 @@ fun StyledToggle( ) StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = true ) } } @@ -156,7 +166,7 @@ fun StyledToggle( Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) ) { Text( @@ -177,10 +187,10 @@ fun StyledToggle( modifier = Modifier .fillMaxWidth() .background( - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(28.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent ) - .padding(horizontal = 12.dp, vertical = 12.dp) + .padding(16.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -194,8 +204,10 @@ fun StyledToggle( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -206,25 +218,35 @@ fun StyledToggle( ) { Text( text = label, - fontSize = 16.sp, - color = textColor + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) ) Spacer(modifier = Modifier.height(4.dp)) if (description != null) { Text( text = description, - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, + style = TextStyle( + fontSize = 12.sp, + color = textColor.copy(0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)), + ) ) } } StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = false ) } } @@ -237,6 +259,7 @@ fun StyledToggle( description: String? = null, controlCommandIdentifier: AACPManager.Companion.ControlCommandIdentifiers, independent: Boolean = true, + enabled: Boolean = true, sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, onCheckedChange: ((Boolean) -> Unit)? = null, @@ -291,15 +314,16 @@ fun StyledToggle( text = title, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 4.dp) + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 4.dp) ) } Box( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(28.dp)) + .padding(4.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -310,8 +334,10 @@ fun StyledToggle( if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } } ) } @@ -335,10 +361,14 @@ fun StyledToggle( ) StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = true ) } } @@ -346,7 +376,7 @@ fun StyledToggle( Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) ) { Text( @@ -367,10 +397,10 @@ fun StyledToggle( modifier = Modifier .fillMaxWidth() .background( - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(28.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent ) - .padding(horizontal = 12.dp, vertical = 12.dp) + .padding(16.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -384,8 +414,10 @@ fun StyledToggle( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -396,25 +428,35 @@ fun StyledToggle( ) { Text( text = label, - fontSize = 16.sp, - color = textColor + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + fontWeight = FontWeight.Normal, + color = textColor + ) ) Spacer(modifier = Modifier.height(4.dp)) if (description != null) { Text( text = description, - fontSize = 12.sp, - color = textColor.copy(0.6f), - lineHeight = 14.sp, + style = TextStyle( + fontSize = 12.sp, + color = textColor.copy(0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)), + ) ) } } StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = false ) } } @@ -427,6 +469,7 @@ fun StyledToggle( description: String? = null, attHandle: ATTHandles, independent: Boolean = true, + enabled: Boolean = true, sharedPreferenceKey: String? = null, sharedPreferences: SharedPreferences? = null, onCheckedChange: ((Boolean) -> Unit)? = null, @@ -509,15 +552,16 @@ fun StyledToggle( text = title, style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f) ), - modifier = Modifier.padding(8.dp, bottom = 4.dp) + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 4.dp) ) } Box( modifier = Modifier - .background(animatedBackgroundColor, RoundedCornerShape(14.dp)) + .background(animatedBackgroundColor, RoundedCornerShape(28.dp)) + .padding(4.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -528,8 +572,10 @@ fun StyledToggle( if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) }, onTap = { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } } ) } @@ -553,10 +599,14 @@ fun StyledToggle( ) StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = true ) } } @@ -564,7 +614,7 @@ fun StyledToggle( Spacer(modifier = Modifier.height(8.dp)) Box( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7)) ) { Text( @@ -585,10 +635,10 @@ fun StyledToggle( modifier = Modifier .fillMaxWidth() .background( - shape = RoundedCornerShape(14.dp), + shape = RoundedCornerShape(28.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent ) - .padding(horizontal = 12.dp, vertical = 12.dp) + .padding(16.dp) .pointerInput(Unit) { detectTapGestures( onPress = { @@ -602,8 +652,10 @@ fun StyledToggle( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - checked = !checked - cb() + if (enabled) { + checked = !checked + cb() + } }, verticalAlignment = Alignment.CenterVertically ) { @@ -629,10 +681,14 @@ fun StyledToggle( } StyledSwitch( checked = checked, + enabled = enabled, onCheckedChange = { - checked = it - cb() - } + if (enabled) { + checked = it + cb() + } + }, + indpendent = false ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt index 1e8a1c0a..5e3be792 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AccessibilitySettingsScreen.kt @@ -40,20 +40,16 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableLongStateOf @@ -79,6 +75,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi @@ -149,13 +147,16 @@ fun AccessibilitySettingsScreen(navController: NavController) { } } + val backdrop = rememberLayerBackdrop() + StyledScaffold( title = stringResource(R.string.accessibility), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, ) { spacerHeight, hazeState -> @@ -163,6 +164,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { modifier = Modifier .fillMaxSize() .hazeSource(hazeState) + .layerBackdrop(backdrop) .verticalScroll(rememberScrollState()) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -370,7 +372,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { ) StyledToggle( - title = stringResource(R.string.noise_control).uppercase(), + title = stringResource(R.string.noise_control), label = stringResource(R.string.noise_cancellation_single_airpod), description = stringResource(R.string.noise_cancellation_single_airpod_description), controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ONE_BUD_ANC_MODE, @@ -392,7 +394,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { } StyledSlider( - label = stringResource(R.string.tone_volume).uppercase(), + label = stringResource(R.string.tone_volume), description = stringResource(R.string.tone_volume_description), mutableFloatState = toneVolumeValue, onValueChange = { @@ -405,6 +407,12 @@ fun AccessibilitySettingsScreen(navController: NavController) { independent = true ) + StyledToggle( + label = stringResource(R.string.volume_control), + description = stringResource(R.string.volume_control_description), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.VOLUME_SWIPE_MODE, + ) + DropdownMenuComponent( label = stringResource(R.string.volume_swipe_speed), description = stringResource(R.string.volume_swipe_speed_description), @@ -425,10 +433,10 @@ fun AccessibilitySettingsScreen(navController: NavController) { if (!hearingAidEnabled.value&& isSdpOffsetAvailable.value) { Text( - text = stringResource(R.string.apply_eq_to).uppercase(), + text = stringResource(R.string.apply_eq_to), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), @@ -437,12 +445,12 @@ fun AccessibilitySettingsScreen(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(vertical = 0.dp) ) { val darkModeLocal = isSystemInDarkTheme() - val phoneShape = RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) + val phoneShape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) var phoneBackgroundColor by remember { mutableStateOf( if (darkModeLocal) Color( @@ -500,11 +508,11 @@ fun AccessibilitySettingsScreen(navController: NavController) { } HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888) ) - val mediaShape = RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + val mediaShape = RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp) var mediaBackgroundColor by remember { mutableStateOf( if (darkModeLocal) Color( @@ -565,7 +573,7 @@ fun AccessibilitySettingsScreen(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(12.dp), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -686,15 +694,18 @@ private fun DropdownMenuComponent( ) .background( if (independent) (if (isSystemInDarkTheme()) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) else Color.Transparent, - if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp) + if (independent) RoundedCornerShape(28.dp) else RoundedCornerShape(0.dp) + ) + then( + if (independent) Modifier.padding(horizontal = 4.dp) else Modifier ) - .clip(if (independent) RoundedCornerShape(14.dp) else RoundedCornerShape(0.dp)) + .clip(if (independent) RoundedCornerShape(28.dp) else RoundedCornerShape(0.dp)) ){ Row( modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, end = 12.dp) - .height(55.dp) + .height(58.dp) .pointerInput(Unit) { detectTapGestures { offset -> val now = System.currentTimeMillis() @@ -766,7 +777,7 @@ private fun DropdownMenuComponent( color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(bottom = 2.dp) + modifier = Modifier.padding(16.dp, top = 0.dp, bottom = 2.dp) ) } } @@ -780,14 +791,21 @@ private fun DropdownMenuComponent( ) { Text( text = selectedOption, - fontSize = 16.sp, - color = textColor.copy(alpha = 0.8f) + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.8f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ) ) - Icon( - Icons.Default.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - tint = textColor.copy(alpha = 0.6f) + Text( + text = "􀆏", + style = TextStyle( + fontSize = 16.sp, + color = textColor.copy(alpha = 0.6f), + fontFamily = FontFamily(Font(R.font.sf_pro)) + ), + modifier = Modifier + .padding(start = 6.dp) ) } @@ -816,7 +834,7 @@ private fun DropdownMenuComponent( Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 8.dp) + .padding(horizontal = 16.dp) .background(if (isSystemInDarkTheme()) Color(0xFF000000) else Color(0xFFF2F2F7)) ){ Text( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt index da0605a2..440e0680 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AdaptiveStrengthScreen.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -94,6 +96,7 @@ fun AdaptiveStrengthScreen(navController: NavController) { } } + val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.customize_adaptive_audio), @@ -101,19 +104,21 @@ fun AdaptiveStrengthScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight -> Column( modifier = Modifier .fillMaxSize() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) StyledSlider( - label = stringResource(R.string.customize_adaptive_audio).uppercase(), + label = stringResource(R.string.customize_adaptive_audio), mutableFloatState = sliderValue, onValueChange = { sliderValue.floatValue = it diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index c8242eaf..62c700bd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -68,6 +68,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController +import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.drawBackdrop import com.kyant.backdrop.highlight.Highlight @@ -80,7 +81,6 @@ import me.kavishdevar.librepods.composables.BatteryView import me.kavishdevar.librepods.composables.CallControlSettings import me.kavishdevar.librepods.composables.ConnectionSettings import me.kavishdevar.librepods.composables.MicrophoneSettings -import me.kavishdevar.librepods.composables.NameField import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.NoiseControlSettings import me.kavishdevar.librepods.composables.PressAndHoldSettings @@ -199,13 +199,15 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, } } val darkMode = isSystemInDarkTheme() + val backdrop = rememberLayerBackdrop() StyledScaffold( title = deviceName.text, actionButtons = listOf { StyledIconButton( onClick = { navController.navigate("app_settings") }, icon = "􀍟", - darkMode = darkMode + darkMode = darkMode, + backdrop = backdrop ) }, snackbarHostState = snackbarHostState @@ -216,6 +218,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, .fillMaxSize() .hazeSource(hazeState) .padding(horizontal = 16.dp) + .layerBackdrop(backdrop) .verticalScroll(rememberScrollState()) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -249,28 +252,28 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, // Only show name field when not in BLE-only mode if (!bleOnlyMode) { - NameField( + NavigationButton( + to = "rename", name = stringResource(R.string.name), - value = deviceName.text, - navController = navController + currentState = deviceName.text, + navController = navController, + independent = true ) - } - if (!bleOnlyMode) { Spacer(modifier = Modifier.height(32.dp)) NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) Spacer(modifier = Modifier.height(16.dp)) NoiseControlSettings(service = service) + Spacer(modifier = Modifier.height(16.dp)) + PressAndHoldSettings(navController = navController) + Spacer(modifier = Modifier.height(16.dp)) CallControlSettings(hazeState = hazeState) // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events - Spacer(modifier = Modifier.height(16.dp)) - PressAndHoldSettings(navController = navController) - Spacer(modifier = Modifier.height(16.dp)) AudioSettings(navController = navController) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index 353e9c93..89cf8665 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -37,7 +36,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -47,25 +45,21 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -79,13 +73,17 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold -import me.kavishdevar.librepods.composables.StyledSwitch +import me.kavishdevar.librepods.composables.StyledSlider +import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.utils.AACPManager import me.kavishdevar.librepods.utils.RadareOffsetFinder import kotlin.io.encoding.Base64 @@ -102,13 +100,13 @@ fun AppSettingsScreen(navController: NavController) { val coroutineScope = rememberCoroutineScope() val scrollState = rememberScrollState() - var showResetDialog by remember { mutableStateOf(false) } - var showIrkDialog by remember { mutableStateOf(false) } - var showEncKeyDialog by remember { mutableStateOf(false) } - var irkValue by remember { mutableStateOf("") } - var encKeyValue by remember { mutableStateOf("") } - var irkError by remember { mutableStateOf(null) } - var encKeyError by remember { mutableStateOf(null) } + val showResetDialog = remember { mutableStateOf(false) } + val showIrkDialog = remember { mutableStateOf(false) } + val showEncKeyDialog = remember { mutableStateOf(false) } + val irkValue = remember { mutableStateOf("") } + val encKeyValue = remember { mutableStateOf("") } + val irkError = remember { mutableStateOf(null) } + val encKeyError = remember { mutableStateOf(null) } LaunchedEffect(Unit) { val savedIrk = sharedPreferences.getString(AACPManager.Companion.ProximityKeyType.IRK.name, null) @@ -117,9 +115,9 @@ fun AppSettingsScreen(navController: NavController) { if (savedIrk != null) { try { val decoded = Base64.decode(savedIrk) - irkValue = decoded.joinToString("") { "%02x".format(it) } + irkValue.value = decoded.joinToString("") { "%02x".format(it) } } catch (e: Exception) { - irkValue = "" + irkValue.value = "" e.printStackTrace() } } @@ -127,59 +125,58 @@ fun AppSettingsScreen(navController: NavController) { if (savedEncKey != null) { try { val decoded = Base64.decode(savedEncKey) - encKeyValue = decoded.joinToString("") { "%02x".format(it) } + encKeyValue.value = decoded.joinToString("") { "%02x".format(it) } } catch (e: Exception) { - encKeyValue = "" + encKeyValue.value = "" e.printStackTrace() } } } - var showPhoneBatteryInWidget by remember { + val showPhoneBatteryInWidget = remember { mutableStateOf(sharedPreferences.getBoolean("show_phone_battery_in_widget", true)) } - var conversationalAwarenessPauseMusicEnabled by remember { + val conversationalAwarenessPauseMusicEnabled = remember { mutableStateOf(sharedPreferences.getBoolean("conversational_awareness_pause_music", false)) } - var relativeConversationalAwarenessVolumeEnabled by remember { + val relativeConversationalAwarenessVolumeEnabled = remember { mutableStateOf(sharedPreferences.getBoolean("relative_conversational_awareness_volume", true)) } - var openDialogForControlling by remember { + val openDialogForControlling = remember { mutableStateOf(sharedPreferences.getString("qs_click_behavior", "dialog") == "dialog") } - var disconnectWhenNotWearing by remember { + val disconnectWhenNotWearing = remember { mutableStateOf(sharedPreferences.getBoolean("disconnect_when_not_wearing", false)) } - var takeoverWhenDisconnected by remember { + val takeoverWhenDisconnected = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_disconnected", true)) } - var takeoverWhenIdle by remember { + val takeoverWhenIdle = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_idle", true)) } - var takeoverWhenMusic by remember { + val takeoverWhenMusic = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_music", false)) } - var takeoverWhenCall by remember { + val takeoverWhenCall = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_call", true)) } - var takeoverWhenRingingCall by remember { + val takeoverWhenRingingCall = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_ringing_call", true)) } - var takeoverWhenMediaStart by remember { + val takeoverWhenMediaStart = remember { mutableStateOf(sharedPreferences.getBoolean("takeover_when_media_start", true)) } - var useAlternateHeadTrackingPackets by remember { + val useAlternateHeadTrackingPackets = remember { mutableStateOf(sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false)) } - var bleOnlyMode by remember { + val bleOnlyMode = remember { mutableStateOf(sharedPreferences.getBoolean("ble_only_mode", false)) } - // Ensure the default value is properly set if not exists LaunchedEffect(Unit) { if (!sharedPreferences.contains("ble_only_mode")) { sharedPreferences.edit { putBoolean("ble_only_mode", false) } @@ -191,10 +188,12 @@ fun AppSettingsScreen(navController: NavController) { return hexPattern.matches(input) } - var isProcessingSdp by remember { mutableStateOf(false) } - var actAsAppleDevice by remember { mutableStateOf(false) } + val isProcessingSdp = remember { mutableStateOf(false) } + val actAsAppleDevice = remember { mutableStateOf(false) } - BackHandler(enabled = isProcessingSdp) {} + BackHandler(enabled = isProcessingSdp.value) {} + + val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.app_settings), @@ -202,15 +201,17 @@ fun AppSettingsScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight, hazeState -> Column( modifier = Modifier .fillMaxSize() - .verticalScroll(scrollState) + .layerBackdrop(backdrop) .hazeSource(state = hazeState) + .verticalScroll(scrollState) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -219,17 +220,33 @@ fun AppSettingsScreen(navController: NavController) { val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black - Spacer(modifier = Modifier.height(8.dp)) + StyledToggle( + title = stringResource(R.string.widget), + label = stringResource(R.string.show_phone_battery_in_widget), + description = stringResource(R.string.show_phone_battery_in_widget_description), + checkedState = showPhoneBatteryInWidget, + sharedPreferenceKey = "show_phone_battery_in_widget", + sharedPreferences = sharedPreferences, + ) + + StyledToggle( + title = stringResource(R.string.connection_mode), + label = stringResource(R.string.ble_only_mode), + description = stringResource(R.string.ble_only_mode_description), + checkedState = bleOnlyMode, + sharedPreferenceKey = "ble_only_mode", + sharedPreferences = sharedPreferences, + ) Text( - text = stringResource(R.string.widget).uppercase(), + text = stringResource(R.string.conversational_awareness), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 8.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -239,330 +256,230 @@ fun AppSettingsScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) - .padding(horizontal = 16.dp, vertical = 4.dp) + .padding(vertical = 4.dp) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - showPhoneBatteryInWidget = !showPhoneBatteryInWidget - sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", showPhoneBatteryInWidget)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.show_phone_battery_in_widget), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.show_phone_battery_in_widget_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } + fun updateConversationalAwarenessPauseMusic(enabled: Boolean) { + conversationalAwarenessPauseMusicEnabled.value = enabled + sharedPreferences.edit { putBoolean("conversational_awareness_pause_music", enabled)} + } - StyledSwitch( - checked = showPhoneBatteryInWidget, - onCheckedChange = { - showPhoneBatteryInWidget = it - sharedPreferences.edit { putBoolean("show_phone_battery_in_widget", it)} - } - ) + fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) { + relativeConversationalAwarenessVolumeEnabled.value = enabled + sharedPreferences.edit { putBoolean("relative_conversational_awareness_volume", enabled)} } + + StyledToggle( + label = stringResource(R.string.conversational_awareness_pause_music), + description = stringResource(R.string.conversational_awareness_pause_music_description), + checkedState = conversationalAwarenessPauseMusicEnabled, + onCheckedChange = { updateConversationalAwarenessPauseMusic(it) }, + independent = false + ) + + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.relative_conversational_awareness_volume), + description = stringResource(R.string.relative_conversational_awareness_volume_description), + checkedState = relativeConversationalAwarenessVolumeEnabled, + onCheckedChange = { updateRelativeConversationalAwarenessVolume(it) }, + independent = false + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + val conversationalAwarenessVolume = remember { mutableFloatStateOf(sharedPreferences.getInt("conversational_awareness_volume", 43).toFloat()) } + LaunchedEffect(conversationalAwarenessVolume.floatValue) { + sharedPreferences.edit { putInt("conversational_awareness_volume", conversationalAwarenessVolume.floatValue.roundToInt()) } } + StyledSlider( + label = stringResource(R.string.conversational_awareness_volume), + mutableFloatState = conversationalAwarenessVolume, + valueRange = 10f..85f, + startLabel = "10%", + endLabel = "85%", + onValueChange = { newValue -> conversationalAwarenessVolume.floatValue = newValue }, + independent = true + ) + + Spacer(modifier = Modifier.height(16.dp)) + + StyledToggle( + title = stringResource(R.string.quick_settings_tile), + label = stringResource(R.string.open_dialog_for_controlling), + description = stringResource(R.string.open_dialog_for_controlling_description), + checkedState = openDialogForControlling, + onCheckedChange = { + openDialogForControlling.value = it + sharedPreferences.edit { putString("qs_click_behavior", if (it) "dialog" else "activity") } + }, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + StyledToggle( + title = stringResource(R.string.ear_detection), + label = stringResource(R.string.disconnect_when_not_wearing), + description = stringResource(R.string.disconnect_when_not_wearing_description), + checkedState = disconnectWhenNotWearing, + sharedPreferenceKey = "disconnect_when_not_wearing", + sharedPreferences = sharedPreferences, + ) + Text( - text = stringResource(R.string.connection_mode).uppercase(), + text = stringResource(R.string.takeover_airpods_state), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp) ) - Spacer(modifier = Modifier.height(2.dp)) + Spacer(modifier = Modifier.height(4.dp)) - Column ( + Column( modifier = Modifier .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) - .padding(horizontal = 16.dp, vertical = 4.dp) + .padding(vertical = 4.dp) ) { - Row( + StyledToggle( + label = stringResource(R.string.takeover_disconnected), + description = stringResource(R.string.takeover_disconnected_desc), + checkedState = takeoverWhenDisconnected, + onCheckedChange = { + takeoverWhenDisconnected.value = it + sharedPreferences.edit { putBoolean("takeover_when_disconnected", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - bleOnlyMode = !bleOnlyMode - sharedPreferences.edit { putBoolean("ble_only_mode", bleOnlyMode)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.ble_only_mode), - fontSize = 16.sp, - color = textColor - ) - Text( - text = stringResource(R.string.ble_only_mode_description), - fontSize = 13.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } + .padding(horizontal = 12.dp) + ) - StyledSwitch( - checked = bleOnlyMode, - onCheckedChange = { - bleOnlyMode = it - sharedPreferences.edit { putBoolean("ble_only_mode", it)} - } - ) - } + StyledToggle( + label = stringResource(R.string.takeover_idle), + description = stringResource(R.string.takeover_idle_desc), + checkedState = takeoverWhenIdle, + onCheckedChange = { + takeoverWhenIdle.value = it + sharedPreferences.edit { putBoolean("takeover_when_idle", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.takeover_music), + description = stringResource(R.string.takeover_music_desc), + checkedState = takeoverWhenMusic, + onCheckedChange = { + takeoverWhenMusic.value = it + sharedPreferences.edit { putBoolean("takeover_when_music", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier + .padding(horizontal = 12.dp) + ) + + StyledToggle( + label = stringResource(R.string.takeover_call), + description = stringResource(R.string.takeover_call_desc), + checkedState = takeoverWhenCall, + onCheckedChange = { + takeoverWhenCall.value = it + sharedPreferences.edit { putBoolean("takeover_when_call", it)} + }, + independent = false + ) } + Spacer(modifier = Modifier.height(16.dp)) + Text( - text = stringResource(R.string.conversational_awareness).uppercase(), + text = stringResource(R.string.takeover_phone_state), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) + modifier = Modifier.padding(horizontal = 16.dp) ) - - Spacer(modifier = Modifier.height(2.dp)) - - Column ( + Spacer(modifier = Modifier.height(4.dp)) + Column( modifier = Modifier .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - val sliderValue = remember { mutableFloatStateOf(0f) } - LaunchedEffect(sliderValue) { - if (sharedPreferences.contains("conversational_awareness_volume")) { - sliderValue.floatValue = sharedPreferences.getInt("conversational_awareness_volume", 43).toFloat() - } - } - - fun updateConversationalAwarenessPauseMusic(enabled: Boolean) { - conversationalAwarenessPauseMusicEnabled = enabled - sharedPreferences.edit { putBoolean("conversational_awareness_pause_music", enabled)} - } - - fun updateRelativeConversationalAwarenessVolume(enabled: Boolean) { - relativeConversationalAwarenessVolumeEnabled = enabled - sharedPreferences.edit { putBoolean("relative_conversational_awareness_volume", enabled)} - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - updateConversationalAwarenessPauseMusic(!conversationalAwarenessPauseMusicEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.conversational_awareness_pause_music), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.conversational_awareness_pause_music_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = conversationalAwarenessPauseMusicEnabled, - onCheckedChange = { - updateConversationalAwarenessPauseMusic(it) - }, + RoundedCornerShape(28.dp) ) - } - - Row( + .padding(vertical = 4.dp) + ){ + StyledToggle( + label = stringResource(R.string.takeover_ringing_call), + description = stringResource(R.string.takeover_ringing_call_desc), + checkedState = takeoverWhenRingingCall, + onCheckedChange = { + takeoverWhenRingingCall.value = it + sharedPreferences.edit { putBoolean("takeover_when_ringing_call", it)} + }, + independent = false + ) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - updateRelativeConversationalAwarenessVolume(!relativeConversationalAwarenessVolumeEnabled) - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.relative_conversational_awareness_volume), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.relative_conversational_awareness_volume_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = relativeConversationalAwarenessVolumeEnabled, - onCheckedChange = { - updateRelativeConversationalAwarenessVolume(it) - } - ) - } - - Text( - text = stringResource(R.string.conversational_awareness_volume), - fontSize = 16.sp, - color = textColor, - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) + .padding(horizontal = 12.dp) ) - val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFFD9D9D9) - val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) - val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) - - Slider( - value = sliderValue.floatValue, - onValueChange = { - sliderValue.floatValue = it - sharedPreferences.edit { putInt("conversational_awareness_volume", it.toInt())} - }, - valueRange = 10f..85f, - onValueChangeFinished = { - sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat() - }, - modifier = Modifier - .fillMaxWidth() - .height(36.dp) - .padding(vertical = 4.dp), - colors = SliderDefaults.colors( - thumbColor = thumbColor, - activeTrackColor = activeTrackColor, - inactiveTrackColor = trackColor, - ), - thumb = { - Box( - modifier = Modifier - .size(24.dp) - .shadow(4.dp, CircleShape) - .background(thumbColor, CircleShape) - ) + StyledToggle( + label = stringResource(R.string.takeover_media_start), + description = stringResource(R.string.takeover_media_start_desc), + checkedState = takeoverWhenMediaStart, + onCheckedChange = { + takeoverWhenMediaStart.value = it + sharedPreferences.edit { putBoolean("takeover_when_media_start", it)} }, - 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)) - ) - } - } + independent = false ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = "10%", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.7f) - ), - modifier = Modifier.padding(start = 4.dp) - ) - Text( - text = "85%", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.7f) - ), - modifier = Modifier.padding(end = 4.dp) - ) - } } Text( - text = stringResource(R.string.quick_settings_tile).uppercase(), + text = stringResource(R.string.advanced_options), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -572,24 +489,18 @@ fun AppSettingsScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(horizontal = 16.dp, vertical = 4.dp) ) { - fun updateQsClickBehavior(enabled: Boolean) { - openDialogForControlling = enabled - sharedPreferences.edit { putString("qs_click_behavior", if (enabled) "dialog" else "cycle")} - } - Row( modifier = Modifier .fillMaxWidth() - .clickable( + .clickable ( + onClick = { showIrkDialog.value = true }, indication = null, interactionSource = remember { MutableInteractionSource() } - ) { - updateQsClickBehavior(!openDialogForControlling) - }, + ), verticalAlignment = Alignment.CenterVertically ) { Column( @@ -599,64 +510,33 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = stringResource(R.string.open_dialog_for_controlling), + text = stringResource(R.string.set_identity_resolving_key), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = stringResource(R.string.open_dialog_for_controlling_description), + text = stringResource(R.string.set_identity_resolving_key_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, ) } - - StyledSwitch( - checked = openDialogForControlling, - onCheckedChange = { - updateQsClickBehavior(it) - } - ) } - } - - Text( - text = stringResource(R.string.ear_detection).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) - ) - - Spacer(modifier = Modifier.height(2.dp)) - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - fun updateDisconnectWhenNotWearing(enabled: Boolean) { - disconnectWhenNotWearing = enabled - sharedPreferences.edit { putBoolean("disconnect_when_not_wearing", enabled)} - } + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + ) Row( modifier = Modifier .fillMaxWidth() - .clickable( + .clickable ( + onClick = { showEncKeyDialog.value = true }, indication = null, interactionSource = remember { MutableInteractionSource() } - ) { - updateDisconnectWhenNotWearing(!disconnectWhenNotWearing) - }, + ), verticalAlignment = Alignment.CenterVertically ) { Column( @@ -666,565 +546,84 @@ fun AppSettingsScreen(navController: NavController) { .padding(end = 4.dp) ) { Text( - text = stringResource(R.string.disconnect_when_not_wearing), + text = stringResource(R.string.set_encryption_key), fontSize = 16.sp, color = textColor ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = stringResource(R.string.disconnect_when_not_wearing_description), + text = stringResource(R.string.set_encryption_key_description), fontSize = 14.sp, color = textColor.copy(0.6f), lineHeight = 16.sp, ) } - - StyledSwitch( - checked = disconnectWhenNotWearing, - onCheckedChange = { - updateDisconnectWhenNotWearing(it) - } - ) } } - Text( - text = stringResource(R.string.takeover_header).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) + Spacer(modifier = Modifier.height(16.dp)) + + StyledToggle( + label = stringResource(R.string.use_alternate_head_tracking_packets), + description = stringResource(R.string.use_alternate_head_tracking_packets_description), + checkedState = useAlternateHeadTrackingPackets, + onCheckedChange = { + useAlternateHeadTrackingPackets.value = it + sharedPreferences.edit { putBoolean("use_alternate_head_tracking_packets", it)} + }, + independent = true ) - Spacer(modifier = Modifier.height(2.dp)) - - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_airpods_state), - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - modifier = Modifier.padding(top = 12.dp, bottom = 4.dp) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenDisconnected = !takeoverWhenDisconnected - sharedPreferences.edit { putBoolean("takeover_when_disconnected", takeoverWhenDisconnected)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_disconnected), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_disconnected_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenDisconnected, - onCheckedChange = { - takeoverWhenDisconnected = it - sharedPreferences.edit { putBoolean("takeover_when_disconnected", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenIdle = !takeoverWhenIdle - sharedPreferences.edit { putBoolean("takeover_when_idle", takeoverWhenIdle)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_idle), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_idle_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenIdle, - onCheckedChange = { - takeoverWhenIdle = it - sharedPreferences.edit { putBoolean("takeover_when_idle", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenMusic = !takeoverWhenMusic - sharedPreferences.edit { putBoolean("takeover_when_music", takeoverWhenMusic)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_music), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_music_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenMusic, - onCheckedChange = { - takeoverWhenMusic = it - sharedPreferences.edit { putBoolean("takeover_when_music", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenCall = !takeoverWhenCall - sharedPreferences.edit { putBoolean("takeover_when_call", takeoverWhenCall)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_call), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_call_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenCall, - onCheckedChange = { - takeoverWhenCall = it - sharedPreferences.edit { putBoolean("takeover_when_call", it)} - } - ) - } - - Spacer(modifier = Modifier.height(12.dp)) - - Text( - text = stringResource(R.string.takeover_phone_state), - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - color = textColor, - modifier = Modifier.padding(top = 8.dp, bottom = 4.dp) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenRingingCall = !takeoverWhenRingingCall - sharedPreferences.edit { putBoolean("takeover_when_ringing_call", takeoverWhenRingingCall)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_ringing_call), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_ringing_call_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenRingingCall, - onCheckedChange = { - takeoverWhenRingingCall = it - sharedPreferences.edit { putBoolean("takeover_when_ringing_call", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - takeoverWhenMediaStart = !takeoverWhenMediaStart - sharedPreferences.edit { putBoolean("takeover_when_media_start", takeoverWhenMediaStart)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.takeover_media_start), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.takeover_media_start_desc), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = takeoverWhenMediaStart, - onCheckedChange = { - takeoverWhenMediaStart = it - sharedPreferences.edit { putBoolean("takeover_when_media_start", it)} - } - ) - } - } + Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(R.string.advanced_options).uppercase(), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Light, - color = textColor.copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 24.dp) + NavigationButton( + to = "troubleshooting", + name = stringResource(R.string.troubleshooting), + navController = navController, + independent = true, + description = stringResource(R.string.troubleshooting_description) ) - Spacer(modifier = Modifier.height(2.dp)) - - Column( - modifier = Modifier - .fillMaxWidth() - .background( - backgroundColor, - RoundedCornerShape(14.dp) - ) - .padding(horizontal = 16.dp, vertical = 4.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - showIrkDialog = true - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.set_identity_resolving_key), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.set_identity_resolving_key_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - showEncKeyDialog = true - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.set_encryption_key), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.set_encryption_key_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - useAlternateHeadTrackingPackets = !useAlternateHeadTrackingPackets - sharedPreferences.edit { - putBoolean( - "use_alternate_head_tracking_packets", - useAlternateHeadTrackingPackets)} - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.use_alternate_head_tracking_packets), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.use_alternate_head_tracking_packets_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - - StyledSwitch( - checked = useAlternateHeadTrackingPackets, - onCheckedChange = { - useAlternateHeadTrackingPackets = it - sharedPreferences.edit { putBoolean("use_alternate_head_tracking_packets", it)} - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { - navController.navigate("troubleshooting") - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.troubleshooting), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.troubleshooting_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - } - } - - LaunchedEffect(Unit) { - actAsAppleDevice = RadareOffsetFinder.isSdpOffsetAvailable() - } - val restartBluetoothText = stringResource(R.string.found_offset_restart_bluetooth) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable( - enabled = !isProcessingSdp, - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - if (!isProcessingSdp) { - val newValue = !actAsAppleDevice - actAsAppleDevice = newValue - isProcessingSdp = true - coroutineScope.launch { - if (newValue) { - val radareOffsetFinder = RadareOffsetFinder(context) - val success = radareOffsetFinder.findSdpOffset() - if (success) { - Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() - } - } else { - RadareOffsetFinder.clearSdpOffset() - } - isProcessingSdp = false - } + LaunchedEffect(Unit) { + actAsAppleDevice.value = RadareOffsetFinder.isSdpOffsetAvailable() + } + val restartBluetoothText = stringResource(R.string.found_offset_restart_bluetooth) + + StyledToggle( + label = stringResource(R.string.act_as_an_apple_device), + description = stringResource(R.string.act_as_an_apple_device_description), + checkedState = actAsAppleDevice, + onCheckedChange = { + actAsAppleDevice.value = it + isProcessingSdp.value = true + coroutineScope.launch { + if (it) { + val radareOffsetFinder = RadareOffsetFinder(context) + val success = radareOffsetFinder.findSdpOffset() + if (success) { + Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() } - }, - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = 8.dp) - .padding(end = 4.dp) - ) { - Text( - text = stringResource(R.string.act_as_an_apple_device), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = stringResource(R.string.act_as_an_apple_device_description), - fontSize = 14.sp, - color = textColor.copy(0.6f), - lineHeight = 16.sp, - ) - if (actAsAppleDevice) { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.act_as_an_apple_device_warning), - fontSize = 12.sp, - color = MaterialTheme.colorScheme.error, - lineHeight = 14.sp, - ) + } else { + RadareOffsetFinder.clearSdpOffset() } + isProcessingSdp.value = false } - StyledSwitch( - checked = actAsAppleDevice, - onCheckedChange = { - if (!isProcessingSdp) { - actAsAppleDevice = it - isProcessingSdp = true - coroutineScope.launch { - if (it) { - val radareOffsetFinder = RadareOffsetFinder(context) - val success = radareOffsetFinder.findSdpOffset() - if (success) { - Toast.makeText(context, restartBluetoothText, Toast.LENGTH_LONG).show() - } - } else { - RadareOffsetFinder.clearSdpOffset() - } - isProcessingSdp = false - } - } - }, - enabled = !isProcessingSdp - ) - } - } + }, + independent = true, + enabled = !isProcessingSdp.value + ) Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { showResetDialog = true }, + onClick = { showResetDialog.value = true }, modifier = Modifier .fillMaxWidth() .height(50.dp), colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.errorContainer ), - shape = RoundedCornerShape(14.dp) + shape = RoundedCornerShape(28.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -1251,9 +650,9 @@ fun AppSettingsScreen(navController: NavController) { Spacer(modifier = Modifier.height(32.dp)) - if (showResetDialog) { + if (showResetDialog.value) { AlertDialog( - onDismissRequest = { showResetDialog = false }, + onDismissRequest = { showResetDialog.value = false }, title = { Text( "Reset Hook Offset", @@ -1289,7 +688,7 @@ fun AppSettingsScreen(navController: NavController) { Toast.LENGTH_SHORT ).show() } - showResetDialog = false + showResetDialog.value = false }, colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.error @@ -1304,7 +703,7 @@ fun AppSettingsScreen(navController: NavController) { }, dismissButton = { TextButton( - onClick = { showResetDialog = false } + onClick = { showResetDialog.value = false } ) { Text( "Cancel", @@ -1316,9 +715,9 @@ fun AppSettingsScreen(navController: NavController) { ) } - if (showIrkDialog) { + if (showIrkDialog.value) { AlertDialog( - onDismissRequest = { showIrkDialog = false }, + onDismissRequest = { showIrkDialog.value = false }, title = { Text( stringResource(R.string.set_identity_resolving_key), @@ -1335,13 +734,13 @@ fun AppSettingsScreen(navController: NavController) { ) OutlinedTextField( - value = irkValue, + value = irkValue.value, onValueChange = { - irkValue = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } - irkError = null + irkValue.value = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } + irkError.value = null }, modifier = Modifier.fillMaxWidth(), - isError = irkError != null, + isError = irkError.value != null, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Ascii, capitalization = KeyboardCapitalization.None @@ -1351,7 +750,7 @@ fun AppSettingsScreen(navController: NavController) { unfocusedBorderColor = if (isDarkTheme) Color.Gray else Color.LightGray ), supportingText = { - if (irkError != null) { + if (irkError.value != null) { Text(stringResource(R.string.must_be_32_hex_chars), color = MaterialTheme.colorScheme.error) } }, @@ -1364,15 +763,15 @@ fun AppSettingsScreen(navController: NavController) { val errorText = stringResource(R.string.error_converting_hex) TextButton( onClick = { - if (!validateHexInput(irkValue)) { - irkError = "Must be exactly 32 hex characters" + if (!validateHexInput(irkValue.value)) { + irkError.value = "Must be exactly 32 hex characters" return@TextButton } try { val hexBytes = ByteArray(16) for (i in 0 until 16) { - val hexByte = irkValue.substring(i * 2, i * 2 + 2) + val hexByte = irkValue.value.substring(i * 2, i * 2 + 2) hexBytes[i] = hexByte.toInt(16).toByte() } @@ -1380,9 +779,9 @@ fun AppSettingsScreen(navController: NavController) { sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.IRK.name, base64Value)} Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() - showIrkDialog = false + showIrkDialog.value = false } catch (e: Exception) { - irkError = errorText + " " + (e.message ?: "Unknown error") + irkError.value = errorText + " " + (e.message ?: "Unknown error") } } ) { @@ -1395,7 +794,7 @@ fun AppSettingsScreen(navController: NavController) { }, dismissButton = { TextButton( - onClick = { showIrkDialog = false } + onClick = { showIrkDialog.value = false } ) { Text( "Cancel", @@ -1407,9 +806,9 @@ fun AppSettingsScreen(navController: NavController) { ) } - if (showEncKeyDialog) { + if (showEncKeyDialog.value) { AlertDialog( - onDismissRequest = { showEncKeyDialog = false }, + onDismissRequest = { showEncKeyDialog.value = false }, title = { Text( stringResource(R.string.set_encryption_key), @@ -1426,13 +825,13 @@ fun AppSettingsScreen(navController: NavController) { ) OutlinedTextField( - value = encKeyValue, + value = encKeyValue.value, onValueChange = { - encKeyValue = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } - encKeyError = null + encKeyValue.value = it.lowercase().filter { char -> char.isDigit() || char in 'a'..'f' } + encKeyError.value = null }, modifier = Modifier.fillMaxWidth(), - isError = encKeyError != null, + isError = encKeyError.value != null, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Ascii, capitalization = KeyboardCapitalization.None @@ -1442,7 +841,7 @@ fun AppSettingsScreen(navController: NavController) { unfocusedBorderColor = if (isDarkTheme) Color.Gray else Color.LightGray ), supportingText = { - if (encKeyError != null) { + if (encKeyError.value != null) { Text(stringResource(R.string.must_be_32_hex_chars), color = MaterialTheme.colorScheme.error) } }, @@ -1455,15 +854,15 @@ fun AppSettingsScreen(navController: NavController) { val errorText = stringResource(R.string.error_converting_hex) TextButton( onClick = { - if (!validateHexInput(encKeyValue)) { - encKeyError = "Must be exactly 32 hex characters" + if (!validateHexInput(encKeyValue.value)) { + encKeyError.value = "Must be exactly 32 hex characters" return@TextButton } try { val hexBytes = ByteArray(16) for (i in 0 until 16) { - val hexByte = encKeyValue.substring(i * 2, i * 2 + 2) + val hexByte = encKeyValue.value.substring(i * 2, i * 2 + 2) hexBytes[i] = hexByte.toInt(16).toByte() } @@ -1471,9 +870,9 @@ fun AppSettingsScreen(navController: NavController) { sharedPreferences.edit { putString(AACPManager.Companion.ProximityKeyType.ENC_KEY.name, base64Value)} Toast.makeText(context, successText, Toast.LENGTH_SHORT).show() - showEncKeyDialog = false + showEncKeyDialog.value = false } catch (e: Exception) { - encKeyError = errorText + " " + (e.message ?: "Unknown error") + encKeyError.value = errorText + " " + (e.message ?: "Unknown error") } } ) { @@ -1486,7 +885,7 @@ fun AppSettingsScreen(navController: NavController) { }, dismissButton = { TextButton( - onClick = { showEncKeyDialog = false } + onClick = { showEncKeyDialog.value = false } ) { Text( "Cancel", diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt index f9dbdab8..fc3bf055 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/DebugScreen.kt @@ -40,14 +40,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Send import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -78,6 +75,8 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.delay @@ -325,14 +324,15 @@ fun DebugScreen(navController: NavController) { } val isDarkTheme = isSystemInDarkTheme() - + val backdrop = rememberLayerBackdrop() StyledScaffold( title = "Debug", navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, actionButtons = listOf( @@ -344,6 +344,7 @@ fun DebugScreen(navController: NavController) { }, icon = "􀈑", darkMode = isDarkTheme, + backdrop = backdrop ) } ), @@ -353,6 +354,7 @@ fun DebugScreen(navController: NavController) { .fillMaxSize() .hazeSource(hazeState) .navigationBarsPadding() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -391,11 +393,13 @@ fun DebugScreen(navController: NavController) { ) ) { Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = if (isSent) Icons.AutoMirrored.Filled.KeyboardArrowLeft else Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = null, - tint = if (isSent) Color.Green else Color.Red, - modifier = Modifier.size(24.dp) + Text( + text = if (isSent) "􀆉" else "􀆊", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isSent) Color(0xFF4CD964) else Color(0xFFFF3B30) + ), ) Spacer(modifier = Modifier.width(4.dp)) Column { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt index ad71bb45..a42241a2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt @@ -42,10 +42,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -87,12 +84,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.kavishdevar.librepods.R +import me.kavishdevar.librepods.composables.StyledButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold import me.kavishdevar.librepods.composables.StyledToggle @@ -116,18 +116,19 @@ fun HeadTrackingScreen(navController: NavController) { } } val isDarkTheme = isSystemInDarkTheme() - val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) val textColor = if (isDarkTheme) Color.White else Color.Black val scrollState = rememberScrollState() - + val backdrop = rememberLayerBackdrop() StyledScaffold ( title = stringResource(R.string.head_tracking), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, actionButtons = listOf( @@ -144,72 +145,95 @@ fun HeadTrackingScreen(navController: NavController) { } }, icon = if (isActive) "􀊅" else "􀊃", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ), ) { spacerHeight, hazeState -> - Column ( + val backdrop = rememberLayerBackdrop() + val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) + + var gestureText by remember { mutableStateOf("") } + val coroutineScope = rememberCoroutineScope() + + var lastClickTime by remember { mutableLongStateOf(0L) } + var shouldExplode by remember { mutableStateOf(false) } + Column( modifier = Modifier - .fillMaxSize() - .padding(top = 8.dp) - .padding(horizontal = 16.dp) - .verticalScroll(scrollState) - .hazeSource(state = hazeState) + .layerBackdrop(backdrop) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { - Spacer(modifier = Modifier.height(spacerHeight)) - val sharedPreferences = - LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) + Column ( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .padding(horizontal = 16.dp) + .verticalScroll(scrollState) + .hazeSource(state = hazeState) + .layerBackdrop( + backdrop = backdrop + ) + ) { + Spacer(modifier = Modifier.height(spacerHeight)) + StyledToggle( + label = "Head Gestures", + sharedPreferences = sharedPreferences, + sharedPreferenceKey = "head_gestures", + ) - var gestureText by remember { mutableStateOf("") } - val coroutineScope = rememberCoroutineScope() + Spacer(modifier = Modifier.height(2.dp)) + Text( + stringResource(R.string.head_gestures_details), + style = TextStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = textColor.copy(0.6f) + ), + modifier = Modifier.padding(start = 4.dp) + ) - StyledToggle( - label = "Head Gestures", - sharedPreferences = sharedPreferences, - sharedPreferenceKey = "head_gestures", - ) - - Spacer(modifier = Modifier.height(2.dp)) - Text( - stringResource(R.string.head_gestures_details), - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Normal, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor.copy(0.6f) - ), - modifier = Modifier.padding(start = 4.dp) - ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + "Head Orientation", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = textColor + ), + modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) + ) + HeadVisualization() - Spacer(modifier = Modifier.height(16.dp)) - Text( - "Head Orientation", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor - ), - modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) - ) - HeadVisualization() - - Spacer(modifier = Modifier.height(16.dp)) - Text( - "Velocity", - style = TextStyle( - fontSize = 18.sp, - fontWeight = FontWeight.Medium, - fontFamily = FontFamily(Font(R.font.sf_pro)), - color = textColor - ), - modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) - ) - AccelerationPlot() + Spacer(modifier = Modifier.height(16.dp)) + Text( + "Velocity", + style = TextStyle( + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = textColor + ), + modifier = Modifier.padding(start = 4.dp, bottom = 8.dp, top = 8.dp) + ) + AccelerationPlot() + + Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.height(16.dp)) - Button ( + LaunchedEffect(gestureText) { + if (gestureText.isNotEmpty()) { + lastClickTime = System.currentTimeMillis() + delay(3000) + if (System.currentTimeMillis() - lastClickTime >= 3000) { + shouldExplode = true + } + } + } + } + StyledButton( onClick = { gestureText = "Shake your head or nod!" coroutineScope.launch { @@ -217,13 +241,9 @@ fun HeadTrackingScreen(navController: NavController) { gestureText = if (accepted) "\"Yes\" gesture detected." else "\"No\" gesture detected." } }, - modifier = Modifier - .fillMaxWidth() - .height(55.dp), - colors = ButtonDefaults.buttonColors( - containerColor = backgroundColor - ), - shape = RoundedCornerShape(8.dp) + backdrop = backdrop, + modifier = Modifier.fillMaxWidth(0.75f), + maxScale = 0.05f ) { Text( "Test Head Gestures", @@ -235,19 +255,6 @@ fun HeadTrackingScreen(navController: NavController) { ), ) } - var lastClickTime by remember { mutableLongStateOf(0L) } - var shouldExplode by remember { mutableStateOf(false) } - - LaunchedEffect(gestureText) { - if (gestureText.isNotEmpty()) { - lastClickTime = System.currentTimeMillis() - delay(3000) - if (System.currentTimeMillis() - lastClickTime >= 3000) { - shouldExplode = true - } - } - } - Box( contentAlignment = Alignment.Center, modifier = Modifier.padding(top = 12.dp, bottom = 24.dp) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt index 6c642a52..1a48a128 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidAdjustmentsScreen.kt @@ -41,6 +41,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi @@ -77,14 +79,15 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") val aacpManager = remember { ServiceManager.getService()?.aacpManager } - + val backdrop = rememberLayerBackdrop() StyledScaffold( title = stringResource(R.string.adjustments), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight -> @@ -92,6 +95,7 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController modifier = Modifier .hazeSource(hazeState) .fillMaxSize() + .layerBackdrop(backdrop) .verticalScroll(verticalScrollState) .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -282,7 +286,7 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController } StyledSlider( - label = stringResource(R.string.amplification).uppercase(), + label = stringResource(R.string.amplification), valueRange = -1f..1f, mutableFloatState = amplificationSliderValue, onValueChange = { @@ -301,20 +305,20 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController ) StyledSlider( - label = stringResource(R.string.balance).uppercase(), + label = stringResource(R.string.balance), valueRange = -1f..1f, mutableFloatState = balanceSliderValue, onValueChange = { balanceSliderValue.floatValue = it }, - snapPoints = listOf(0f), + snapPoints = listOf(-1f, 0f, 1f), startLabel = stringResource(R.string.left), endLabel = stringResource(R.string.right), independent = true, ) StyledSlider( - label = stringResource(R.string.tone).uppercase(), + label = stringResource(R.string.tone), valueRange = -1f..1f, mutableFloatState = toneSliderValue, onValueChange = { @@ -326,7 +330,7 @@ fun HearingAidAdjustmentsScreen(@Suppress("unused") navController: NavController ) StyledSlider( - label = stringResource(R.string.ambient_noise_reduction).uppercase(), + label = stringResource(R.string.ambient_noise_reduction), valueRange = 0f..1f, mutableFloatState = ambientNoiseReductionSliderValue, onValueChange = { diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 106ea482..28e4c989 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -20,15 +20,10 @@ package me.kavishdevar.librepods.screens import android.annotation.SuppressLint import android.util.Log -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.tween import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -37,25 +32,18 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font @@ -74,9 +62,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.ConfirmationDialog +import me.kavishdevar.librepods.composables.NavigationButton import me.kavishdevar.librepods.composables.StyledIconButton import me.kavishdevar.librepods.composables.StyledScaffold -import me.kavishdevar.librepods.composables.StyledSwitch import me.kavishdevar.librepods.composables.StyledToggle import me.kavishdevar.librepods.services.ServiceManager import me.kavishdevar.librepods.utils.AACPManager @@ -117,7 +105,8 @@ fun HearingAidScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, actionButtons = emptyList(), @@ -129,7 +118,7 @@ fun HearingAidScreen(navController: NavController) { .hazeSource(hazeState) .fillMaxSize() .verticalScroll(verticalScrollState) - .padding(16.dp), + .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -175,22 +164,22 @@ fun HearingAidScreen(navController: NavController) { } fun onAdjustPhoneChange(value: Boolean) { - adjustPhoneEnabled.value = value + // TODO } fun onAdjustMediaChange(value: Boolean) { - adjustMediaEnabled.value = value + // TODO } Text( - text = stringResource(R.string.hearing_aid).uppercase(), + text = stringResource(R.string.hearing_aid), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp) ) val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) @@ -198,9 +187,9 @@ fun HearingAidScreen(navController: NavController) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .clip( - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) ) { StyledToggle( @@ -209,31 +198,17 @@ fun HearingAidScreen(navController: NavController) { independent = false ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) + ) + NavigationButton( + to = "hearing_aid_adjustments", + name = stringResource(R.string.adjustments), + navController, + independent = false ) - - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { navController.navigate("hearing_aid_adjustments") } - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.adjustments), - fontSize = 16.sp, - color = textColor - ) - Spacer(modifier = Modifier.weight(1f)) - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = null, - tint = textColor - ) - } } Text( text = stringResource(R.string.hearing_aid_description), @@ -243,12 +218,12 @@ fun HearingAidScreen(navController: NavController) { color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(horizontal = 8.dp) + modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(16.dp)) - + StyledToggle( - title = stringResource(R.string.media_assist).uppercase(), + title = stringResource(R.string.media_assist), label = stringResource(R.string.media_assist), checkedState = mediaAssistEnabled, independent = true, @@ -260,99 +235,27 @@ fun HearingAidScreen(navController: NavController) { Column ( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) ) { - val isDarkThemeLocal = isSystemInDarkTheme() - var backgroundColorAdjustMedia by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColorAdjustMedia by animateColorAsState(targetValue = backgroundColorAdjustMedia, animationSpec = tween(durationMillis = 500)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColorAdjustMedia = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - onAdjustMediaChange(!adjustMediaEnabled.value) - } - ) - } - .background( - animatedBackgroundColorAdjustMedia, - RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - ), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.adjust_media), - modifier = Modifier.weight(1f), - style = TextStyle( - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Normal, - color = textColor - ) - ) - StyledSwitch( - checked = adjustMediaEnabled.value, - onCheckedChange = { - onAdjustMediaChange(it) - }, - ) - } - + StyledToggle( + label = stringResource(R.string.adjust_media), + checkedState = adjustMediaEnabled, + onCheckedChange = { onAdjustMediaChange(it) }, + independent = false + ) HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 12.dp, end = 0.dp) + .padding(horizontal = 12.dp) ) - var backgroundColorAdjustPhone by remember { mutableStateOf(if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } - val animatedBackgroundColorAdjustPhone by animateColorAsState(targetValue = backgroundColorAdjustPhone, animationSpec = tween(durationMillis = 500)) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - .pointerInput(Unit) { - detectTapGestures( - onPress = { - backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0x40888888) else Color(0x40D9D9D9) - tryAwaitRelease() - backgroundColorAdjustPhone = if (isDarkThemeLocal) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) - }, - onTap = { - onAdjustPhoneChange(!adjustPhoneEnabled.value) - } - ) - } - .background( - animatedBackgroundColorAdjustPhone, - RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) - ), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.adjust_calls), - modifier = Modifier.weight(1f), - style = TextStyle( - fontSize = 16.sp, - fontFamily = FontFamily(Font(R.font.sf_pro)), - fontWeight = FontWeight.Normal, - color = textColor - ) - ) - StyledSwitch( - checked = adjustPhoneEnabled.value, - onCheckedChange = { - onAdjustPhoneChange(it) - }, - ) - } + StyledToggle( + label = stringResource(R.string.adjust_calls), + checkedState = adjustPhoneEnabled, + onCheckedChange = { onAdjustPhoneChange(it) }, + independent = false + ) } } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt index e6420bbe..7e886baf 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/Onboarding.kt @@ -73,6 +73,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -151,6 +153,7 @@ fun Onboarding(navController: NavController, activityContext: Context) { isComplete = true } } + val backdrop = rememberLayerBackdrop() StyledScaffold( title = "Setting Up", actionButtons = listOf( @@ -160,7 +163,8 @@ fun Onboarding(navController: NavController, activityContext: Context) { showSkipDialog = true }, icon = "􀊋", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) @@ -168,6 +172,7 @@ fun Onboarding(navController: NavController, activityContext: Context) { Column( modifier = Modifier .fillMaxSize() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index 032cbc47..d4613225 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -23,6 +23,7 @@ package me.kavishdevar.librepods.screens import android.content.Context import android.util.Log import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures @@ -37,8 +38,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -50,11 +49,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily @@ -63,6 +62,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.StyledIconButton @@ -76,20 +77,20 @@ import kotlin.io.encoding.ExperimentalEncodingApi @Composable fun RightDivider() { HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 72.dp) + .padding(start = 72.dp, end = 20.dp) ) } @Composable fun RightDividerNoIcon() { HorizontalDivider( - thickness = 1.5.dp, + thickness = 1.dp, color = Color(0x40888888), modifier = Modifier - .padding(start = 20.dp) + .padding(start = 20.dp, end = 20.dp) ) } @@ -117,19 +118,22 @@ fun LongPress(navController: NavController, name: String) { val longPressActionPref = sharedPreferences.getString(prefKey, StemAction.CYCLE_NOISE_CONTROL_MODES.name) Log.d("PressAndHoldSettingsScreen", "Long press action preference ($prefKey): $longPressActionPref") var longPressAction by remember { mutableStateOf(StemAction.valueOf(longPressActionPref ?: StemAction.CYCLE_NOISE_CONTROL_MODES.name)) } + val backdrop = rememberLayerBackdrop() StyledScaffold( title = name, navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ) { spacerHeight -> val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) Column ( modifier = Modifier + .layerBackdrop(backdrop) .fillMaxSize() .padding(top = 8.dp) .padding(horizontal = 16.dp) @@ -138,11 +142,11 @@ fun LongPress(navController: NavController, name: String) { Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)), + .background(backgroundColor, RoundedCornerShape(28.dp)), horizontalAlignment = Alignment.CenterHorizontally ) { LongPressActionElement( - name = "Noise Control", + name = stringResource(R.string.noise_control), selected = longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES, onClick = { longPressAction = StemAction.CYCLE_NOISE_CONTROL_MODES @@ -153,7 +157,7 @@ fun LongPress(navController: NavController, name: String) { ) RightDividerNoIcon() LongPressActionElement( - name = "Digital Assistant", + name = stringResource(R.string.digital_assistant), selected = longPressAction == StemAction.DIGITAL_ASSISTANT, onClick = { longPressAction = StemAction.DIGITAL_ASSISTANT @@ -165,23 +169,25 @@ fun LongPress(navController: NavController, name: String) { } if (longPressAction == StemAction.CYCLE_NOISE_CONTROL_MODES) { + Spacer(modifier = Modifier.height(32.dp)) Text( - text = "NOISE CONTROL", + text = stringResource(R.string.noise_control), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), ), fontFamily = FontFamily(Font(R.font.sf_pro)), modifier = Modifier - .padding(top = 32.dp, bottom = 4.dp) - .padding(horizontal = 8.dp) + .padding(horizontal = 18.dp) ) + Spacer(modifier = Modifier.height(8.dp)) + Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)), + .background(backgroundColor, RoundedCornerShape(28.dp)), horizontalAlignment = Alignment.CenterHorizontally ) { val offListeningModeValue = ServiceManager.getService()!!.aacpManager.controlCommandStatusList.find { @@ -189,28 +195,28 @@ fun LongPress(navController: NavController, name: String) { }?.value?.takeIf { it.isNotEmpty() }?.get(0) val offListeningMode = offListeningModeValue == 1.toByte() ListeningModeElement( - name = "Off", + name = stringResource(R.string.off), enabled = offListeningMode, resourceId = R.drawable.noise_cancellation, isFirst = true) if (offListeningMode) RightDivider() ListeningModeElement( - name = "Transparency", + name = stringResource(R.string.transparency), resourceId = R.drawable.transparency, isFirst = !offListeningMode) RightDivider() ListeningModeElement( - name = "Adaptive", + name = stringResource(R.string.adaptive), resourceId = R.drawable.adaptive) RightDivider() ListeningModeElement( - name = "Noise Cancellation", + name = stringResource(R.string.noise_cancellation), resourceId = R.drawable.noise_cancellation, isLast = true) } - Spacer(modifier = Modifier.height(4.dp)) + Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Press and hold the stem to cycle between the selected noise control modes.", + text = stringResource(R.string.press_and_hold_noise_control_description), style = TextStyle( fontSize = 12.sp, fontWeight = FontWeight.Light, @@ -218,7 +224,7 @@ fun LongPress(navController: NavController, name: String) { fontFamily = FontFamily(Font(R.font.sf_pro)) ), modifier = Modifier - .padding(horizontal = 8.dp) + .padding(horizontal = 18.dp) ) } } @@ -329,8 +335,8 @@ fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, } val shape = when { - isFirst -> RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - isLast -> RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + isFirst -> RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) + isLast -> RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp) else -> RoundedCornerShape(0.dp) } var backgroundColor by remember { mutableStateOf(if (darkMode) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } @@ -352,7 +358,7 @@ fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, }, ) } - .padding(horizontal = 16.dp, vertical = 0.dp), + .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { @@ -384,24 +390,19 @@ fun ListeningModeElement(name: String, enabled: Boolean = true, resourceId: Int, fontFamily = FontFamily(Font(R.font.sf_pro)), ) } - Checkbox( - checked = checked.value, - onCheckedChange = { valueChanged() }, - colors = CheckboxDefaults.colors().copy( - checkedCheckmarkColor = Color(0xFF007AFF), - uncheckedCheckmarkColor = Color.Transparent, - checkedBoxColor = Color.Transparent, - uncheckedBoxColor = Color.Transparent, - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent, - disabledBorderColor = Color.Transparent, - disabledCheckedBoxColor = Color.Transparent, - disabledUncheckedBoxColor = Color.Transparent, - disabledUncheckedBorderColor = Color.Transparent + + val floatAnimateState by animateFloatAsState( + targetValue = if (checked.value) 1f else 0f, + animationSpec = tween(durationMillis = 300) + ) + Text( + text = "􀆅", + style = TextStyle( + fontSize = 20.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = Color(0xFF007AFF).copy(alpha = floatAnimateState), ), - modifier = Modifier - .height(24.dp) - .scale(1.5f), + modifier = Modifier.padding(end = 4.dp) ) } } @@ -417,15 +418,15 @@ fun LongPressActionElement( ) { val darkMode = isSystemInDarkTheme() val shape = when { - isFirst -> RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp) - isLast -> RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp) + isFirst -> RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) + isLast -> RoundedCornerShape(bottomStart = 28.dp, bottomEnd = 28.dp) else -> RoundedCornerShape(0.dp) } var backgroundColor by remember { mutableStateOf(if (darkMode) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) } val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500)) Row( modifier = Modifier - .height(48.dp) + .height(55.dp) .background(animatedBackgroundColor, shape) .pointerInput(Unit) { detectTapGestures( @@ -437,7 +438,7 @@ fun LongPressActionElement( } ) } - .padding(horizontal = 16.dp, vertical = 0.dp), + .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { @@ -449,24 +450,18 @@ fun LongPressActionElement( .weight(1f) .padding(start = 4.dp) ) - Checkbox( - checked = selected, - onCheckedChange = { onClick() }, - colors = CheckboxDefaults.colors().copy( - checkedCheckmarkColor = Color(0xFF007AFF), - uncheckedCheckmarkColor = Color.Transparent, - checkedBoxColor = Color.Transparent, - uncheckedBoxColor = Color.Transparent, - checkedBorderColor = Color.Transparent, - uncheckedBorderColor = Color.Transparent, - disabledBorderColor = Color.Transparent, - disabledCheckedBoxColor = Color.Transparent, - disabledUncheckedBoxColor = Color.Transparent, - disabledUncheckedBorderColor = Color.Transparent + val floatAnimateState by animateFloatAsState( + targetValue = if (selected) 1f else 0f, + animationSpec = tween(durationMillis = 300) + ) + Text( + text = "􀆅", + style = TextStyle( + fontSize = 20.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = Color(0xFF007AFF).copy(alpha = floatAnimateState) ), - modifier = Modifier - .height(24.dp) - .scale(1.5f), + modifier = Modifier.padding(end = 4.dp) ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt index 9dd1f959..11f5eba7 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/RenameScreen.kt @@ -32,11 +32,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf @@ -52,12 +50,16 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.edit import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R import me.kavishdevar.librepods.composables.StyledIconButton @@ -81,19 +83,23 @@ fun RenameScreen(navController: NavController) { name.value = name.value.copy(selection = TextRange(name.value.text.length)) } + val backdrop = rememberLayerBackdrop() + StyledScaffold( title = stringResource(R.string.name), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) }, ) { spacerHeight -> Column( modifier = Modifier .fillMaxSize() + .layerBackdrop(backdrop) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) @@ -105,10 +111,10 @@ fun RenameScreen(navController: NavController) { verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .height(55.dp) + .height(58.dp) .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(horizontal = 16.dp, vertical = 8.dp) ) { @@ -120,8 +126,9 @@ fun RenameScreen(navController: NavController) { ServiceManager.getService()?.setName(it.text) }, textStyle = TextStyle( - color = textColor, fontSize = 16.sp, + color = textColor, + fontFamily = FontFamily(Font(R.font.sf_pro)) ), singleLine = true, cursorBrush = SolidColor(cursorColor), @@ -138,14 +145,15 @@ fun RenameScreen(navController: NavController) { IconButton( onClick = { name.value = TextFieldValue("") - sharedPreferences.edit { putString("name", "") } - ServiceManager.getService()?.setName("") } ) { - Icon( - Icons.Default.Clear, - contentDescription = "Clear", - tint = if (isDarkTheme) Color.White else Color.Black + Text( + text = "􀁡", + style = TextStyle( + fontSize = 16.sp, + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f) + ), ) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt index a0a8855a..ebee5a54 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TransparencySettingsScreen.kt @@ -59,6 +59,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.delay @@ -95,19 +97,23 @@ fun TransparencySettingsScreen(navController: NavController) { val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFFFFFFFF) + val backdrop = rememberLayerBackdrop() + StyledScaffold( title = stringResource(R.string.customize_transparency_mode), navigationButton = { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ){ spacerHeight, hazeState -> Column( modifier = Modifier .hazeSource(hazeState) + .layerBackdrop(backdrop) .fillMaxSize() .verticalScroll(verticalScrollState) .padding(horizontal = 16.dp), @@ -154,19 +160,15 @@ fun TransparencySettingsScreen(navController: NavController) { object : (ByteArray) -> Unit { override fun invoke(value: ByteArray) { val parsed = parseTransparencySettingsResponse(value) - if (parsed != null) { - enabled.value = parsed.enabled - amplificationSliderValue.floatValue = parsed.netAmplification - balanceSliderValue.floatValue = parsed.balance - toneSliderValue.floatValue = parsed.leftTone - ambientNoiseReductionSliderValue.floatValue = - parsed.leftAmbientNoiseReduction - conversationBoostEnabled.value = parsed.leftConversationBoost - eq.value = parsed.leftEQ.copyOf() - Log.d(TAG, "Updated transparency settings from notification") - } else { - Log.w(TAG, "Failed to parse transparency settings from notification") - } + enabled.value = parsed.enabled + amplificationSliderValue.floatValue = parsed.netAmplification + balanceSliderValue.floatValue = parsed.balance + toneSliderValue.floatValue = parsed.leftTone + ambientNoiseReductionSliderValue.floatValue = + parsed.leftAmbientNoiseReduction + conversationBoostEnabled.value = parsed.leftConversationBoost + eq.value = parsed.leftEQ.copyOf() + Log.d(TAG, "Updated transparency settings from notification") } } } @@ -251,12 +253,7 @@ fun TransparencySettingsScreen(navController: NavController) { try { val data = attManager.read(ATTHandles.TRANSPARENCY) parsedSettings = parseTransparencySettingsResponse(data = data) - if (parsedSettings != null) { - Log.d(TAG, "Parsed settings on attempt $attempt") - break - } else { - Log.d(TAG, "Parsing returned null on attempt $attempt") - } + Log.d(TAG, "Parsed settings on attempt $attempt") } catch (e: Exception) { Log.w(TAG, "Read attempt $attempt failed: ${e.message}") } @@ -297,7 +294,7 @@ fun TransparencySettingsScreen(navController: NavController) { ) Spacer(modifier = Modifier.height(4.dp)) StyledSlider( - label = stringResource(R.string.amplification).uppercase(), + label = stringResource(R.string.amplification), valueRange = -1f..1f, mutableFloatState = amplificationSliderValue, onValueChange = { @@ -309,20 +306,20 @@ fun TransparencySettingsScreen(navController: NavController) { ) StyledSlider( - label = stringResource(R.string.balance).uppercase(), + label = stringResource(R.string.balance), valueRange = -1f..1f, mutableFloatState = balanceSliderValue, onValueChange = { balanceSliderValue.floatValue = it }, - snapPoints = listOf(0f), + snapPoints = listOf(-1f, 0f, 1f), startLabel = stringResource(R.string.left), endLabel = stringResource(R.string.right), independent = true, ) StyledSlider( - label = stringResource(R.string.tone).uppercase(), + label = stringResource(R.string.tone), valueRange = -1f..1f, mutableFloatState = toneSliderValue, onValueChange = { @@ -334,7 +331,7 @@ fun TransparencySettingsScreen(navController: NavController) { ) StyledSlider( - label = stringResource(R.string.ambient_noise_reduction).uppercase(), + label = stringResource(R.string.ambient_noise_reduction), valueRange = 0f..1f, mutableFloatState = ambientNoiseReductionSliderValue, onValueChange = { @@ -356,20 +353,20 @@ fun TransparencySettingsScreen(navController: NavController) { // Only show transparency mode EQ section if SDP offset is available if (isSdpOffsetAvailable.value) { Text( - text = stringResource(R.string.equalizer).uppercase(), + text = stringResource(R.string.equalizer), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp) ) Column( modifier = Modifier .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(14.dp)) + .background(backgroundColor, RoundedCornerShape(28.dp)) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceBetween diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt index 1124ad53..467367bd 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/TroubleshootingScreen.kt @@ -85,6 +85,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.FileProvider import androidx.navigation.NavController +import com.kyant.backdrop.backdrops.layerBackdrop +import com.kyant.backdrop.backdrops.rememberLayerBackdrop import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.coroutines.Dispatchers @@ -208,6 +210,8 @@ fun TroubleshootingScreen(navController: NavController) { showBottomSheet = true } + val backdrop = rememberLayerBackdrop() + Box( modifier = Modifier.fillMaxSize() ) { @@ -217,28 +221,30 @@ fun TroubleshootingScreen(navController: NavController) { StyledIconButton( onClick = { navController.popBackStack() }, icon = "􀯶", - darkMode = isDarkTheme + darkMode = isDarkTheme, + backdrop = backdrop ) } ){ spacerHeight, hazeState -> Column( modifier = Modifier .fillMaxSize() - .verticalScroll(scrollState) + .layerBackdrop(backdrop) .hazeSource(state = hazeState) + .verticalScroll(scrollState) .padding(horizontal = 16.dp) ) { Spacer(modifier = Modifier.height(spacerHeight)) Text( - text = stringResource(R.string.saved_logs).uppercase(), + text = stringResource(R.string.saved_logs), style = TextStyle( fontSize = 14.sp, - fontWeight = FontWeight.Light, + fontWeight = FontWeight.Bold, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 8.dp) + modifier = Modifier.padding(16.dp, bottom = 4.dp, top = 8.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -249,7 +255,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally @@ -266,7 +272,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(horizontal = 16.dp, vertical = 8.dp) ) { @@ -372,14 +378,14 @@ fun TroubleshootingScreen(navController: NavController) { Spacer(modifier = Modifier.height(16.dp)) Text( - text = "TROUBLESHOOTING STEPS".uppercase(), + text = "TROUBLESHOOTING STEPS", style = TextStyle( fontSize = 14.sp, fontWeight = FontWeight.Light, color = textColor.copy(alpha = 0.6f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), - modifier = Modifier.padding(8.dp, bottom = 2.dp, top = 8.dp) + modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 8.dp) ) Spacer(modifier = Modifier.height(2.dp)) @@ -389,7 +395,7 @@ fun TroubleshootingScreen(navController: NavController) { .fillMaxWidth() .background( backgroundColor, - RoundedCornerShape(14.dp) + RoundedCornerShape(28.dp) ) .padding(16.dp) ) { diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 2c499877..31a83bea 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -15,12 +15,13 @@ Case Test Name - Noise Control + Listening Mode Off Transparency Adaptive Noise Cancellation Press and Hold AirPods + Press and hold the stem to cycle between the selected listening modes. Head Gestures Left Right @@ -110,7 +111,7 @@ Press Speed Adjust the speed required to press two or three times on your AirPods. Press and Hold Duration - Adjust the duration required to press and hold on your AirPods + Adjust the duration required to press and hold on your AirPods. Volume Swipe Speed To prevent unintended volume adjustments, select preferred wait time between swipes. Equalizer @@ -133,7 +134,7 @@ Ambient Noise Reduction Conversation Boost Conversation Boost focuses your AirPods Pro on the person talking in front of you, making it easier to hear in a face-to-face conversation. - AirPods can use the results of a hearing test to make adjustments that improve the clarity of voices and sounds around you. \n\n Hearing Aid is only intended for people with perceived mild to moderate hearing loss. + AirPods can use the results of a hearing test to make adjustments that improve the clarity of voices and sounds around you.\n\nHearing Aid is only intended for people with perceived mild to moderate hearing loss. Media Assist AirPods Pro can use the results of a hearing test to make adjustments that improve the clarity of music, video, and calls. Adjust Music and Video @@ -174,4 +175,5 @@ Must be exactly 32 hex characters Error converting hex: Found offset please restart the Bluetooth process + Digital Assistant From 3f582b8fcf2e571a13c64767cc52e3e5edd03975 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 12:27:42 +0530 Subject: [PATCH 42/72] remove bleonly mode, use CAPod instead --- .../screens/AirPodsSettingsScreen.kt | 99 ++++++++----------- .../librepods/screens/AppSettingsScreen.kt | 9 -- android/app/src/main/res/values/strings.xml | 3 - 3 files changed, 40 insertions(+), 71 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 62c700bd..1f948cc2 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -102,7 +102,6 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, var isLocallyConnected by remember { mutableStateOf(isConnected) } var isRemotelyConnected by remember { mutableStateOf(isRemotelyConnected) } val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE) - val bleOnlyMode = sharedPreferences.getBoolean("ble_only_mode", false) var device by remember { mutableStateOf(dev) } var deviceName by remember { mutableStateOf( @@ -236,78 +235,60 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, BatteryView(service = service) Spacer(modifier = Modifier.height(32.dp)) - // Show BLE-only mode indicator - if (bleOnlyMode) { - Text( - text = "BLE-only mode - advanced features disabled", - style = TextStyle( - fontSize = 14.sp, - fontWeight = FontWeight.Medium, - color = (if (isSystemInDarkTheme()) Color.White else Color.Black).copy(alpha = 0.6f), - fontFamily = FontFamily(Font(R.font.sf_pro)) - ), - modifier = Modifier.padding(8.dp, bottom = 16.dp) - ) - } - - // Only show name field when not in BLE-only mode - if (!bleOnlyMode) { - NavigationButton( - to = "rename", - name = stringResource(R.string.name), - currentState = deviceName.text, - navController = navController, - independent = true - ) - - Spacer(modifier = Modifier.height(32.dp)) - NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) + NavigationButton( + to = "rename", + name = stringResource(R.string.name), + currentState = deviceName.text, + navController = navController, + independent = true + ) - Spacer(modifier = Modifier.height(16.dp)) - NoiseControlSettings(service = service) + Spacer(modifier = Modifier.height(32.dp)) + NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) - Spacer(modifier = Modifier.height(16.dp)) - PressAndHoldSettings(navController = navController) + Spacer(modifier = Modifier.height(16.dp)) + NoiseControlSettings(service = service) - Spacer(modifier = Modifier.height(16.dp)) - CallControlSettings(hazeState = hazeState) + Spacer(modifier = Modifier.height(16.dp)) + PressAndHoldSettings(navController = navController) - // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events + Spacer(modifier = Modifier.height(16.dp)) + CallControlSettings(hazeState = hazeState) - Spacer(modifier = Modifier.height(16.dp)) - AudioSettings(navController = navController) + // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events - Spacer(modifier = Modifier.height(16.dp)) - ConnectionSettings() + Spacer(modifier = Modifier.height(16.dp)) + AudioSettings(navController = navController) - Spacer(modifier = Modifier.height(16.dp)) - MicrophoneSettings(hazeState) + Spacer(modifier = Modifier.height(16.dp)) + ConnectionSettings() - Spacer(modifier = Modifier.height(16.dp)) - StyledToggle( - label = stringResource(R.string.sleep_detection), - controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG - ) + Spacer(modifier = Modifier.height(16.dp)) + MicrophoneSettings(hazeState) - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "head_tracking", stringResource(R.string.head_gestures), navController) + Spacer(modifier = Modifier.height(16.dp)) + StyledToggle( + label = stringResource(R.string.sleep_detection), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG + ) - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "accessibility", "Accessibility", navController = navController) + Spacer(modifier = Modifier.height(16.dp)) + NavigationButton(to = "head_tracking", stringResource(R.string.head_gestures), navController) - Spacer(modifier = Modifier.height(16.dp)) - StyledToggle( - label = stringResource(R.string.off_listening_mode), - controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, - description = stringResource(R.string.off_listening_mode_description) - ) + Spacer(modifier = Modifier.height(16.dp)) + NavigationButton(to = "accessibility", "Accessibility", navController = navController) - // an about card- everything but the version number is unknown - will add later if i find out + Spacer(modifier = Modifier.height(16.dp)) + StyledToggle( + label = stringResource(R.string.off_listening_mode), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, + description = stringResource(R.string.off_listening_mode_description) + ) - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton("debug", "Debug", navController) - } + // an about card- everything but the version number is unknown - will add later if i find out + Spacer(modifier = Modifier.height(16.dp)) + NavigationButton("debug", "Debug", navController) Spacer(Modifier.height(24.dp)) } } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index 89cf8665..34208f9e 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -229,15 +229,6 @@ fun AppSettingsScreen(navController: NavController) { sharedPreferences = sharedPreferences, ) - StyledToggle( - title = stringResource(R.string.connection_mode), - label = stringResource(R.string.ble_only_mode), - description = stringResource(R.string.ble_only_mode_description), - checkedState = bleOnlyMode, - sharedPreferenceKey = "ble_only_mode", - sharedPreferences = sharedPreferences, - ) - Text( text = stringResource(R.string.conversational_awareness), style = TextStyle( diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 31a83bea..3b4e5c14 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -142,9 +142,6 @@ Widget Show phone battery in widget Display your phone\'s battery level in the widget alongside AirPods battery - Connection Mode - BLE Only Mode - Only use Bluetooth Low Energy for battery data and ear detection. Disables advanced features requiring L2CAP connection. Conversational Awareness Volume Quick Settings Tile Open dialog for controlling From 1152f45a6c47d0c8ba58727921228f41332c962e Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 12:30:09 +0530 Subject: [PATCH 43/72] remove bleonly mode, use CAPod instead --- .../me/kavishdevar/librepods/MainActivity.kt | 2 -- .../librepods/screens/AppSettingsScreen.kt | 10 --------- .../librepods/services/AirPodsService.kt | 21 +++---------------- 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 9696eb9f..2c4a54ed 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -131,7 +131,6 @@ import kotlin.io.encoding.ExperimentalEncodingApi lateinit var serviceConnection: ServiceConnection lateinit var connectionStatusReceiver: BroadcastReceiver -@ExperimentalHazeMaterialsApi @ExperimentalMaterial3Api class MainActivity : ComponentActivity() { companion object { @@ -427,7 +426,6 @@ fun Main() { } } -@ExperimentalHazeMaterialsApi @OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class) @Composable fun PermissionsScreen( diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt index 34208f9e..01f3524a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AppSettingsScreen.kt @@ -173,16 +173,6 @@ fun AppSettingsScreen(navController: NavController) { mutableStateOf(sharedPreferences.getBoolean("use_alternate_head_tracking_packets", false)) } - val bleOnlyMode = remember { - mutableStateOf(sharedPreferences.getBoolean("ble_only_mode", false)) - } - - LaunchedEffect(Unit) { - if (!sharedPreferences.contains("ble_only_mode")) { - sharedPreferences.edit { putBoolean("ble_only_mode", false) } - } - } - fun validateHexInput(input: String): Boolean { val hexPattern = Regex("^[0-9a-fA-F]{32}$") return hexPattern.matches(input) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index b5fe15b3..cacf2850 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -427,7 +427,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (!contains("qs_click_behavior")) putString("qs_click_behavior", "cycle") if (!contains("name")) putString("name", "AirPods") - if (!contains("ble_only_mode")) putBoolean("ble_only_mode", false) if (!contains("left_single_press_action")) putString( "left_single_press_action", @@ -612,7 +611,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList } Log.d("AirPodsCrossDevice", CrossDevice.isAvailable.toString()) - if (!CrossDevice.isAvailable && !config.bleOnlyMode) { + if (!CrossDevice.isAvailable) { Log.d("AirPodsService", "${config.deviceName} connected") CoroutineScope(Dispatchers.IO).launch { connectToSocket(device!!) @@ -624,12 +623,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList sharedPreferences.edit { putString("mac_address", macAddress) } - } else if (config.bleOnlyMode) { - Log.d("AirPodsService", "BLE-only mode: skipping L2CAP connection") - macAddress = device!!.address - sharedPreferences.edit { - putString("mac_address", macAddress) - } } } else if (intent?.action == AirPodsNotifications.AIRPODS_DISCONNECTED) { device = null @@ -695,7 +688,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList if (profile == BluetoothProfile.A2DP) { val connectedDevices = proxy.connectedDevices if (connectedDevices.isNotEmpty()) { - if (!CrossDevice.isAvailable && !config.bleOnlyMode) { + if (!CrossDevice.isAvailable) { CoroutineScope(Dispatchers.IO).launch { connectToSocket(device) } @@ -704,12 +697,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList sharedPreferences.edit { putString("mac_address", macAddress) } - } else if (config.bleOnlyMode) { - Log.d("AirPodsService", "BLE-only mode: skipping L2CAP connection") - macAddress = device.address - sharedPreferences.edit { - putString("mac_address", macAddress) - } } this@AirPodsService.sendBroadcast( Intent(AirPodsNotifications.AIRPODS_CONNECTED) @@ -1143,7 +1130,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList conversationalAwarenessVolume = sharedPreferences.getInt("conversational_awareness_volume", 43), textColor = sharedPreferences.getLong("textColor", -1L), qsClickBehavior = sharedPreferences.getString("qs_click_behavior", "cycle") ?: "cycle", - bleOnlyMode = sharedPreferences.getBoolean("ble_only_mode", false), // AirPods state-based takeover takeoverWhenDisconnected = sharedPreferences.getBoolean("takeover_when_disconnected", true), @@ -1188,7 +1174,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList "conversational_awareness_volume" -> config.conversationalAwarenessVolume = preferences.getInt(key, 43) "textColor" -> config.textColor = preferences.getLong(key, -1L) "qs_click_behavior" -> config.qsClickBehavior = preferences.getString(key, "cycle") ?: "cycle" - "ble_only_mode" -> config.bleOnlyMode = preferences.getBoolean(key, false) // AirPods state-based takeover "takeover_when_disconnected" -> config.takeoverWhenDisconnected = preferences.getBoolean(key, true) @@ -1686,7 +1671,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) - if (!::socket.isInitialized && !config.bleOnlyMode) { + if (!::socket.isInitialized) { return } if (connected && (config.bleOnlyMode || socket.isConnected)) { From 5bc1dd2e1d0d4b24116e1cb411da2b6dcba76819 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 13:44:00 +0530 Subject: [PATCH 44/72] android: fix switch styling --- .../librepods/composables/StyledSwitch.kt | 29 ++++++++++--------- .../librepods/composables/StyledToggle.kt | 18 ++++-------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt index 94ea6903..1a1e21e7 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledSwitch.kt @@ -79,7 +79,6 @@ fun StyledSwitch( checked: Boolean, onCheckedChange: (Boolean) -> Unit, enabled: Boolean = true, - indpendent: Boolean = true, ) { val isDarkTheme = isSystemInDarkTheme() @@ -135,7 +134,6 @@ fun StyledSwitch( .height(trackHeight) .onSizeChanged { trackWidthPx.floatValue = it.width.toFloat() } ) - Box( modifier = Modifier .padding(horizontal = 2.dp) @@ -178,7 +176,9 @@ fun StyledSwitch( { RoundedCornerShape(thumbHeight / 2) }, highlight = { val progress = progressAnimation.value - Highlight.AmbientDefault.copy(alpha = progress) + Highlight.AmbientDefault.copy( + alpha = progress + ) }, shadow = { Shadow( @@ -188,21 +188,23 @@ fun StyledSwitch( }, layerBlock = { val progress = progressAnimation.value - val scale = lerp(1f, 1.6f, progress) + val scale = lerp(1f, 1.5f, progress) scaleX = scale scaleY = scale }, onDrawBackdrop = { drawScope -> drawIntoCanvas { canvas -> canvas.save() - canvas.drawRect(0f, 0f, size.width, size.height, Paint().apply { - color = if (indpendent) { - if (isDarkTheme) Color(0xFF000000) else Color(0xFFF2F2F7) - } else { - if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF) + canvas.drawRect( + left = 0f, + top = 0f, + right = size.width, + bottom = size.height, + paint = Paint().apply { + color = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7) } - }) - scale(0.75f) { + ) + scale(0.7f) { drawScope() } } @@ -246,7 +248,7 @@ fun StyledSwitch( } } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Composable fun StyledSwitchPreview() { val isDarkTheme = isSystemInDarkTheme() @@ -255,7 +257,7 @@ fun StyledSwitchPreview() { modifier = Modifier .background(backgroundColor) .width(100.dp) - .height(400.dp), + .height(150.dp), contentAlignment = Alignment.Center ) { val checked = remember { mutableStateOf(true) } @@ -265,7 +267,6 @@ fun StyledSwitchPreview() { checked.value = it }, enabled = true, - indpendent = false ) // LaunchedEffect(Unit) { // delay(1000) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt index 920fe7f1..2dd21d98 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/StyledToggle.kt @@ -157,8 +157,7 @@ fun StyledToggle( checked = it cb() } - }, - indpendent = true + } ) } } @@ -245,8 +244,7 @@ fun StyledToggle( checked = it cb() } - }, - indpendent = false + } ) } } @@ -367,8 +365,7 @@ fun StyledToggle( checked = it cb() } - }, - indpendent = true + } ) } } @@ -455,8 +452,7 @@ fun StyledToggle( checked = it cb() } - }, - indpendent = false + } ) } } @@ -605,8 +601,7 @@ fun StyledToggle( checked = it cb() } - }, - indpendent = true + } ) } } @@ -687,8 +682,7 @@ fun StyledToggle( checked = it cb() } - }, - indpendent = false + } ) } } From 55cb69f880d8c23f91a9dc43e57928ad18682aeb Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 15:41:26 +0530 Subject: [PATCH 45/72] android: remove fade from transition --- .../main/java/me/kavishdevar/librepods/MainActivity.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt index 2c4a54ed..6ac3d349 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/MainActivity.kt @@ -43,8 +43,6 @@ import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.Canvas @@ -325,25 +323,25 @@ fun Main() { slideInHorizontally( initialOffsetX = { it }, animationSpec = tween(durationMillis = 300) - ) + fadeIn(animationSpec = tween(durationMillis = 300)) + ) // + fadeIn(animationSpec = tween(durationMillis = 300)) }, exitTransition = { slideOutHorizontally( targetOffsetX = { -it/4 }, animationSpec = tween(durationMillis = 300) - ) + fadeOut(animationSpec = tween(durationMillis = 150)) + ) // + fadeOut(animationSpec = tween(durationMillis = 150)) }, popEnterTransition = { slideInHorizontally( initialOffsetX = { -it/4 }, animationSpec = tween(durationMillis = 300) - ) + fadeIn(animationSpec = tween(durationMillis = 300)) + ) // + fadeIn(animationSpec = tween(durationMillis = 300)) }, popExitTransition = { slideOutHorizontally( targetOffsetX = { it }, animationSpec = tween(durationMillis = 300) - ) + fadeOut(animationSpec = tween(durationMillis = 150)) + ) // + fadeOut(animationSpec = tween(durationMillis = 150)) } ) { composable("settings") { From 1076218cccd798542ade76e9a932cd41241729fd Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 15:48:36 +0530 Subject: [PATCH 46/72] android: add A16's new bluetooth identifier for log collection just why... --- .../kavishdevar/librepods/utils/LogCollector.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt index 02c9235d..d03ca48c 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/LogCollector.kt @@ -53,12 +53,20 @@ class LogCollector(private val context: Context) { } } + private suspend fun getBluetoothUID(): String? { + val pkgs = listOf("com.android.bluetooth", "com.google.android.bluetooth") + for (pkg in pkgs) { + val uid = executeRootCommand( + "dumpsys package $pkg | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'" + ).trim() + if (uid.isNotEmpty()) return uid + } + return null + } + private suspend fun getPackageUIDs(): Pair { return withContext(Dispatchers.IO) { - val btUid = executeRootCommand("dumpsys package com.android.bluetooth | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'") - .trim() - .takeIf { it.isNotEmpty() } - + val btUid = getBluetoothUID() val appUid = executeRootCommand("dumpsys package me.kavishdevar.librepods | grep -m 1 \"uid=\" | sed -E 's/.*uid=([0-9]+).*/\\1/'") .trim() .takeIf { it.isNotEmpty() } From e9da7a2a50355761d17e91b5d79062f2480c87d4 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 16:01:56 +0530 Subject: [PATCH 47/72] android: fix crash in head gestures screen --- .../kavishdevar/librepods/screens/HeadTrackingScreen.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt index a42241a2..8bd5737a 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HeadTrackingScreen.kt @@ -151,7 +151,6 @@ fun HeadTrackingScreen(navController: NavController) { } ), ) { spacerHeight, hazeState -> - val backdrop = rememberLayerBackdrop() val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) var gestureText by remember { mutableStateOf("") } @@ -161,20 +160,17 @@ fun HeadTrackingScreen(navController: NavController) { var shouldExplode by remember { mutableStateOf(false) } Column( modifier = Modifier - .layerBackdrop(backdrop) .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { Column ( modifier = Modifier .fillMaxWidth() + .hazeSource(state = hazeState) + .layerBackdrop(backdrop) .padding(top = 8.dp) .padding(horizontal = 16.dp) .verticalScroll(scrollState) - .hazeSource(state = hazeState) - .layerBackdrop( - backdrop = backdrop - ) ) { Spacer(modifier = Modifier.height(spacerHeight)) StyledToggle( From 147e51165912d8262fb7615c103589f3e19bc312 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 16:03:55 +0530 Subject: [PATCH 48/72] android: show head gestures status in the navigation button --- .../me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt | 4 ++-- android/app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 1f948cc2..c70860cb 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -273,10 +273,10 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, ) Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "head_tracking", stringResource(R.string.head_gestures), navController) + NavigationButton(to = "head_tracking", name = stringResource(R.string.head_gestures), navController = navController, currentState = if (sharedPreferences.getBoolean("head_gestures", false)) stringResource(R.string.on) else stringResource(R.string.off)) Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "accessibility", "Accessibility", navController = navController) + NavigationButton(to = "accessibility", name = stringResource(R.string.accessibility), navController = navController) Spacer(modifier = Modifier.height(16.dp)) StyledToggle( diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 3b4e5c14..4a448a30 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -173,4 +173,5 @@ Error converting hex: Found offset please restart the Bluetooth process Digital Assistant + On From e158ba1b27b2ec6ca57d56de861e030d4a94d791 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 17:01:42 +0530 Subject: [PATCH 49/72] android: don't crash if att not available --- .../java/me/kavishdevar/librepods/screens/HearingAidScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 28e4c989..9bdb6957 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -85,7 +85,7 @@ fun HearingAidScreen(navController: NavController) { val verticalScrollState = rememberScrollState() val hazeState = remember { HazeState() } val snackbarHostState = remember { SnackbarHostState() } - val attManager = ServiceManager.getService()?.attManager ?: throw IllegalStateException("ATTManager not available") + val attManager = ServiceManager.getService()?.attManager ?: return val aacpManager = remember { ServiceManager.getService()?.aacpManager } From 5ec300aad8258adc54b96cc82ab0ea2b59862981 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 17:10:49 +0530 Subject: [PATCH 50/72] android: use lazycolumn in airpods settings for better performance and navigation transitions --- .../screens/AirPodsSettingsScreen.kt | 118 ++++++++++-------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index c70860cb..22c7ff60 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -38,9 +38,8 @@ 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.rememberScrollState +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -212,84 +211,93 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, snackbarHostState = snackbarHostState ) { spacerHeight, hazeState -> if (isLocallyConnected || isRemotelyConnected) { - Column( + LazyColumn( modifier = Modifier .fillMaxSize() .hazeSource(hazeState) .padding(horizontal = 16.dp) .layerBackdrop(backdrop) - .verticalScroll(rememberScrollState()) ) { - Spacer(modifier = Modifier.height(spacerHeight)) - LaunchedEffect(service) { - service.let { - it.sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { - putParcelableArrayListExtra("data", ArrayList(it.getBattery())) - }) - it.sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply { - putExtra("data", it.getANC()) - }) + item { Spacer(modifier = Modifier.height(spacerHeight)) } + item { + LaunchedEffect(service) { + service.let { + it.sendBroadcast(Intent(AirPodsNotifications.BATTERY_DATA).apply { + putParcelableArrayListExtra("data", ArrayList(it.getBattery())) + }) + it.sendBroadcast(Intent(AirPodsNotifications.ANC_DATA).apply { + putExtra("data", it.getANC()) + }) + } } - } - BatteryView(service = service) - Spacer(modifier = Modifier.height(32.dp)) + BatteryView(service = service) + } + item { Spacer(modifier = Modifier.height(32.dp)) } - NavigationButton( - to = "rename", - name = stringResource(R.string.name), - currentState = deviceName.text, - navController = navController, - independent = true - ) + item { + NavigationButton( + to = "rename", + name = stringResource(R.string.name), + currentState = deviceName.text, + navController = navController, + independent = true + ) + } - Spacer(modifier = Modifier.height(32.dp)) - NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) + item { Spacer(modifier = Modifier.height(32.dp)) } + item { NavigationButton(to = "hearing_aid", stringResource(R.string.hearing_aid), navController) } - Spacer(modifier = Modifier.height(16.dp)) - NoiseControlSettings(service = service) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { NoiseControlSettings(service = service) } - Spacer(modifier = Modifier.height(16.dp)) - PressAndHoldSettings(navController = navController) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { PressAndHoldSettings(navController = navController) } - Spacer(modifier = Modifier.height(16.dp)) - CallControlSettings(hazeState = hazeState) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { CallControlSettings(hazeState = hazeState) } // camera control goes here, airpods side is done, i just need to figure out how to listen to app open/close events - Spacer(modifier = Modifier.height(16.dp)) - AudioSettings(navController = navController) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { AudioSettings(navController = navController) } - Spacer(modifier = Modifier.height(16.dp)) - ConnectionSettings() + item { Spacer(modifier = Modifier.height(16.dp)) } + item { ConnectionSettings() } - Spacer(modifier = Modifier.height(16.dp)) - MicrophoneSettings(hazeState) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { MicrophoneSettings(hazeState) } - Spacer(modifier = Modifier.height(16.dp)) - StyledToggle( - label = stringResource(R.string.sleep_detection), - controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG - ) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { + StyledToggle( + label = stringResource(R.string.sleep_detection), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.SLEEP_DETECTION_CONFIG + ) + } - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "head_tracking", name = stringResource(R.string.head_gestures), navController = navController, currentState = if (sharedPreferences.getBoolean("head_gestures", false)) stringResource(R.string.on) else stringResource(R.string.off)) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { + NavigationButton(to = "head_tracking", name = stringResource(R.string.head_gestures), navController = navController, currentState = if (sharedPreferences.getBoolean("head_gestures", false)) stringResource(R.string.on) else stringResource(R.string.off)) + } - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton(to = "accessibility", name = stringResource(R.string.accessibility), navController = navController) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { NavigationButton(to = "accessibility", name = stringResource(R.string.accessibility), navController = navController) } - Spacer(modifier = Modifier.height(16.dp)) - StyledToggle( - label = stringResource(R.string.off_listening_mode), - controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, - description = stringResource(R.string.off_listening_mode_description) - ) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { + StyledToggle( + label = stringResource(R.string.off_listening_mode), + controlCommandIdentifier = AACPManager.Companion.ControlCommandIdentifiers.ALLOW_OFF_OPTION, + description = stringResource(R.string.off_listening_mode_description) + ) + } // an about card- everything but the version number is unknown - will add later if i find out - Spacer(modifier = Modifier.height(16.dp)) - NavigationButton("debug", "Debug", navController) - Spacer(Modifier.height(24.dp)) + item { Spacer(modifier = Modifier.height(16.dp)) } + item { NavigationButton("debug", "Debug", navController) } + item { Spacer(Modifier.height(24.dp)) } } } else { From 48b715af68d202527187911a4fd57f482134c2ed Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Sun, 28 Sep 2025 18:12:01 +0530 Subject: [PATCH 51/72] android: fix text color in troubshooting button and pressandhold settings --- .../me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt | 3 ++- .../librepods/screens/PressAndHoldSettingsScreen.kt | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt index 22c7ff60..60be94ca 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/AirPodsSettingsScreen.kt @@ -350,7 +350,8 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService, style = TextStyle( fontSize = 16.sp, fontWeight = FontWeight.Medium, - fontFamily = FontFamily(Font(R.font.sf_pro)) + fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isSystemInDarkTheme()) Color.White else Color.Black ) ) } diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt index d4613225..18c01d6f 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/PressAndHoldSettingsScreen.kt @@ -442,10 +442,12 @@ fun LongPressActionElement( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { + val isDarkTheme = isSystemInDarkTheme() Text( name, fontSize = 16.sp, fontFamily = FontFamily(Font(R.font.sf_pro)), + color = if (isDarkTheme) Color.White else Color.Black, modifier = Modifier .weight(1f) .padding(start = 4.dp) From 504e70371b5722838be560912ed24711f34fc266 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 29 Sep 2025 00:17:02 +0530 Subject: [PATCH 52/72] android: bring back original confirmation dialog too lazy to fix/implement properly the glassy one --- .../composables/ConfirmationDialog.kt | 333 ++++++------------ .../librepods/screens/HearingAidScreen.kt | 3 +- 2 files changed, 118 insertions(+), 218 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt index f08f448a..991dbbe0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/composables/ConfirmationDialog.kt @@ -18,289 +18,188 @@ package me.kavishdevar.librepods.composables -import android.graphics.RuntimeShader -import android.os.Build -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.VectorConverter -import androidx.compose.animation.core.VisibilityThreshold -import androidx.compose.animation.core.spring -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.background 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.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredWidthIn +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ShaderBrush -import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput 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.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.util.fastCoerceAtMost -import androidx.compose.ui.util.fastCoerceIn -import androidx.compose.ui.util.lerp -import com.kyant.backdrop.Backdrop -import com.kyant.backdrop.drawBackdrop -import com.kyant.backdrop.effects.blur -import com.kyant.backdrop.effects.colorControls -import com.kyant.backdrop.effects.refraction -import com.kyant.backdrop.highlight.Highlight -import kotlinx.coroutines.launch +import androidx.compose.ui.window.Dialog +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.materials.CupertinoMaterials +import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import me.kavishdevar.librepods.R -import me.kavishdevar.librepods.utils.inspectDragGestures -import kotlin.math.abs -import kotlin.math.atan2 -import kotlin.math.cos -import kotlin.math.sin -import kotlin.math.tanh +@ExperimentalHazeMaterialsApi @Composable fun ConfirmationDialog( showDialog: MutableState, title: String, message: String, - confirmText: String = "Ok", + confirmText: String = "Enable", dismissText: String = "Cancel", onConfirm: () -> Unit, onDismiss: () -> Unit = { showDialog.value = false }, - backdrop: Backdrop, + hazeState: HazeState, ) { - AnimatedVisibility( - visible = showDialog.value, - enter = fadeIn() + scaleIn(initialScale = 1.25f), - exit = fadeOut() + scaleOut(targetScale = 0.9f) - ) { - val animationScope = rememberCoroutineScope() - val progressAnimation = remember { Animatable(0f) } - var pressStartPosition by remember { mutableStateOf(Offset.Zero) } - val offsetAnimation = remember { Animatable(Offset.Zero, Offset.VectorConverter) } - - val interactiveHighlightShader = remember { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - RuntimeShader( - """ -uniform float2 size; -layout(color) uniform half4 color; -uniform float radius; -uniform float2 offset; - -half4 main(float2 coord) { - float2 center = offset; - float dist = distance(coord, center); - float intensity = smoothstep(radius, radius * 0.5, dist); - return color * intensity; -}""" - ) - } else { - null - } - } - - val isLightTheme = !isSystemInDarkTheme() - val contentColor = if (isLightTheme) Color.Black else Color.White - val accentColor = if (isLightTheme) Color(0xFF0088FF) else Color(0xFF0091FF) - val containerColor = if (isLightTheme) Color(0xFFFFFFFF).copy(0.6f) else Color(0xFF101010).copy(0.6f) - - Box( - Modifier - .fillMaxSize() - .clickable(onClick = onDismiss, indication = null, interactionSource = remember { MutableInteractionSource() } ) - ) { + val isDarkTheme = isSystemInDarkTheme() + val textColor = if (isDarkTheme) Color.White else Color.Black + val accentColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) + if (showDialog.value) { + Dialog(onDismissRequest = { showDialog.value = false }) { Box( - Modifier - .align(Alignment.Center) - .clickable(onClick = {}, indication = null, interactionSource = remember { MutableInteractionSource() } ) - .drawBackdrop( - backdrop, - { RoundedCornerShape(48f.dp) }, - highlight = { - Highlight.SolidDefault - }, - onDrawSurface = { drawRect(containerColor) }, - effects = { - colorControls( - brightness = if (isLightTheme) 0.4f else 0.2f, - saturation = 1.5f - ) - blur(if (isLightTheme) 16f.dp.toPx() else 8f.dp.toPx()) - refraction(24f.dp.toPx(), 48f.dp.toPx(), true) - }, - layerBlock = { - val width = size.width - val height = size.height - - val progress = progressAnimation.value - val maxScale = 0f - val scale = lerp(1f, 1f + maxScale, progress) - - val maxOffset = size.minDimension - val initialDerivative = 0.05f - val offset = offsetAnimation.value - translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) - translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) - - val maxDragScale = 0.1f - val offsetAngle = atan2(offset.y, offset.x) - scaleX = - scale + - maxDragScale * abs(cos(offsetAngle) * offset.x / size.maxDimension) * - (width / height).fastCoerceAtMost(1f) - scaleY = - scale + - maxDragScale * abs(sin(offsetAngle) * offset.y / size.maxDimension) * - (height / width).fastCoerceAtMost(1f) - }, - onDrawFront = { - val progress = progressAnimation.value.fastCoerceIn(0f, 1f) - if (progress > 0f) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader != null) { - drawRect( - Color.White.copy(0.05f * progress), - blendMode = BlendMode.Plus - ) - interactiveHighlightShader.apply { - val offset = pressStartPosition + offsetAnimation.value - setFloatUniform("size", size.width, size.height) - setColorUniform("color", Color.White.copy(0.075f * progress).toArgb()) - setFloatUniform("radius", size.maxDimension / 2) - setFloatUniform( - "offset", - offset.x.fastCoerceIn(0f, size.width), - offset.y.fastCoerceIn(0f, size.height) - ) - } - drawRect( - ShaderBrush(interactiveHighlightShader), - blendMode = BlendMode.Plus - ) - } else { - drawRect( - Color.White.copy(0.125f * progress), - blendMode = BlendMode.Plus - ) - } - } - }, - contentEffects = { - refraction(8f.dp.toPx(), 24f.dp.toPx(), false) - } - ) - .fillMaxWidth(0.75f) + modifier = Modifier + // .fillMaxWidth(0.75f) .requiredWidthIn(min = 200.dp, max = 360.dp) - .pointerInput(animationScope) { - val progressAnimationSpec = spring(0.5f, 300f, 0.001f) - val offsetAnimationSpec = spring(1f, 300f, Offset.VisibilityThreshold) - val onDragStop: () -> Unit = { - animationScope.launch { - launch { progressAnimation.animateTo(0f, progressAnimationSpec) } - launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) } - } - } - inspectDragGestures( - onDragStart = { down -> - pressStartPosition = down.position - animationScope.launch { - launch { progressAnimation.animateTo(1f, progressAnimationSpec) } - launch { offsetAnimation.snapTo(Offset.Zero) } - } - }, - onDragEnd = { onDragStop() }, - onDragCancel = onDragStop - ) { _, dragAmount -> - animationScope.launch { - offsetAnimation.snapTo(offsetAnimation.value + dragAmount) - } - } - } + .background(Color.Transparent, RoundedCornerShape(14.dp)) + .clip(RoundedCornerShape(14.dp)) + .hazeEffect( + hazeState, + style = CupertinoMaterials.regular( + containerColor = if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f) + ) + ) ) { - Column(horizontalAlignment = Alignment.Start) { - Spacer(modifier = Modifier.height(28.dp)) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(24.dp)) Text( title, style = TextStyle( - fontSize = 18.sp, + fontSize = 16.sp, fontWeight = FontWeight.Bold, - color = contentColor, + color = textColor, fontFamily = FontFamily(Font(R.font.sf_pro)) ), + textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) - Spacer(modifier = Modifier.height(16.dp)) + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(12.dp)) Text( message, style = TextStyle( fontSize = 14.sp, - color = contentColor.copy(alpha = 0.8f), + color = textColor.copy(alpha = 0.8f), fontFamily = FontFamily(Font(R.font.sf_pro)) ), + textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) - Spacer(modifier = Modifier.height(16.dp)) - + androidx.compose.foundation.layout.Spacer(modifier = Modifier.height(16.dp)) + HorizontalDivider( + thickness = 1.dp, + color = Color(0x40888888), + modifier = Modifier.fillMaxWidth() + ) + var leftPressed by remember { mutableStateOf(false) } + var rightPressed by remember { mutableStateOf(false) } + val pressedColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9) Row( - Modifier - .padding(horizontal = 12.dp) - .padding(top = 12.dp, bottom = 24.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val event = awaitPointerEvent() + val position = event.changes.first().position + val width = size.width.toFloat() + val height = size.height.toFloat() + val isWithinBounds = position.y >= 0 && position.y <= height + val isLeft = position.x < width / 2 + event.changes.first().consume() + when (event.type) { + PointerEventType.Press -> { + if (isWithinBounds) { + leftPressed = isLeft + rightPressed = !isLeft + } else { + leftPressed = false + rightPressed = false + } + } + PointerEventType.Move -> { + if (isWithinBounds) { + leftPressed = isLeft + rightPressed = !isLeft + } else { + leftPressed = false + rightPressed = false + } + } + PointerEventType.Release -> { + if (isWithinBounds) { + if (leftPressed) { + onDismiss() + } else if (rightPressed) { + onConfirm() + } + } + leftPressed = false + rightPressed = false + } + } + } + } + }, + horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { - StyledButton( - onClick = onDismiss, - backdrop = backdrop, - surfaceColor = if (isLightTheme) Color(0xFFAAAAAA).copy(0.8f) else Color(0xFF202020).copy(0.8f), - modifier = Modifier.weight(1f), - isInteractive = false + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .background(if (leftPressed) pressedColor else Color.Transparent), + contentAlignment = Alignment.Center ) { - Text( - dismissText, - style = TextStyle(contentColor, 16.sp) - ) + Text(dismissText, color = accentColor) } - StyledButton( - onClick = onConfirm, - backdrop = backdrop, - surfaceColor = accentColor, - modifier = Modifier.weight(1f), - isInteractive = false + Box( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(Color(0x40888888)) + ) + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .background(if (rightPressed) pressedColor else Color.Transparent), + contentAlignment = Alignment.Center ) { - Text( - confirmText, - style = TextStyle(Color.White, 16.sp) - ) + Text(confirmText, color = accentColor) } } } } } } -} +} \ No newline at end of file diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 9bdb6957..b036db96 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -287,6 +287,7 @@ fun HearingAidScreen(navController: NavController) { } } }, - backdrop = backdrop + hazeState = hazeState, + // backdrop = backdrop ) } From bdb93efec6b47316f62ebf4d9baa8db7bcd41bd1 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 29 Sep 2025 00:22:01 +0530 Subject: [PATCH 53/72] android: prevent hearing aid turning off itself --- .../java/me/kavishdevar/librepods/screens/HearingAidScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index b036db96..3faac986 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -155,7 +155,7 @@ fun HearingAidScreen(navController: NavController) { LaunchedEffect(hearingAidEnabled.value) { if (hearingAidEnabled.value && !initialLoad.value) { showDialog.value = true - } else if (!hearingAidEnabled.value) { + } else if (!hearingAidEnabled.value && !initialLoad.value) { aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_AID.value, byteArrayOf(0x01, 0x02)) aacpManager?.sendControlCommand(AACPManager.Companion.ControlCommandIdentifiers.HEARING_ASSIST_CONFIG.value, 0x02.toByte()) hearingAidEnabled.value = false From 3a388da48e59f125115a8ccac9be9c481424f8b0 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 29 Sep 2025 00:22:46 +0530 Subject: [PATCH 54/72] android: hide media assist, not implemented --- .../librepods/screens/HearingAidScreen.kt | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt index 3faac986..c1534ab0 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/screens/HearingAidScreen.kt @@ -222,41 +222,43 @@ fun HearingAidScreen(navController: NavController) { ) Spacer(modifier = Modifier.height(16.dp)) - StyledToggle( - title = stringResource(R.string.media_assist), - label = stringResource(R.string.media_assist), - checkedState = mediaAssistEnabled, - independent = true, - description = stringResource(R.string.media_assist_description) - ) + // not implemented yet - Spacer(modifier = Modifier.height(8.dp)) + // StyledToggle( + // title = stringResource(R.string.media_assist), + // label = stringResource(R.string.media_assist), + // checkedState = mediaAssistEnabled, + // independent = true, + // description = stringResource(R.string.media_assist_description) + // ) - Column ( - modifier = Modifier - .fillMaxWidth() - .background(backgroundColor, RoundedCornerShape(28.dp)) - ) { - StyledToggle( - label = stringResource(R.string.adjust_media), - checkedState = adjustMediaEnabled, - onCheckedChange = { onAdjustMediaChange(it) }, - independent = false - ) - HorizontalDivider( - thickness = 1.dp, - color = Color(0x40888888), - modifier = Modifier - .padding(horizontal = 12.dp) - ) + // Spacer(modifier = Modifier.height(8.dp)) - StyledToggle( - label = stringResource(R.string.adjust_calls), - checkedState = adjustPhoneEnabled, - onCheckedChange = { onAdjustPhoneChange(it) }, - independent = false - ) - } + // Column ( + // modifier = Modifier + // .fillMaxWidth() + // .background(backgroundColor, RoundedCornerShape(28.dp)) + // ) { + // StyledToggle( + // label = stringResource(R.string.adjust_media), + // checkedState = adjustMediaEnabled, + // onCheckedChange = { onAdjustMediaChange(it) }, + // independent = false + // ) + // HorizontalDivider( + // thickness = 1.dp, + // color = Color(0x40888888), + // modifier = Modifier + // .padding(horizontal = 12.dp) + // ) + + // StyledToggle( + // label = stringResource(R.string.adjust_calls), + // checkedState = adjustPhoneEnabled, + // onCheckedChange = { onAdjustPhoneChange(it) }, + // independent = false + // ) + // } } } From c2ebbef14bd61f24f1054cdcb085b6358bdfddab Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 29 Sep 2025 01:00:41 +0530 Subject: [PATCH 55/72] docs: update README with new features --- README.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0079f6e0..d4bbf55c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ Most features should work with any AirPods. Currently, testing is only performed - **Battery Status**: Accurate battery levels - **Head Gestures**: Answer calls just by nodding your head - **Conversational Awareness**: Volume automatically lowers when you speak +- **Hearing Aid\*** +- **Customize Transparency Mode\*** +- **Multi-device connectivity\*** (upto 2 devices) - **Other customizations**: - Rename your AirPods - Customize long-press actions @@ -63,8 +66,8 @@ For installation and detailed info, see the [Linux README](/linux/README.md). | ![Settings 1](/android/imgs/settings-1.png) | ![Settings 2](/android/imgs/settings-2.png) | ![Debug Screen](/android/imgs/debug.png) | | ![Battery Notification and QS Tile for NC Mode](/android/imgs/notification-and-qs.png) | ![Popup](/android/imgs/popup.png) | ![Head Tracking and Gestures](/android/imgs/head-tracking-and-gestures.png) | | ![Long Press Configuration](/android/imgs/long-press.png) | ![Widget](/android/imgs/widget.png) | ![Customizations 1](/android/imgs/customizations-1.png) | -| ![Customizations 2](/android/imgs/customizations-2.png) | ![audio-popup](/android/imgs/audio-connected-island.png) | | - +| ![Customizations 2](/android/imgs/customizations-2.png) | ![accessibility](/android/imgs/accessibility.png) |![transparency](/android/imgs/transparency.png) | +|![hearing-aid](/android/imgs/hearing-aid.png) |![hearing-aid-adjustments](/android/imgs/hearing-aid-adjustments) | | #### Root Requirement > [!CAUTION] @@ -79,17 +82,19 @@ This method is less intrusive and should be tried first: 1. Install LSPosed, or another Xposed provider on your rooted device 2. Download the LibrePods app from the releases section, and install it. -3. Enable the Xposed module for the bluetooth app in your Xposed manager -4. Follow the instructions in the app to set up the module. -5. Open the app and connect your AirPods +3. Enable the Xposed module for the bluetooth app in your Xposed manager. +4. Disable unmount modules for the Bluetooth app if enabled. +5. Follow the instructions in the app to set up the module. +6. Open the app and connect your AirPods ##### Method 2: Root Module (Backup Option) If the Xposed method doesn't work for you: 1. Download the `btl2capfix.zip` module from the releases section 2. Install it using your preferred root manager (KernelSU, Apatch, or Magisk). -3. Reboot your device -4. Connect your AirPods +3. Disable Unmount modules for the Bluetooth aop if enabled. +4. Reboot your device +5. Connect your AirPods ##### Method 3: Patching it yourself If you prefer to patch the Bluetooth stack yourself, follow these steps: @@ -111,24 +116,19 @@ If you're unfamiliar with these steps, search for tutorials online or ask in And - When renaming your AirPods through the app, you'll need to re-pair them with your phone for the name change to take effect. This is a limitation of how Bluetooth device naming works on Android. -## Development Resources - -For developers interested in the protocol details, check out the [AAP Definitions](/AAP%20Definitions.md) documentation. +## Bluetooth DID (Device Identification) Hook -## CrossDevice Stuff +Turns out, if you change the manufacturerid to that of Apple, you get access to several special features! -> [!IMPORTANT] -> This feature is still in early development and might not work as expected. No support is provided for this feature yet. +### Multi-device Connectivity -### Features in Development +Upto two devices can be simultaneously connected to AirPods, for audio and control both. Seamless connection switching. The same notification shows up on Apple device when Android takes over the AirPods as if it were an Apple device ("Move to iPhone"). Android also shows a popup when the other device takes over. -- **Battery Status Sync**: Get battery status on any device when you connect your AirPods to one of them -- **Cross-device Controls**: Control your AirPods from either device when connected to one -- **Automatic Device Switching**: Seamlessly switch between Linux and Android devices based on active audio sources +### Accessibility Settings and Hearing Aid -Check out the demo below: +Accessibility settings like customizing transparency mode (amplification, balance, tone, conversation boost, and ambient noise reduction), and loud sound reduction can be configured. -https://github.com/user-attachments/assets/d08f8a51-cd52-458b-8e55-9b44f4d5f3ab +The hearing aid feature can now also be used. Currently it can only be used to adjust the settings, not actually take a hearing test because it requires much more precision. It is much better to use an already available audiogram result. ## Star History From 9d60dc368234ae4b6caab6cc41bedd41c6506f8a Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 29 Sep 2025 01:31:31 +0530 Subject: [PATCH 56/72] docs: add demo video --- README.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d4bbf55c..002ab812 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,24 @@ For installation and detailed info, see the [Linux README](/linux/README.md). > > There are **no exceptions** to the root requirement until Google merges the fix. +## Bluetooth DID (Device Identification) Hook + +Turns out, if you change the manufacturerid to that of Apple, you get access to several special features! + +### Multi-device Connectivity + +Upto two devices can be simultaneously connected to AirPods, for audio and control both. Seamless connection switching. The same notification shows up on Apple device when Android takes over the AirPods as if it were an Apple device ("Move to iPhone"). Android also shows a popup when the other device takes over. + +### Accessibility Settings and Hearing Aid + +Accessibility settings like customizing transparency mode (amplification, balance, tone, conversation boost, and ambient noise reduction), and loud sound reduction can be configured. + +The hearing aid feature can now also be used. Currently it can only be used to adjust the settings, not actually take a hearing test because it requires much more precision. It is much better to use an already available audiogram result. + +here's a very unprofessional demo + +https://github.com/user-attachments/assets/43911243-0576-4093-8c55-89c1db5ea533 + #### Installation Methods ##### Method 1: Xposed Module (Recommended) @@ -116,20 +134,6 @@ If you're unfamiliar with these steps, search for tutorials online or ask in And - When renaming your AirPods through the app, you'll need to re-pair them with your phone for the name change to take effect. This is a limitation of how Bluetooth device naming works on Android. -## Bluetooth DID (Device Identification) Hook - -Turns out, if you change the manufacturerid to that of Apple, you get access to several special features! - -### Multi-device Connectivity - -Upto two devices can be simultaneously connected to AirPods, for audio and control both. Seamless connection switching. The same notification shows up on Apple device when Android takes over the AirPods as if it were an Apple device ("Move to iPhone"). Android also shows a popup when the other device takes over. - -### Accessibility Settings and Hearing Aid - -Accessibility settings like customizing transparency mode (amplification, balance, tone, conversation boost, and ambient noise reduction), and loud sound reduction can be configured. - -The hearing aid feature can now also be used. Currently it can only be used to adjust the settings, not actually take a hearing test because it requires much more precision. It is much better to use an already available audiogram result. - ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=kavishdevar/librepods&type=Date)](https://star-history.com/#kavishdevar/librepods&Date) From b43e5f7526d9e3abc2a998a055eea4a0620980a2 Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Mon, 29 Sep 2025 01:44:04 +0530 Subject: [PATCH 57/72] docs: add new screenshots for android --- README.md | 2 +- android/imgs/accessibility.png | Bin 0 -> 179353 bytes android/imgs/audio-connected-island.png | Bin 70548 -> 0 bytes android/imgs/cd-connected-remotely-island.png | Bin 70877 -> 0 bytes android/imgs/cd-moved-to-phone-island.png | Bin 71450 -> 0 bytes android/imgs/customizations-1.png | Bin 166744 -> 192233 bytes android/imgs/customizations-2.png | Bin 166381 -> 307615 bytes android/imgs/head-tracking-and-gestures.png | Bin 134208 -> 146289 bytes android/imgs/hearing-aid-adjustments.png | Bin 0 -> 121001 bytes android/imgs/hearing-aid.png | Bin 0 -> 63696 bytes android/imgs/long-press.png | Bin 61440 -> 91942 bytes android/imgs/settings-1.png | Bin 176217 -> 204459 bytes android/imgs/settings-2.png | Bin 157290 -> 271392 bytes android/imgs/transparency.png | Bin 0 -> 145869 bytes 14 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 android/imgs/accessibility.png delete mode 100644 android/imgs/audio-connected-island.png delete mode 100644 android/imgs/cd-connected-remotely-island.png delete mode 100644 android/imgs/cd-moved-to-phone-island.png create mode 100644 android/imgs/hearing-aid-adjustments.png create mode 100644 android/imgs/hearing-aid.png create mode 100644 android/imgs/transparency.png diff --git a/README.md b/README.md index 002ab812..55fbd4a0 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ For installation and detailed info, see the [Linux README](/linux/README.md). | ![Battery Notification and QS Tile for NC Mode](/android/imgs/notification-and-qs.png) | ![Popup](/android/imgs/popup.png) | ![Head Tracking and Gestures](/android/imgs/head-tracking-and-gestures.png) | | ![Long Press Configuration](/android/imgs/long-press.png) | ![Widget](/android/imgs/widget.png) | ![Customizations 1](/android/imgs/customizations-1.png) | | ![Customizations 2](/android/imgs/customizations-2.png) | ![accessibility](/android/imgs/accessibility.png) |![transparency](/android/imgs/transparency.png) | -|![hearing-aid](/android/imgs/hearing-aid.png) |![hearing-aid-adjustments](/android/imgs/hearing-aid-adjustments) | | +|![hearing-aid](/android/imgs/hearing-aid.png) |![hearing-aid-adjustments](/android/imgs/hearing-aid-adjustments.png) | | #### Root Requirement > [!CAUTION] diff --git a/android/imgs/accessibility.png b/android/imgs/accessibility.png new file mode 100644 index 0000000000000000000000000000000000000000..9017d8231db96aff1e1534757946ba04548782eb GIT binary patch literal 179353 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ξRUvRb0lk|9DQDY_M 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 diff --git a/android/imgs/cd-moved-to-phone-island.png b/android/imgs/cd-moved-to-phone-island.png deleted file mode 100644 index 81bdfb0b5ae3d72c8ee3ea7dd63614205e5d1801..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71450 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 diff --git a/android/imgs/customizations-1.png b/android/imgs/customizations-1.png index b18993d1517cf0cae1d18cf827a4cf5b01620832..3fe3a4ba53e5e2a25dc2551594d9efcee77e2891 100644 GIT binary patch literal 192233 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

HmmoTd-H;Mm{ToZJ7_#F!F&Ri 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

}mvJjE3+aWlr9vb$T%oc( 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

)mHTMy>y3yzde2NHXKj^fsz`}49!o)J~0 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 literal 166744 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 PP{_)hkxn^w;p+bZa`QZO diff --git a/android/imgs/customizations-2.png b/android/imgs/customizations-2.png index 777f517788a42d4c3af1fb6b2b6de2e967992c65..237876a8df81a989b213e63a2ad023d50a78ec4c 100644 GIT binary patch literal 307615 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

`#c; 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�)V66)Ju zVAW?hXG6($VDH{n;2(H;dgkTL$BOSqS*i6@JyewNQ9l}*&-<2YANdPUi7UcR9tXIH zrqffb$|bn+CwAo24jqUNaO}j*@{Lu zEF$TE5tLn6#kZAQ!l)Y)+Q4cA%UIWVV@QgvVJ5|BERvf7yeqf}RaIfnGQ8l6n+wfN z#)P1P^*Wg(({S2eD6z+p>0S*k1yCwmULRmq5c{j-P4g(;d{N;VfZ*Q*~H*e zu+(GMr&Vlgw)73LF!XHGFh<|`-sl+CJeU!r@@{T@WiG3pBLlluFXH=85J1R=M@w@A#rkWII2TM^!B?U$BNB@sk^`bp=eGoe}5oro}IkF zn$u*QQ5z^KHf#I(czGeM9gau$vR`?5Q9`Ob{B z3bq-5y#(tm1mcCVtI3EdDTTs--;*JfnRM^Vz(fgg@xp=vLT>jMN*nu`0pMHW;$vF+ z(o8SGdjT6rAo>92-Y(|m9E)i(V(8|WoX*q@@AvvzS^0S4^Vrxoe0EIS}lM^z?MJ^RdF!Cx#PxRi5_Kd#BP3Qun;n1@63qz0HCzJ zSf%TC7VJvsou{`k4#YkO;0{uSV(|sAIFgfjKoR{SGKixNL_GTQ_unL6t97s8*Z^S+ z%{rnsVqg;;T@QBE#>iX$yy|ee@PzSRD?2;F34z4z+80+FnwvG`*dy?3Sr*~fxIXw+oshGlZ#g+bWSri7`hVe;0kJRjsnl@eO?|Y z3Am7lV82VvqZ+e@NQXS55Oy{4!vgLtPl~-E?LLP|thV-tA{(s~UN!`aa8moiF9lJd zR7Z}y5e8Mt2odkNu&j%8w*K;~*Cg4gbCY*yxs1N%p1_#*^eI_}*sQD{?gBAb_aX7G zU&qjrXXoS;ZEjGdRj56eW5I4Wy|^fS^CE9ng)F9@PE(o-4bLe zJ;ER`D9BI>TGQ$~yjUnHE^WM8v;_XfZ%d&fS>LP@M7`s}`rB0m!D?J#6h6#arh9+=r+@d77E zP8vD3&S^UXCj83PLn2Azr>#Z!W!p-j{K(U-9taPZ^_#=w1P~X=!GjxU{DQP7411|X zCdtrB0H#Dr#Qd0K(BZGcvo6lg%4wYl6;TJGdIc+axpf%xDmsIXo4>wFT^eyU$8;X7|n|3c^JE zgQnmQoYL)&rBOCWNa(|`A~;wN)LT>2G&zLQ4YD0XU0^VSg@%^VAx{wzNco3VHN!4x|0Fp5oS6lVdAX%7KMry{ z%{3~T6rrb&j+YMg5{t#)DZcP*oVmCMWL{+a9+& zoALiotM#ggx`0(rK#m?{zQcjtv7atm^%ymENHAX-wY+B_w;7-fQEu*lFN2ceek!iZ zza}PXdy}qXi%(H8b<3wDjPNJr1avdJa^9I-J!IDi6Dhq)FTWabDD2v2AO%A< zClb@sU9@@DKp~hyT}JOXav4w#RBn)EDDS(oL%oaEf`iZc!@c|1#{qI_pZ*C__`5W+ z)N#>{^36-Sx^ZHRM=|$eo8oqLW+E`jP5RnZ5HwX@elfb<--jl%F)srDPF6i+Iiz6w zSyX_JXX!zb2%aR<#_wUte;!_NUiymP+5UB4U;x5%c6HmEa1-PYD}kY~P&*U{s&5o- z-{zk_76#I>ufjPVo)SJJOjFo*1Zu6$@u(zJE_!ozg=1G$TY6 zH*YRO?j*IiKks&@R_U!<21z}eXbX!Ld-Vt^c^g!&<3gW%di>6YV2rJZG2G@q#sg!5hU>0o2Y7Tk&s#AIq000+ zeC$}7GsV4nMltKj?jL5SI@;T_YKX8YvC4izuvUake%e~g9oFES8gk1*jd;}?ARxnx z>(Q?LJGWn!k$IOre#z?H8kt|fENl#rWpHgGE+XRCf~$NwkXO`2$d2$4!@!FA-~xLU zo-FMh%qDS4tig!r_BFA5mvN)(xtev)Y1GL8^uHq-gNa-=I_l%#O#5}n#xh)j*Fp9` zFRG@h3X>VOJ*>dYHTLi9mZFFVr+N1CsP3OYP3Axcr*Eo`lkzVK%-kHkag22hlgz*` zx|@9&bN}bZQ$!vdKmUU6*s(u@gM$P;z#r8-S$B)hch^??RQzR|Y^E?=%^ui+raHO# zD%$(5+~GEi`GUMqYVO|6`q<33chSpTlgX^NqzVc;RKyVOGX@xLc8Kwu&Fqbeq62av zhBVleoJ~x3G0gdx@@2$Fsg#z$6~K|4gd^EP(Xo%6=zAYJ9%&ZJhT6ucxzgfl$<2N9YvgI7nJBrwG`GdoV^ z;OJ=xZ+xm~9#JTV5WL#1P>gU_LAWSTc`K08}a ze&*{tzj(vtldsk5fE(A=WJ8CDo!uqrR|-Q>wrtorwZde=wwV*ya0Imak9Szny5-tY zK$e4MzsYqUa18j_ip4cjQ?M<@%gqt8>u8N0S>1X}CVbG(C5JGD+@Vxh!}mqbjEf7H z!k-<`lkYOta)Q@uw)+dx`KeAoDetr*w~9!VDX&Vsh1~AQ9-4%>7*ifRu6PDJEqpIe zlF6V4Q&YQu=bRA>;OsEX%Mg{R41WGD(r)E@`CNkX-May3a3MSJ@wwOIBpt5+%U{Nnq@*M5-ch&FdcZ*v`kO<-_ucUD zfbHS2zY6!on4-A2Z{$%eNbN|p-$5}qH;1hxcr2YcVL_5tqRyXAgkSaQwd2RH202cR zj&^{YhR}4_T0;aw5Lb=dh#Jqg|0yZ{mY}anO!R~f4bkNoPpqSC1mNZG(um>++(z6W zOTL#ts*_A&d{G=sPjdmpprVS5avgk2>~x~oI@vMnCDmskfjvAq1~24q+Gg}K7nbzy zbx^79@-yD2q;$B2S=*hBEIWa6_mQGf9X&_W*BMU!`TIsuLGy~1>$9-I{vWM*go1lcp_7NU^- zoV9p+m8PzC`-ksW6qy>wzPun)k2 z5G(8G+`^4O2dpUSrmo&+Xmc7uVPRqCEWLwAm1ukxF2-_O_BJ>3M$m=AkpP+?5wy+w z_xobxV`!)v$hK22=eV>Op1+IhbAiRhtH9|^_4JM(J0@(UKlWlMV6N*lHuC2Pi(gn^ zj`cmvX}lR53Uh(z5PsNkAdy9zI%((b-B}^NyZP6}K41K=D?dq9@Y|oN)2=IaTiaTj zwrm&n^oQxx)U$f5C#hb9Mj_? z%%rK4n3z80`i=rnS0Yb+^}mTQnx^k5OeMJI#o2v+*Vf$)*|j)We2~y8SDt2JdEo`N z2ZXvwZ3STTVTVV-*Z|+2+FEEjut<^>VFwf*7M5o%5DIABT43KnFSxIRr-c* z%ep_^g>2AOf!<$V&k0`Mi2Qu%p8NG2pV{2v5$Ex^v?4tVxTR))tL0~Vne(Q|<{*5-4Y8IQ* z7618j=iu(i!NT&x$sOGv7U9Om>J*|V_AC&e<>!AI-q~n5c(qVQzi905?z`_VTy%Q> zkZ({28gxer6++o3hCK1u*yeo2t6~79D=KtwghAx>w4)%sW(H8%bKuluXdBn@)QLu% zqP+>$f|I`9S<&o!ng;bLQTv~Y2-uXKJ2HK~*`{U3(FI)#(-QgmE}#Xkj?yZ)to!}F z@h98q1a(5=rfq4gz(4D`<)xjaf|jQ`6FJHk!w#c_bDEQ6#55&G_i;$^NK|&b<`d{^ zzo3xpU1OYc=pM?o1hwNI&Q9c$Cq;-jg=lU-u7VZel>|ZGCvOHp<@~(TnU8~GN_^?3 zR+f*ZJs@95k(jvpth2XFCL|jTW@F;ipfHmcD za1p=MHftTO;Rv`SJXX?%K|@ZKoYsZ@?_sp12Tn0F9}Hrp;8d#D0$67X+PU>oYLiZrb&r$AO16#?t*%b$5=Pc@ZRHz7U^kdV zj?=y?mz-NTcsuJl6L~g=H#A?npDrS==h(^E>1j}8NSqAGfzR*LLN=H0_M~ zK1DCDQWdDYbzDwk^l~@rx8!+^ZCY+_Eg3-xH)IE%dTO!0&WnX}F6wXzhY~TyFhQG| zz5370Ck5#IzbvRjg|~2)nNJ`eh_ScW$46K77z4aq+7u@1VhajhQAxvC@4Bp#S&amQ zFg9(Gh!s>!zFsluR#djX*&?H2B-E;DWMrfrdd}Kbr{=@Cb@lZIE~dqWg=fJA)!vTQ z+oNjI#2XnIg#pGaFW0(q z0C`QXV$P<3@_TI6HG6{HJuV$6xH*Sx4mt3HqCQI{^N{HM zvx~gwe|}X{#q3d_2zVLbvt0cIky|YiAOPRz<$?HM(ikW9IDfUoq;1lZ{>~|C-{J3J zm4BH-zdh*L4w=6v3KIo#7F+iktJwwl4a2j`cDTmI}w6yS@F{;5p36hEtdV5ux zwJYhZNJkGjTO95M62tF5Yklj{uU?Uo$I`SJojLJAYgr3?J+HI}_Mdzxzy5T4FjzS7@LV{viI&K?m_oUsqhonI(`idlH=}sl{m=L~ z%is9CJZ~4V2FZMTzZ49ox^7buk!**@2sHIk+1VK!pFf2GI zNGe`IG`bvHrI__lO)zKxZ7&>}EI3H!z830ZBJi5+4g`iKZc`m7N`QXurjw2Uwd5b&@AEQhpbhxLjWMy z<&bA1-^G=Ew_a8=^MGyvVO59!)V+wXYi#qFSR}$?mmrD(fj=re>tRC#uJ(HWj%hruc zO$;xr5IR`_bDA_6&)=!tFuy`$eA4yqPOhjvxdu)J<7cW9sd9M$S_Dknm&*B=ss03T z@bCn#PYv=&Oq6tDJhq8MU~p=Ko!WqXi9+`X;y8W9Gw#Ad0h-sphy_hD=LeQ4wu~P6Bke>MNZl#1~@%84G1lfz( zd7x%+sJ_r|wi$I#{chF&@l7oY?R0nZV9ll7f(PeS^Y-0Xz$G^8E7M%wmswsu1=ndr zO&PV;~<^z9ENr9L6!sApi{ z1{zGI5sA#I6Ql_1po!j6Qi_*jDPV~Q$d1;AFxKKK<>qKM#-9Z29l2S&xr+HN@`I&Y z^*g!3LkYwk3@^@}?E*u@lYXP?NS_Ch#3>3Jv9p0B&O%PU{hiCn>FIo?v(IZ_7y=!< z9E%}dz{b{=;2}w&YYMVYTJJ|(ycj!KNnDaJna_7*jXH#afy`mp?C1AuWj?B?sI#bu zjfNbn2F84gb90I+DkgoD{;pOe*AgSbPiysY!-m0GG!rBi;7_Z6r{>!>Q)JuR!Q*c) zaquPkYdCqp-hAwV-iaN@Jss0J+fNITp)kI1{yZWERFssC7K@m0SU|4_p3&!@WC6rm zmD4alA26GZj4)EbLBzKv5t3IZxdti9UXLq<8XG8eDZrM*?DAfA5?oroHpWR5$)(2U zYnB<4 zhW9&l3L(VLI4%U^?;P8ZcMou&y3>F1<86CkT~p`)T8OHe!-F`h2PQl}3zNSDyC1M7 zOh{1Ri0RhcxhLZ+IG$}(^gAmirXtIK6%8EPpgarjcB#Y0dQkqmyi(}cM5*6yaBQNu zXb;hjD{r=6g{#or;O#akk=x660V8ToC{%Bpq z!Yg@sdDGNptKoHlLI>s*8bOV%tx)I}LLi_tnMz3{voGD%-4Ry>JvvSxfZiuUzex%T zL9ifUT`CDfxf%O4^p83MU3@Z_MdR&xy$)CZ;l=fEcY@LU{(pH-BOiF(eY^#okLlmM zw#ALoJ}9%G?fXXZf83yBN1&@c6D3>*`8Tn0 zEN~r?Q7d9*WMt&#zRVoxU3D+SV`b%OuEBl`C-$srP{Ekk>^A&qpM4pmkjD-LhJSBq z%2^j#RD>5CSjqHsb&rWUi|M6YI$=MSd>Wl5kixdKWif}BVvN^hBgtrJF!`>}VG3EW@iu_z1 zY+!0FhkRg&tb+s?=Gcc19|i?cVizTtgW!T-*Fr3*mc0Chh`ii&WqJ8*-0jh(#4n%3 zgm4mG!%fB5OY-@#A##&k+W9U`!i|5cgo&WxxTFVuLq<5e9HhF}G`e>(Mm3&~^7Z{T zH+lx<<~Xf7dV12&NdsrRV5Of+>H+eHtc=Vtp5LD&vK3mPV2bPwv1uUZC z<4+xSFvf*x|L4M}-t-@(F+QjHrm?37ZUo#FJ_@%CK2^yw#iA@j!0N!jOVm!>I?jnV zWM5v7M+uMZw71A6{4YD<&=m<6Kl6W@S5A@F{MW$qo9?+}#m@HjBw;>w_8=0tI^RDD zGoDb+^0waIJ27$qnDOLcx`o~C2I7D)Z|m!G!=?aTc5aOi&SkG8M1GW%42-qZEi4@5 zcZK&l`>*2F$zp<{*|a??ApV%;FhcJ9MQlOz1uFnWG~7ib#KbT)0JJySbc;hPgM(F2 z;Clp4Xg11qfYD&#O^uAeAIitun~q2S02$|5Gtq!YUU10)P>LVx>DCw=7{H%rXJ-fN zZ+$c|n#5`XA72bdCGsARVjLczib2oQCr?NlP>4b`pvu5&X8Sgyct4&vZEM=4qCR_> z!$}(D=CSEM{{E+90bhZWj(Ht_PR8VQaq%jUra7p6PckwpZV$$&xg*rG($oJ>Z>gsr z0@#rO`#nc*5i_0CwAeRqet@Q2KTMcrj>RPDbk3oWhG3A0LzaoE2=Ojs|W45J+? zDgx^rAR`3@?0y-7UwINj<5A8ftv!$SLgCo)A=0iFgdH$!;Zc{@ec`{`2_;DW4?7{( zzpX;U$c2G-%K619EHru->pU4|yy;#n+5!btjGa+mKm#9yW6$5;-_K7L%pI;aSKLH& z0MApe0igU=mhDwFfY^B?{hqb^uWQ89aZ-eA#`DHK*k_3_8(OO|k;CHx4C&(+EQ5R% zj=%*AA?*YY6#)|=QnG5@a~2jyr9X(Dk--25W4k>Wr-X#U#RFNnWu&CMU|>b?N{OO9 z6MP+}^c{Z-6U`Fdz6JH*vDeltZtepW%K2-jt{*)JB^Nw?7->}L*W0rP!N|j#uMw|$ z8fY~H3LWZwJPCzv!Q(!pgnbpprev_up{Jkf&vb!B66<_V;R+KRQ+70^#*a@Dj*ufg zNVS#i5DOVCOCp8fBvkAkk9E=A5m?!kyFPw;AZr_b4+jyDc-=g;y2x9z#wCSnvR&9% z34g7wX^F7$^W9Z4ZmUn2a<+i#-TF=LiSHinrPX7CJ4o1bx zR(^7?;nUf}T7#KCq&4 z$ofcua@UOF&f@O7#kX$u<;K{bU{NI%5n)S8WMO?o^ZJCW)E;v3#;yhaDSC>}dUm;C zJ%8#pgGGD9O=LJ>4Br?bicmHaNWEjP~-NO^}#)()dkKcSNgvOcwke;19BHnF8{v0-H$Iy(iuD+7ZcAsWEvZ* z$O^cbm6CYgalJKtszLVMQH14LubN6S-!jk>Y9o_AR`~vyp!3oXT#v1LOi*O%z_E-W zE+?naXs>Nxa&G?US@m8UdOTxl$Q1#F*FeorrD|pzCIsV{SKOkZ#J$c z4@Ft(s3ALxh{)xYC+(MQA8n$=CEmuQy7#Z&`}#toC@}$?ULn0RpJHO(zeS+Ria78) zmD!XuVcC4~>Y5rP@sVmaf#g1TBQWp1E&rbzQraye5z;uA_8dIc4H1shRH!%^;g z=V#B92kg09=zwS)XdcRHjPoqJFL~ULP9nWB=XkMs|CrD1<70O@V7ZiGp{y9x#HsN` znxV&rT-;77_|+>pRaL3I+(+i%e`NbQ;oZAut_@08_b0Y;x?4@;wFxh@zx5YcFx1yi zXp)4%N)D81qywsa&-=bQa0*!X5)P6#nZzTzoK@_l6s?Ph_&;mkW{*oqxZq;lp*I-T z_MGGfE89&Jy5v`oJ0o_8dem^?PpZiG$)6Q{Bg#iTJVHsYKtZS(eHb9lk8+T*+^VSR zN>#kYVrk7^*>#yow;0>l?H~SboJV$LMAW&%(8x%MU>270M*CHhBpeBun4XXhv`etw z56G?P=q_fJ_@D2}lwbWP^*osT)_dv^>`0GpU<;rBeueUkn9sw2v>-SpVmgZ=~!Bx zTXzK!qcL83glz+`+CfGG@9;u9rDvj)hjC#q^}TH}Qd{U8)6x(~NHogpBqB+QZ5p-x zv$QalYOrtT_C%QL^Q*%by65Gm2Yvei&x0^Q#V9A z|AzA}%Iio$W_|Dvoq@RU=zo79#e`-aU&^UUOyrs)$zh!kie+eCbSTb3?S?hSLV3q& zg73)-0CjvOcNVxx8w^3R_dm@33i=~RDez^+9}Z??6ekk)+}7s>j)~Qg3s_C19C2a! z=b`k>tuu<8o)+^U1$TIdN<%tK10gDgC*R04of3H4df$q{7ntjNy!+Gz7o>)7EJ6+V z!|sQXZE<=*f&J`nfu4gm>X#<+>-zTtLlKdA4;CCjBNt-ZN&=h9?d}grQ_9LVFdbW; zYpQSOJTVw{B?V!c}0gp1~z)=ueqC6kY z`CX64V@$kHKCCumcG#poI&-}L5QsA%Q{H9|K_TPY#B-C9SRDGP9_kWIr$Mdh%3Kn$ z%-rcX8RgsH`OkL01Gu-Q&>ei?0fa)E1z4vwU{0DXtBIf}Jcr z1KeBRA-eAyoAmrSKi!U>hC2`Cv-mJRdHJo_G;ynAB`N`46z|kDv_JYLHfkK8`nleH zN48%0A)v!_ptR{k6b024sS`A5Ti#?3>mT)#N7kok@j5OY36rU_JF)e%mq1_KE)cZW zY3(%Y6VkwHroie?_gp2ScW!<6b+xXkQGa?Or{tQ-dTic1UTn){Z@0cRQvS!*>4okY zCaEawM}KUKTa3XRa^ADm)Bd>0Cm^uv=1rA~m-uFy@_uo7%L_EATQ9tiX@XK>)8FG& z(T`Z<8J@hVWro{l@b%?`TYvKneZ%NGGqbhDxgnMUa;hs{4TgA-$-SRUUD|s2%C5(k z`?|Y7*-_R#;=s#~9B@pgjC@EqyNGs1G#Hv18~+p{7I?({&-biVw=OfO9P_Og`e&F7 zDX1O||6{Y|E_P8mhQ(*jw^R<#K$7=SUOi*G2;VT}S7WC}jZV6E>G{?#e4~=n zH33!7WxkZRdrD8@jj5(nKfcTvAoZh!j_?~ks9W#R!u^}N)IP>X_!Z{)69shrPv&ZB(VC_WtM9Wa-LX4GsFa9ZLs4*_8^($6R9~ z&l6NkR-8Ewya-?QbB>yllEqc``h{LZjqUd)mSqe;`;nw*U$pfn)&p)NwixG8`-V+j zT6;Ui#L5f)n}P>RFzejCt&6wr-eTM`ZX+1^UPWcjvZalj>ZwMC9BVMK2YLDg;pV-W zieahI{xLr9__pDL9s9P1Ma}^#Vh@ds7vF{$$)d6e@8Z&wey*CI#M=f#t|Ke@t{+EM zy@{`44ac7glfgrT`mC>?@N-02kduKKj-owW*5ysGu=F~M`~!djc#c%rfzLArfO+jL zb|prRso>!h(bnImkKxGb!gkk#%V4UUicfQ!of;3W^PYb$!PYPS|Nig)$`y#(yL9j& zJ$xatKUb0C){uLP>dh_ zNUHq8?IJ+R=kSjJsilKkeufVba*1O{mN~G)G)I-)*|RdBEIJA7_d*SZ&~M_5+7QR;Dx%sZAgvg6NOmWp<}QK$c2BxPM>8ErRnJ(Zh=< z1fD;Ce@F9Z+Tqiz#(9FgyvNLj2+t;whCC(cah(kf6?P{?$AVRs6_VM7g*8}%|6QM# zlPv~9x{4B?fSIEfq!<$unQHf;OJiqzi=ufJes-R_R~*4rGCd;T#}+{QrdtC#b$ zR_PJ1ytfa{L-wtW_Qab{_p%jPo5pQ${Ux}VS!rY`jh(ly;QdSN)O!q^g5O9Z*@&^h zaqEpgM5ZIt-qmgULBUNwD1P(P*1I|ucs;&?zwA2cDb~66S!c?&Nvzw!!3iRIm%(`~*(~IkR_~yTVi0BC^)E>;iHA~MK_mT(0tgh_p)=M5<+*Nr> zuwJRW*_e;jW$V}PqzaIdk+;q5_^xxnSyUb*%$S(9;VmNyc$dcv&p-oz0oIt zq-Xz6!9mnbzIUfFU-a2(2Wdqn@@rCi|MT0D5g`snMO!z%(L5$srcW>uBO)@Lwr{Ar z8H=Yi_&OD5?mr6Wkf7_BMGfh-t}(GD&23%%A8gY$qjgtf8OgnWoZ2d3E@i&K+Gt!q ze8`(P=bwlC&Xdsn_S}oPV&hx?{l=Rj_~n5-Cbd&ENu=W6p2U#HToDu7cBJCby~WF+ zfx901<8+W(qini9=IzI_V#Up;ZIflR5 z#F8IPuAm?s=MOioouFb(tUAN|#cL01!K>)-@0AxW&T#EJtika)Odu_9cje;z%JICQ zj=H;-G#kflaIkCC1&oWG5)~zT+d!zb=B}og7<`Pe`VSX?TrqdhgF%B&m(n;{=oB&K3A;j zM3Q_r3k_+!F9|a`Ps`aj7Fo*03iDs5$)HaD^zp-jm0sc>$MEcq&O17$NzE#oG3n=< zU2}Sy*M=##|E9l}&S%&sWFq`wbO(uwtSrgy9d>!;=^B1tp9koIldB2`~7UB#f3Pwm8r2#f#6vxS_ZMi zruXl7?JoWw#JzVs*83kn>@-zGvXT*LNt7}&ODaiLWD~OY-c(jbQua==63WP?LJ|^^ zkdeLjp7--|&i8Tuet+G6-q+)t^T$z_>-v1&pV#~Ke!iX~Hr^sCwX%wo($`^Fe|xzV zoRX6FKGzWR+R{0`Jx`8iAm)|0*Nf{j0Jf!`hTX8BJ8Il9W?!l~XB#K^z+Dv}IW8=GCwK`$(p;70HW*{ucY4Io;MmJ4r`*U&zQp$dP9w7&q)R}k( z7Ar45e{o)F=Uk0@!<3TyV7W?qT#yJ;#&6qSzqn7hEbIeDY5ZQPAd<$OtPqK_qMx*? z#0YQL<2(N1>(>^K-EwBt)KeU{Z^>0|Tr{qFYS~Q5ayjME3L7u0>)^xMvATMy)dNpQ zc8i^+V&m}tAU|r77h^{)nS)-#KQh|UGCVc4#mHCeTiZC{T_4gkl}zJZ3*(IKmA{1y z-`1>A4t*6c#Q!c%)~{bt?5W(7{xy|nwq+-d?GUBZNvx}4_~3NBdPXKBHfM4E&j~hk za4fPixpIW1@Q~=xDYoN}CzSVJ?+iIV_dHjZLd)EgJ(ejdGFjqJTS5Mu`9FuqS&kmk z;{T~_K9tJxYxj;=soMG&zHjsZARQcnJ}7K=YVN*DLQ3?trd4kyvqO?RGK&Qag;SV! zUwOl&C2p|VN?LBSdG45Kn#>DE?d7t-+Vf&8P(upboa@!&kxQdwIJ~{UO%rOH`n{gJ z+_aPp%Rc6|`mKNW`j*L9eIi{Q0-2$z?s|X*swVjAoXFL@v^i@`X8Hcu8GZRmC$zI#dgNaEpD4xfg1*6IW${ltL zd?_%@(dL+1_h9i13i9$OAbFT`z&VhDDqxP7$W_5*{UeY^!VcLJIU2QV=F0cIuPhiJ zZNB1V6feE3_IB_*-K1bdHs6ip1kJmiPx>g|wC#(jkHuSjr=Xf&s*1hM$>2KOv+1#S zseEPdWSP!L%Ia^n<$-XRgG_ekjomEUxsSd3SNr-3@8*1SCTIDdGRL{2kNh*XhIQ5s zUfE&}+OLqaGO@VSE1^lrZTj)|t>?!bNzXi#XQ`TfeUyLeB{8!YxAQT5p$tnqtp-_P zRtn43>z#2zB8PCG>ZtS$E{f%2V;cpzf!TA|X4SAcbH9T{4E7Q*qCA7Xij#J~pTFyB zLPbJ*9y{>aPzIO16iQ_o1Ups4zKMSQnReHQq(;bXT?~=iOsl`kcK&cbol<2fv_r^p zj&GoWclYBCip;)CwBPSMV=T{@tnO}?q7XeT>dE$g_8VU(%@AJ%w?56Hnh+uYm&D+< z8A<**+xM9gdvXfq8+|ldxnS3~!d@~zZ7mg9&+eI}CFiC7((n?8*h4JG{AzX<+>+-jem8Vl z_7L%|KXt@{0MU!#;Z4j81aS0}{(xJ-*H16EDAzD2yd!J(^rTU3*g1$ITzqzRjffm& zyK?2$+-FCF*xp}W)n{6(rT$)~kW!<*z;ZiO?XAEaj$HZY2T6IEj7jHRE*HPM`DzDI zPcc-_CvKbd5gHlcnwvASip{2_oT9|npAt9J%g=Od(c0^g&}ynF-+J=UEtQ3dsx6#? zD$po`dD|CyIQYTtLnYt0dYv>qQAqvrmH2os8&NXHhPP2NXKSgw^)Cxco&RTMW+yk@ zlo>5%6ky)LLQ=&05CRvEwpb*ex{WU9A{W;l651aVA;OgAJ$@|YX9KCB;Afgr4^@u$ zn8|RtL`PbEG~zhV(SU|Aq05)s;NL**^7|*cGng6eq?8taf-Hp24#pV|mvg(!Xif@e zTzyz}CnlBhdhK%HWAa)3(yaG~b_?G8oIsm)QaZMAW$$&dMlMV3?jt%3711_2BTXl) z2i3qIBR_QLs;oTRJpiG>L8~0@aBhm4vgs}M-K8!~uYK!A$`RB7^bgJgMb<-0WnPx` z+&SQn1%-uWHp2c>v{XY=)fx8A-SCSV0CHkho+)!0{2n*FntvcKWbt&_+W4Llw>m`1Tc=v$U@0ZVv(Z^u$(kPwZ>B}~{4rh5mPwM6rBs0J zyR2-XUVRjRWFhwb2dN`V!Bs&3kEUIlE>IoMAWY6d{4{HyM@&*j@v2|Lr{xc6{euTJ z%GNU>9{Ztq|3=~rj3!|p6)!Ek@-zK}uI4y9Zv-QUqWF{1?*daXmkj0X?830hPrrd0 z4>C{S*>LDm)i@U=c~Z4_F%)Aa(XWKq*wQ88;2JUF$O}3Ik*yf<88)`nj_!LGo0nYf zLZ(y>pNAVy>8Pk$3gwWsGy~cU%r8%Q24Qjq-uTGEDnZ8S+kJ-d>5UwWr9m(`NKbo; za(~7><6E~z2(GiILTiKL zV5B=6Wo-S__RI{{D0L^oO@QR}ff>+U@rrn!CC6qYDX}$6Q(DuQ>%glh6G@YWT44gyY-(SEJCaRprNK-Y&RYozIYb~;P%}Um_6b20UJ6v)d8m@W#G4&8@!XFui3Ypap<@= z!3q{sBv0~E4R_2PAjd?GyPmFY((Bj6UY~)*>*uuH%UC_TCf?o+i_MAvIj?GE=JeeVGkjV{}cVq^y?x2T(&2Bs=kjzwG?Dnf)XU+43M z%e37*p*ejAWm zsj_fhedq7{=YhE6g)=jTMlgzBnKKu(|Gw09>N-m!f~-RzY;J6{9Q~Qr)Rg_t&OH!V zonU1p7qzn9TfXpX(6BjKKuB@lY+lcVMupq8g$X>rSx+1$r~IuXRGLD31+RaOzNE`C z$LH1S{&e{;1=SMLnTBfQzl;NT-%3?_~9y5B-T$_;eWWT%Ef zjoSN^=yM+f6 z@mWD}D8rnNRcztMH8Pk2Ym=2DywR+9!cO#SsG`QL_)r+DjM&G_yPN^N*PJK18^*yj z8V-#W6J5}G4cipKrL#99ze}&HuC~^Bp+ymU5-2I)d!E<-t`_hAxc2(ueJ};?r7z^Q z@(3>dnX3y0&-`twK-s0^c_bWP1OWtHtCf*C!1q9+EiJ4DH zt zI}5``nN74SjvNnli{PEwQym@Hs@A+R?2aq*Cva_XJZB54I!WTl z^M9?vT$jO+0W=?_X ztcXR2(?$?^bvK!s>@Mx93nn}r*u92UH?e_(?k-hsS0Y6ySKstMJzkmj$D2cRN7u@U zfXGZux0SqTukj-dM+3}jZSlM`a|*AG&*4|Ma-v$AcC8QMW?A)o`~-%#4fhfnAT`^T zOQK&fzNF?X^K$hdzu9Frs;DXC?>2+9zk=946!P-r-rGzpS}@;gd~dr~r^5TGjX2s) z^8zi#Ik%uJuZNSLg<}p7tsYHyoOtzH@l9Dz^3c>Nd6(HcgbA2fE%`h|vstRT!zzNt+@; z8HlZNn1;f4ZKCn$y|ktdvZTpa#s=F<8a^rxgG zfqkvPwl@7vta1t-LPob7K;Ib|_4)hbhZNP|cV3Zd%+5c@XwSQYhiev{>GVLvktlbCgw6X}@ zVo+%(g}kpsaN*M_@&dh-V!>jI0b}ze8b#|9O%G^H9@@@zF_xLK>)88wk``Y}NE9X1;SLHl+d2&rBcZ zQfoGXDd<*vUnV5Xz*)5ugaXh)|MK+^cm@2Vs=p5Zqq4u8SVneN3(FEKEz`G@yu6(6 z3O2bZhg?8x4wXn!{IvX>thP$<7~T1gON?yYuJ&W_nMXWsIXZA`VaBaxCHFtE#JfK& zg`v+6At!j$!e1xT$Q_Ms<%Q?denXpiFMO#bj=<5w&XcyeFnk1r zvNfL5u#cG#_ka_~Nmf?zZD*TT(o)D_!`O$N8cJR8N%Mt99iYZA9{&x91uRvxfg3Z` zw^|L?m-)PQ>77|;vABLn-hbTsd|9Y8Y!QTtzkdCS4l23SbL^RO4*6z(hZSt98f{x3 z1%Nk73EjIFcH_TiVD`oADTosT3Oar@805GhIyi8INZ0=LvK}&mBS&;d2rLPqA5MOH zRMTFdE#|U1&zIq_Au%t;#pirBi{$;rkNVULI;{V>A_A~HXuW6nkkYYZlnR+s*)6)7 z3Yl3flvM#M2I(UgcQ!9o1s;M`;<#A+WMk7sr(GAo22(W2M`lIqVd-@bxB7XTwx%Xg zURtDS!Uk%FttT^p{_+bqOE`1q)9gik#ZI`g*k=~~caQd&_0OEw53T?AwF#`1L_{?z zAvrv|xDkp~@JgQB9oQNB$_YaQTbId3queC}!) z^?KfcwRZ_c+s}DeGk@8&g0H@TR;Xv~metp($!kZ-nk7=mPO^2obzBxi7ttrA=5`3} zWkK}Faz)azT_>GnL$hu22lge!^^i0m<&+kNQ zbmxvNJl-*slYKTh@~Ql&n`a|}hXyBJn(J8)O?=5@D_C>$KLro^`=8xzhY=W`wKD7W=B7N{&Okr;67QQwQ&&wQG0EwyurG{dtpp7 zQD6=eaQCgX!^dxsEEyegfi-kiQv=hUgY<+vnjfxHT);Yz-6J4m$c1?}!S^|zsXo?O zNN9iYy>wk~$$gu7KZ4bN&v}n_pw5@S57~2*%zn?|qTMgE=?K^x@&3z-L z!%Vfvq|FrOnH!58BQ&R^Hw$%37JjLM&*m9~lPMOo!KLpTwm2;jJd@l8LKPuGqP6{e2SS@I1bS2+0|N3B(Q(9>3!|%3f$3h%r8JwO_JiRfuxu%M; z!ytmmF@*X_9a8lCHeV<1?zl-NQsT!kibgG5Q=?B_!Y!p`6gH^ROcx1)wzctQ0$hvp z0wi@XD2&Z2Ta}Ox(Q8hw4>T1o;Wlilgqi!7Gu*M4t-k;X5iKXU?nRzF{i&SVDf@tP zmyD|bb{yVcY^52Gs{NXGv>x4Ep1a(opp$i1-%Pk&_Vc=-`FZ8^ott7_1W39)^+)}+ z!blqC{w(*64wzmYrlwAoOPeg;Dg)RCN6&X9C9jeP)wH#>jf`Mdkci{zurHi&wpLqP z0yXnhGSE1la9xCd^UWK9?CT(~ftz=hau#OV#+i|KM0^a5jjib|R{s=6xo?cEKusif zj2!$p1vj_N$Mb28!4W zzpK)&udAa4uY3CMZYU-0rHMwEU1{U9&rD88xB!L9F>;(7-Kqo45tsEQy#aCQC$8N7 zOCxl~uPg6s+3r5;6va;!2P??M+e>dtOV_z+gCB(~aA9 zl+xV%uf6KH$#RJrG$bgA{k5D~m~?_kkAR37X^{zGxAlUW2NJh##lZ-zlj2%>%LQ9M z!Sl2vXYaiw(Agj@EihEt`Yxb~qKh2lB#;bi2OtW@t1g8Q*HSGKH($u8BHP&Y$-^4w zYd)9d>5a9ecz#>9lPC4ia^NuAl!mKR+n$%j)x390Ww?f8-0|PjI$7kw{L?8;Mq7jK zJ4916H(3OZF4aGc{bne*zEWn%$?PsH!?p3<&L%r<*u!@}@5NWpaxTO48p%0BkO=Q% zhFKt9W+`t|Qwz2>_o`KYaT|YM4K=KOT?7Z^{uG2Nm_}Jo(8NkDecVZQd10&^sSrP8 z76{N8wSQmV-tY15iOu=w%D|CZfGjOrEeM_4aQW88F)l8-jCP+EU^Fba6%6te|0ZCtK97%b3YV^mL)FpYjVrirTt#npUXD?w5%q4l(!7H zh@|2RP~8hqvgk#GtopxJkYyG1`qc=ti|1E;_im_NSQ+a0Ir`&NoP*@aVC>qO?i=*{ zdq9+Q>3@FH4M!s$t&ng?9O6{3}wTLT;X+zBY={2Jo;BEbqium%u1U zX^urymj~JA8st9<{o#oF1E^M^AGb2p^Xan3-XT8M_O7lntASFfQlkI+o^;bLaa+cw z+FZx}x1c)aOVJrA8M^k!cr7Ap)b6k-|3#{QE=c=UUELV_jgO{Y{tQ5y_xV27O&Pp`g9dxqh8=+WcnGxNlW& zEyqz2?Mh#k?)%u!S3OoIi2Jv zOU<6U5+|P;2o4VqkBme~LCpCS^4d?MEZreSp6U_qQ8^w>q(zlQ5wu9TeD9Gf02l$OME+W+GMYz%rilQKPXIHPsk#kF|)d$L3-0yLd^3gKnhk}qgIVtCc`hsJo(7krVWFDk;r*xi;43wVZr!R&{e0 zCfCA=i2j^DcdoS8?`&qt>^FhQW&aN9IqM;fhWRTCAn{rpWgvR!9MYEAVHa4wazM>_ zYc+Xuh$(7u?yywPh>5ptipa742A_kg6GH_xa>PC~i}WYTN=sjXEW!&z2Y3*DhlWoM zAm!4Za%y%ZSksn4Q{*q!>*=`+;QFG_L+GnFI_>jkkDenUy0+w)r`=05b7i-Ej?L(V zzhf}*un!yOy|Rmf?5EVm^``%HO?vs+k4ny8B>jiEQpLE3OSXUfb6k#C)#v&EA|j{a zfm%g8bYtl9$@hCz_|OytwoA$W#B)o4zd8T@Lu7M(tt)ccC)+Wq@FDT(fA0PxN$8*T zO`M9xpVDMc4V`&?W##2!H0P+@q1mB6%FZ?Y!hdJVZ`_pG#pQ{JlrBU{@^+Uj1I6n= z2z9gi*;pz2=^pdj;awjr{@&48u)CiR;Rgtem5sDSJE{xHZ*5IDmL(RX?pw_Kvcaif z)u~{3W#hL?kJoHI=lY3bp0s5G=7Jxu`ybk)pF%OYM=QYB7f#hN^#_SwlEJVUVuv$l z((H;S`fKiDEa-A`7ts&W`mKT6O`-+S@g0-pPp*;Or&Nxz>5TPop+Ci&y52s!_U-Ui z>x$1qa+Y`U2GNR416+FmfGaB2)|ZI=sq-dh0SlO7YES+iqTwM>>;bvM^8lU*iix8C z5bdJ6ZRz7;l6&QwY#u{v@uYhh^DzCM8H=%7Lb~(}an-WokKX4PcJFjAxpG=`SFEz1 zy1uFNW=Lsl^UZ;i!HxHO41Uv3Qcxwxyq2-}UAE2G;nO$IS7NjEtKB<&!P@70`7jNQ zRcNB=iO?@m?+;b5ikhv-EC$;*xUK-xWyqlv6(5^RVb>K7tR26IQP}$kG*0#)&B}|5 zGmYDB^P=e6MgziN5vq3S=G3s3Pm#; z)>~tevYT~sQc>iRQ?UQ))6CF)}?TF^?vZLMK_02m*466_-3=7?NfMl%rxR% zsaxye6JzCkTfuE+mUVLMRP7&zq?NzrmWY9``1D2~0A&v64Hi$Z9-n%r`#dD@D#&+< zh9{j#`C@h9)X)qQ=j5)pbGrwYheCILk@PLI*=F=KcPy!2&OEo4V7b+5>r7X7&Xc^F z5-jMgNtbFp;bM0Jjr#2gp}%}!D_2&Z+P_P!VVkh!z{-@iPdTEps$hZ9ifZt#pH-9+ z3;DArUm4y=Nh^{{gjCD&9ILB|~XDsYVrU-OsQ8B;Y4#!~e0lO75x$zl>twB-v+Ct~zNI zD$;G|l-sN4RHh#D>bLE^jYaDs(;#Gxp5Jz;U1)8x(?v_G`D z7vzE+Yc@&#YF-7&XDgX?Ea$Fg631v9AZGrh;xXgHzY#bW5}By+LD1O5q$m5){=dS6 zZ~f&UrN6T-*)PHs?E#m^V&(MHx5q>&&mH(m^5-=@jfje6Y(Vcq+c&TE%3t@nHoCOi zyB`RDQQuPO%vQ+&5@jKHm+^m0%*i2wq6TdLnADI5sV8dK8n1YrRyj!$N=(JVGTY2; z)cWjRjL3t?*UMpbYT}|XZKsPr>6z#Mrn~cj&SN3qyWE?+dtBp_T%vJEPrg+8oSptfsCK4!pcVYBt)e| zn&FdR?@{|JXSL378E^m3`?vm+N7PG$`#v&kFl4;_{fR#Ac}6qwh{3XXXK^P7rQd70 zGn(7)P4sZDN=8_cgX?NeA_!4J5VZ9F7X>n2q1<48`ojOq zUwxRHpTE08hUNd1FkJYL+CFc5GWdN`QsmTW;(gw4G;_nk^ol#b+i$OBqK)uQGqM-# zUb|{QGI`QzhJ%zu(u@CCv8!p2rT2`CuqhE#2rWhlrzJRU!cm{_%0$01YNJw?xO^8Z zQBU%mHdYF)0;lbtNsZXFf8g14UbUiB`&TFMuTj;1T-uGtpI(}qne__n*S{K$Z%(qO zRNhbO2|I!AM-B;! zzh1wPAn-Bd(gOaw*pH*yOpFkwa-B?$wvdf3y6rsN|u(Q)y;mVyE`| zX$EoW1uro#$OSM-mS+=GC;WxaMT#Wga0wee=QQe8vlZAgs#3i|f zr1F8b3lcng*pn*l=i;Eyi?N`$rBM55|B&!kems+*`8@&dycsS7&$fKw9JRmKtJ7sW zw{Ubv-wXy9(!OlfJTQHSIYLLO$=1k-n?#c|~_9W8%d$5s2=)5Tp%v#!rH z7w;_k>=>rj^jW?CrS1J^!y9F>o2x664|fWjuXNel;|^2+F&IndKM@!-a5I1S1y-XB zbabl#-qu`u8MQ^S{uL#Qeg#el5`+_9cc$A!b}a|S?PW5jUvH6+k}gaiCwvmjNT4vs z5PaoeI7KC1Vld{Xu~+#*vk>k(Pv)yWWK7o*a@1~fTLj+6}EU8I2F*pbGt0(#u zEpzXjX#|iN&~rj1Y~d6X=kv2A3n;;z)0A)>aRFfhP83e1WkYlO_1u)X>F1`xhyp9M zEyct*$>B9)6>9B1xLac%8QGGHTq49iOm6f74lh?MR>zWxuLaQ;@1VI6#D3qs zdGBGWc8nU`R!2_dGhu9JO;1vd^eZ(rdS)@!*-|4-WZJ=p6^0=sd#*KHvsyEjNJ0$8 z3-W9jCE@DCHJn&p(QUD)D_7mY2Z=^qc|cde^LZH&K}v^#r@UPjlA4sI=gCo)MY>2Lrl#-(M6$3;G9|5=t#FcYdV4UQ!*T>7N(H&;Z zO(r~tMPaft!aws)s{-MNa0G{OBl>I5cba#B6GKhB3og#KPJ2S(y8ZllLZiXFlPiS! zva*MSXd39<&w;^!JcA^;MkaNqk)(M}fPGo)@s{>S1)sVe$=1w(0s5(%RGVoa(V}1Z`WZ7=msUQFd^4-^s28haPfP(>80A?Pno@gyF zgn)b-oA2P+Ox4F^0teO2@Hw^LBbIX&&>Sihpg}weLO}SV$IfL*z=&K=U%&YT!BkIO z-Ibu4Pu4GwwJDx)R^Wn#3?D0-#zeVi5J+S&1Q7j-$#;K&0Y(WBP3q-10vri!PJuA> z^=lYqj{`*Mc&we{%5PbG?y{EkX<{)?u$b^GSv~15TUF*2!e847xu!J_tW4^^o(#zR zMa;~KE6>dhaUt%Ct-9VnpJo!e(xke-wEEEe{ezh`;&nYc)mu}M>eL5&b<>@ zem`9*WVa&YeW~Hp)i5mwXR7Mi@KXK}BIgYuSwb2W^J_35;9m_{w{92GRK5Tpz+6J} zvJ`uGt$E=<7)Zp)zDJoK;B*cHuv#S8#%H>@H@&_U*i3M|1L!?DJUmUWBq8ywx5|IS zC*YFbD;U77fk@@d=bile^^e*#SRZg3xzRI=cP?2F-wgK3YZ@XO-S# zc@9*7OFs*j54aAufI!Fldm8~qE&>rfTz#7{o}^7z!-+8^mH zPBkaO>*7U3uw6P1eLI`wf-KD9r-dB~*WBCT_ym#x8SVLpn<1C89H<@uUItVs79UBg zsBYT=oVeYTdmeBj6(uF6XCTo%cWk!3J#AFY>=^(A1z{|8`y$A>z&JupmW~sIq<@7F z+Q1__^QIrvRRo6(7mSV=Ai#Rm>7-s^?7Kh9rEEu5mm)77S=$Il+YaxY>zWS<5h#uX zdK!v-!s#~!1;1yK$6wzH<|yoc0VMX~KRKMI8#+={&GJv2>SWx%gHj7IAz(09eE$5? zXaQjS;QYw^NVB!I^)pzav$C;Q{4ls=Lz_vXicf?>4@dTM)xP_ee4G}$bqmKFopZI^ z-wxMkJ8>TmOA+DGzmc`SzGAYc`}5M`@2dNn-X>CyJH7u_DA3nxE{?w5sWCF;yWbQZ zmU#Lg(@)LHQcQ0F0*xAgAtlBf>i~%&YQi|wqmYsKUJ2Gb>2Kbg_5&zg^Yv>4@N7`^ zfELEM*;PI{gpUNT8I*_{iIzWJUd7{j&6}ifD9%~Xem3~|b4$!!U}%M^gi3@*0bVkL zLqnQ)+UcGS0(Ti7^~Wj)2I(p;{xwxLk(e0~Kz7mf&*-=jC@}$^8iIT7o~A@1dZ4dw zB%h_Wf^;nnbft}qVY<`pE3}Md#0x2dJ;3q?1ewcy+`9HIWleM=cdqF-MUcR36 z^5sjIly(-`GM?0V_s0^%GMM*?KDzRwsR@KWcn{IT*eW=otMJ9O8c_w>Lpi^-xp+YdyaV` zCJYws;N6Bfy5oj;k|#^ORos1j%xKM~%1U+C|1W>jXHn`m$AZev z&W0D%m<|7GJ>KY#<4`Q_J- zC4l~qXV(hGlu{f%4BU1fGdOO@0K71_SwSWOB$h|7j&{*k0;>uyIXMbfzRhk#pSQPv zn=NMd&{y`>$oQ3>StNlzMNttEi;vPcUWA3&p&(U%`Qmgidf*LOV{m%xONQrpzQXhN zb`rcq+gml9pB6?V49E?f>MvDQ2=z7ISY0qXt_E}#PAo9gY0XeoynDCL(bB-gjvkF+ zi-O|5U5~-v$NZ7&EdQA^a3R%=bEeRDP?!aV8Ya#PQYsia_xHnsWDwIBT0V;&DE{%3 zozT+`>+sD#DiZyI*)vAi)d@?nZcZWWsBa<~8t}=%>K$arQ;7`BgYxboxi`9ONag8p*3=Y2sJ32aq;*XS`%gwagTTW9(R?U(1Eok7@4{KUs zwW@8KW;as0McM z66knt$2|=$(IGc!J_lnpT-P`qX?D~R4Ac0S`|&_dvCSgCyRJ?$y#=Nu_XdeYi7(@4 z8|R$}-jseXjR|dv+Wwq2!Y^yO{r9ixJa-Z-J)8npuyPUb+`F;VJ*6ej{(fKFc4LCA zJK))~l+&Xqm3D7`qO+QGXfE=>AYj5tPKyb=^Q%TiYz(h;V&h?>h4vA52yROGbEOj> zItcp!S~GZ9W!K3O7c*cqc7g8c7o$9o(Dw$O>O>VL%+pK;dlhQPn1lsSobZbKJtGEG z^JW7F2pec?=A*~+51aG4@tnAZ!!jW&W8zlTLwqW7Q6y7EbR<=H6Z|G%R)On=0YF^{ zsxqV|I3gs4|4e_ZUC=2UyZqr?;eIvGT+q1zU&E=R=>7iVh%zL%!rm3-O5S*OzD=yk;L?Yp z=B}jvcv_5~Z^@nJkz=+Nd7soXPP2090P>%aoX8J%zf zQ4qO^2W|C>68N|nQOBGVc9=dEojF{}wo+Ls=A_*!wH2Y)Y(sjwx8AOI;!8vFgY{SF zMiUaQCdhyR-8}U`SND)8>I{1HOu00;7>Sn06Gk~&W)#i}G9(RDv`*Q$)MB1e!#=ms zAl3;;mU0dTW}9p6+BZqIz+OI9VHj91Jkav~)hYj)`jL;mWL|@I_QW!Ooomy%=Kt;h zk=U(LAxjvKhwF#YR#2m%OZjG{-RgAnM&fhw-b>_1nOT{cwXh#U$ANDK4*`yFl*vrD zUlWPatJH}~>{61FHmbJKW!Dc;Q7xd93b=}OUxG;u95XQk>f}&VREwT%-n;wJm7b!n zfzP+FSsp$q`4!DK@+ORo_?;I`&;Y=|zJNZ^@6L2r7;|)sguThs;^%g~98lsxgJ!hScsxpkv;3j8fZ9VcfRQN8}5$tDG(=NMz1)J6= z9AmMW#X}2`M*aIm8uh94qN1n~y4w8nk9VO#!cO-0QnlWHJ6ZprO;4qdsCBWxuGHz$ zfon&cZ2$R1RlfctQN#7gFcr>Z6vSW#9PjKnxslRo#v3WgI9Y6vr_#zB87sohUT6AB z)ZQIp{1K$W|Hv8BdL=3wVy?N4yly0*<1i8(6$J?lUfh_uqs?8!JlbaB@~JDocTFiA z$yUplFAdA&@Yo@=|B>R=rWEop9DD(mNS-VvuwCMX>yt@K6^FmPROdFJ{e6BYH>{J%hVuQ{|1V6X%?8H{q9!1_WUhtN`WYMBQugfY+3F z$l=;FAhD}V)jtVx{EQ%RYOukYVRi7rGuFl zeoucODJ>X`waq@WT=NAPMK&M&v2h*e7 zjkemF-s6gAH=@;rMi!kz8M`09mOdw`Ic0+FYPFuXTdmMa7r~yeABuP1bgWjX6}s82 zJT>a}VyI~LQkFzqS;l~)_TaAZi8AV#qg&u|!stT%*<&-!tF2eyWs;tm$#}^?#=(KF z^Gelw#wS=hFWmE!u80kfV&gypTf*9=+-XWU<=jgTHT&8ae4buFWMVY-SZi=bX~)!_ z;Y&jmvhDd*{R1{VS)vDC7?b=TRS$uut2r8W!~L>uZo&jS<)lf4V41}{Hw zQ%N&Lx_Y$Q&S3e9A@ za>`W;g`Fjey_OamTVv|un|?8#Jgd1kP$J6z=Y_Z55=g1e-#ys&EClX%M2H4r%yDirUM^1;r_F$?ptjw@KCG57`ojJGB!5zP=ul3JLe3`4JkuaGLjijzBerh5dJLaQzvcg=2j+^0t1EQ;~xm$wJg*m(lo`}F$g8xC=V!b zbd=QZ(_fK|93>ng2~+2GB(^U&xkB@SgYtL54;sKAGIMi7D*m!7XM0Xg-yyzr`*t*Z z-Gk35g*uM@tbLG;zLqXS&~f$wCnsl~${l;h2O3f_%FCvwS>v zc8px6p(oNRObZ7rN;rfZOT{yJ$G7w+1surCx!@I4(#l0v>88AKY~EW4rO1c0!R1~9ovGzEV}&&M#i03Z|Gq` zk~||6NDh=eSk}W?#lYz@(;PjQNCp}h`8;#TrO?C7&w{>=7WlgbxzPa=0q!~vc23Te z8)u`y{y>Vr@Bp?d`Sgbp{>q?n!SkJ!`2mDV)Dn55IMKnkI^lJ=WNP65)B#m#t4Sv%(eAZS;lXWlO!k`3XTXD@R1! zN}{8q|Mru?NF1pyuaj5OQ&Z26PZ}z`dOHx5{WIkd-7AhrD>&z3H|N1*skc&ima6Nhsp!pIBNCNTzS9Rtu|xJ=@mh7&fn@SdI? zZn*g?y?gooeIpW?;gDMKSN`5TMm~#=R!C6K08x(kz=7M+(%|J!j*hk$SSIFG8;`=l zADa=`zJ1F1X6m1vr4gMOOo?egp`D zT<@|1Je7yOC;QZgSUO`5dQ@!K183{j0)HJH8!Ig-N#%^|?bWU&3!myN=0!{oPC8Jq zzRk?kYx29#!pmz9az4;-TKr85!t0$>O0msI69^HLrArzoRo}7A{Y(QA08?Y|G5~?j zqwI;Z(jQELA_|`?`RtO)e@`C4;%(XRM)OJ7WV_;Q<4NvBdp1hJH#_XwH@Wy5;yB`U z!_Yj|{K`~68(zD$xm4a=;uu`3T0_@Ow^|>O(ud{>OO)lstJhf@w1nT6*7_~uuT!8)sBGC5 zH@%>@YXTJuFa+Kc(PX2MQ;^4@`COV0!*Pgkz_@pB&z?O8=`M+%ri9O{+icA#AZ~dj zSr9$ZXu8Z`#hIF#az(P^iXJ=m5^n9dW!SBBxfUwXrl=_?!S3M%g>T}QDx{=1_C4hV zqKhLCM6`faO-$J18(@#Tj;?0VNQ8F#cIK6oxHKou{c5c*2a(s=>{91(m#+U4a#5Pq8`U;T{9eXgGOj!zJ8Hi|S1UlCN(lCB6#n!##a z+@Ur!>eH2nrA^R1ps#8WoU8j_+JSXT8)#;zFbF0lq_Xbue3p-e7r?_p);k2S_*E|& zUERG0Dr>yDy+uWBL(GkgUR5u=`e98;`@QSVdp22~cjfF0ww{-qA~=oW&N_d7dE*PL zfGn-8kt=c(D+h?qUM83Oi_8n~RyVkWUr6WVc1!WTw4wf{##;Gco%Z$}n+VFgf5=mC$wutqFC=}Vk00L^ieT(5~Nvx0P|M8~zTi*=O zpcNN4L&LNY$=#B_?Tp@@bN;#dfr@eHcdlWu7)u+Dyx317-kO^Zrjdk%hhH42{rWnF zFK2qGaltrD!B_iw{0%-Nf7I30VO5-pcWIK<+BTT#`t!%ciL*8c3G%hSbjHt2+$(L3 z%PNdJzxDRr+^Y{;V;_08t5Nrx29CkYxr@>5CbT?1duh!g=Y1NQ-1d@E5e>t;Y#2mt z*hqD|txW(_H(YK);tnEL|LQ*Ur2J4>8GIK2YZ^3PWMJIP+?qc|A`V1>o|`uOMq(TEmN;Iv z1;TV=lkAc_(Z?gu{NNmYF1QBFcC1&F9rw)@{cyS#Ix}vpNebWB@Gt>P8%WppuWRA| z_XP4K)8F?DL~FZtd;$-V7r-()C``IORJ?yM(_2M|b=>cTf@<-T2O@2L^R5zttnSFg zK@0^s;vW6uoso^77T}|P8<<50l`@elUHDm{1VnitFd$FBu{4|F;cnNN#>Q6K!DcF{ z(%l;u<}lLN>1mMMg3i^;(-S`1=55(k&>q9Uxr z!4CfRJ;E6zbSg$h4F!dPR8lD*y)`dR%wKZp>goz?=Q^)-tD`2J=K(%GP z$4s)Qh#y|^fBvX>M*|XoX|q9sVjIX7MD8r%7i~%k)jnqu=7Ld3t+Oh`ud~ZE9vNApXF0`D4MdGLJD) z{OYCyKM@hV@k3sD)X~(A-Sh|H;a_iZEvc|@rRG#TAs{48{+{VN&;pd3#7zvY@OZ+d z98uJ1@*K+9{R^#{`68Rb0|3eU`mPc5wLK_UZ3p#_9p3K+w8xX2acWD@#L&7e=O(^m ze!e{}tcC{Toc)3gF~{Rj$%BF%ZejIwA5+Ri;;?+Gd$Sp?bW2D|vU1pd~aVL^7g<+fzdy5lwc8eS5EGGtbl&eAZq_*`tQi~ z#QvOm0&@yxPqhMzt4VTC83cIv_#()&p)k;}cUqqQi->q+2O!un(RH7fXp-DzgCEBb z*^ONXxKT-oV4jMpodEELG0tP?$R&pu8~+z=$APQR z&Vx`5pL)41taWddEo?up<2SWwGe~{4XQ>lpB49Gh$cSVWoT{@l%Qe*0Oze#1(l9aN zVP{7~13Y-K3=Q(yaanxD=bzFDQL= zM@sx_FG_4N29YTb!wUSPEj~3 zAQDVXa+C*G2jRrnPw=j|vXSaf9gko?Kq>;-kj4=k2yJnwz!Zi0;G@4c_hz)_)b(fU zGQt)Bp<+oAx9{G)3!i%wEN9XeAfaJs4tT?Ge!#FHjI&l_658aH8 z@ycVuzKUBRG6g@R5QFrew(~XO`@&cIFxHU2=H-~n!}_K*AK~+F)e4&|rkqNJ zPn`lJDRhJMe4^ML6dZ&^;j~HcC3*0mAEUoiZ@;W8&b+(LZ?3_+A?C6*aJK+DHWHm- z?kx<5lhfdjYvYxel8tAr%w|NcqT?a%JZv&npe_oSjf{#aa+uLBYP&^@;61`DNCY4X zrVy|c_?{ZbjioI58R-aeJ3N%>OoLZ$`WT)MYsem` z{q^P;yvr5eDqZEaH2}W54vC)AguWG~V?cOI($YrY7Lh{Af4;r_A<5w)b3ewIbOLs7VPEB6g?5~TmkaH>yZaU<4cHC1+$AC!h7OC8q|*Ti z9Q&1Vn0ObuFo;879<;f+DUl?XAS1XsoQTe^)NO;m)C!Z_(U$Z=H8U)2z}Ohz-@kt! zqbZCf&?5va=HXZi!7%n&=x?yH_w3$XRauEj%b{6Tf@VI2vlUM~R#;(iF$Ug;M5W>c z(;5xFGCh-4qROcE9UVCbfDe=3mpBX<7<@&5vZ*O435leH1RcVkP|va1Ip6t-;HplH z`dPi>&Ob|NU01}BL%h10Q@#9D2s^%nl5sV1gLM7mYs`d28tn9oKF%M+~a?e13 zproRbt&hN}NKH-6&As$qY5mJl^WQny&0KB4C=Fy@6Jo94Rgf&$qHi_ zP#W^`xX}$JC*L?3jNE}Ulz!M!wuVHuMn(enGWUab4Rwu!jt)Gc%M7)+qnf2k6#)FC zYe?^%^V~@^Z@lwE!i%UVG((M$`E$!-25D?$v}4B(?3i;nqj3Fsg3F7>nM1v((xn`q z{wJrGcb_b_0L5eEaGfrN%x&J_zE$Bm4(c$nAh-aHC1%D0#9=%3a5leub!=i;!D0k95C?o=Jzj#DG3EZ75d;%0Ye(B#5tXl}06`rN-B|MQ59eYlA~-SwGpBTpk1|&5j;nPOYew&MPMC#Sunjc zRyv`(*{J2Gb(Pz>O@)eZLi7^V!ps^phwDp$0F#~x&Wg*($l!P4&eYv-KnXZ?>Qrk> z3yhbdf%Sxu`Sj-A#gT`^T6g8>>(V(JqD5x8!Z-_xSyC7B;AJ0^J9-S7#coG{4ESPs` z%7_qF%HVy_ezod*;=7EG5jWsDl7MkII-3QKcaFMuT|;W()QW6s-c2F`a{~i53c_({ z#H+4W3D*(B*%G%6rwY>-B4(7x(gQ8g$X9`hf#;%g8yA@s8Wup~@^l28}s0|lc_<0wG`7DHyYE)VD)p})FI3LBwo9B9A z?T4;vCl-;Ci>9jngRx)owQ5v?&jt^op}!oq;TMkIg7VGN$EUd#t&_byZ^g}k041t( z^;WOy{vY<WXppRolpWe-6q1=yDnfR)GD?XOvXf+!nN3Lw86le_ zdu3+3pKs3Z?|0qTeLt@2pX>hb{yfe<&WyMBINrx`yk5^Gw8SvXja|>hfzvT`za}Qk zcg3|T>``A$E|>Hw{vz`&D^nuSsqF0W1a=ejW{Ay*y0q(RVe6ya@m9ty@7ve!sIn-} zKGl@R!)w>3E8i}12S^=>a9hSC>p_d!(_L1UPhaR34Drnon6Kqk9`4R<03d4$z)N~Pqq_SK*M zzOV_U*gRd2*l7`DTxn^U%2?Z(UBoXz|bO0?&mMF zs0|DbQ4sqs^qE#Y{X$zljaF)=TP}gstV`{5&E?G|ueG?s%t{toE{^Zudgmp4Hs* z(c)cH_|GtwGT<3gyB2G#8eazV;-_+@=EESEux$Is+)4f--Gg=v+ z1hqt+rMOT~s&X=-tf$V=2)EC(@j`~XyF1;ZPjE~HKNpt3Vw$t{`oa`f!~^ywY>xAL z{0K}3m0l_qtYU3ft#P;Re31;$o~)R?6o0UBl#8ygk z?;MNgovoio-546>*!%{@#&q=b%-bt-(vJo(49+g)r?(XFsC2^`m?q^hht zKkTk-*{QzuR+GkVEO{XGp=<}3;)uRqfb@)d`dXt_*gdbjjOz7kb>93gTFCy-wc(yr zWCJu@K}6BY$_kvaQpPUp^9Tr-gDh8+dTQnSjrn)(9Ji0J?A$lI^A(z-SlZ_>FJs-p zUIsF1z`SsMKo~^bkago|fOT8i^NMMaaOp5vPxlC^VMC@7Nnq@p7lCA~tJ zT?Gch_Bz$rEEzkn8`me9Q$d(opT#lE?S3Khvy5*Yo*yHfJ$XjP+UyLv^LqZ)yD!}C z!!Eo*ZwjLkeKI9SRGlyzY+gFr5a|*Zb+V&kAnyW#ecR zaX)3EN&kT3bRVwv@+=-lBcFnNL!6RWk)J;1*oRc7^|joONboXe*Uju;`tPefRc?hA z1XwnDd6u4uesos!H^XGaM=K=HB<@EAP}gZgiBa89Lyw3Vc>v?sG!*3LV^f9Q9t(5f zN?ccBwQz0VR_azV64??#5@w#p8Qow3y`xJ!Hv6t)N>_9Y7ZVUrN@}XyKsky;{u|{` zKu$Frl@L6c({3iLd2bPfH(!@Zpql_@5+)Ddx}BZ>?wvoaL3^oy`pFTC#;!igy!sP6 zrt7pCd;A6W<)iHLU})(iVXY9u0QFw zueWOio?SX3V5pjde$W$j;S@Ek+4}mO?c_Yud%@1bUJ?znA&Wb>-Q6Jk-il+b1cHTX zJFR&HoGr7G72E#w*~-<+=4$;R13oiO;>m4f>9Yk0Nua09@~T8l`Q}mraS}Jfg8~Au z$|9cfLK2xXR#p!tCsS;5y(Azi@4!xTI|rzBGe2brMC9`Ae-V+&Owg;u%ve^6PHrb7 zLki~{U{Y*ihCGEro+OrqP#g6AxyhP}C~>A=x9VS?1aLfqE(|~jh|yKyQ^_}UNCo9c zxS;<1p`ZlnR)Vp}wF&8~h_DqJ+g~cVt$(59-t@sH8-0*ee)NCJ08Egm$`f)S$7N*^ zwaDT#fGOj70|2mz(2a_O-`id1YnmrW3ZdIkzI7=u=it zqxa$fgbLhQn9`yXlA4+dAW3|`4oZEZ_mFN-{_4bo`}l(o9@vO?qRWG6)C#mHrqn(3 z^e3xD_4crNddk0xE(Q762*m=NfOysVu)$dus-x&=v0lE4sbbIc1@SFqLbgN+=CLI~ zHDAAM+wDzLn5uB~%ky^~Qt6qd{hmg)s4J&XSpu5Gz8j>ysxM!FLjRqbdd+kLsdhmU z3S<8hW@D?m`THud$5ro3jBC(uIt2qm2qfM@0A^7 z$abUighd3EZt;kjemq*S-PpVX?t~d0N5I$kNq)Sen568GDH{{j0nAqn8FdEbK4?MR z?!zs@FvkvbBE5o5SUVF&IUwx66gF1GZW+y=Y~`5xaNrwYlH@EDd#lxpYr~lij%c~| zlfM9&)jOg=2m(<9F>;S}#ou}@!t|~D@ouCA8fz9EL(AgK{!n4#r|;j3iH2aTtYG70 zX4`VW>xf3+nxD+7Jwp6|%3`As*KnLy&x0tw{(Pi>WBnZP#C z$y4q`L6Q7@t!00WNenfpGrmey=MBcB-y%v@hQDRyt#ycyt&g98f_NonBmRSHI4 z3NkXX?b`{_dbaPU{{f=tn=o{jrKO|$H8O(yBn@@-{?ZdXA*b7S|F*n`UYw3jMBkCK z2@0s)u%%NLErinoCtA9|aiW~29@SS8a-(1|F)^*d%Vu{l=xci?L2HHE#n5ygHN4CY zK2pfAo9F_o_=`~nY#QBZGw_@u#a{_al2>`xnYc%vqU_GNPSJGt0DL1jpTX7grEg_o z;L=n5gG&ZW7b+P8HJ~^F^-@>BqMx0WA!q$gopD&fzZtGN4u=?bwe7q;XD!(A5VQPF zX^5E?Kf2||d>6oL%~(@x;0{-smODBr>Y%wETR!Vxa52j{8tkL5nNrm|PZBzQeC$kd z?rBZ8S?9A5YwIxAY78e$>{yoig7uh+MeekTpmJ*-NsuN|*3srpOstfnkLpp;kltcK z@nk5s?5SoePqlFC?w2o)B+;2P(bqRHm~zMeh;+7<(dB+BHCrugKDJ%dQJm3iOUf>i z!IC=Ve+JW#vWxWziygo|ph;j;Lbdzm(h#aZFKX6vWI)U>zy@z2=0PQ94vnWRYDa4K zQwkoN1$013MepWfiaip6< zasTHgVD{oL&)~5CZ~=;o5j~G}`kDlTF&PaFyOcxLg@e;GGp2y9($dyYRZx-6Yi8Zt z;a34u$_W-BzXGZ8!fCMm(O(OZylT``NnLMpQ2*7(g!O2Y|2@$1|Nq4Q<_@_1<5^}) zGWyfv7&b|@yZmoU(OA<2421hdZ&I?(Ecox7(OSxY18B)sD{2lbm+X z&R|k#-H47V2~YW@@Lx=<&n7JfcC9}L;An* zH54G|V7_b|Hp|(3_lbL*GU%@c>F>S0x!T={gOI#K_GeLc_H~k(yb179+5ET|&KDM1 z>|W3R_^E_8C2aUs7WLn`bPy|1SJ$PP!1duM7~4Fer5Os5Q7iSQQ!5!kFnvaPU38uA znC$wIwKV}Kp=EDpx4Ye4BV|ogwXoO|&9?LGGY9z#--V!#(O?;DN+ottrA}*n3O8TAR zih2C};-db+)9c?O1s=J{y|g^)AFEC;a__RqX$lKFJL8SOA2;_?duho$%~Ctm@78a8 zcY60D!BRq;P9huib9w=Kf_CD2K2drknj3L4&OXRn_B3N<9Wy00@H%`{)ya9~&!_cY zaodSe*gTr{%aV#dq?)v7V0|=&k*BF&d)-Myerlfx)q@(=Q*L@gB9^;~=qiSt;l~@IO`CRBP>&rj4q2GPK zmp^z!T#QEhSG|C_`J=eF-R&ytSKl!lFTtauGis-I+7d7F+VTaOBU;{M$luALs#`X>2k&NET-PgG zs9+0?cQ&Oc^9ON9TH8d|k0kiPAT}bx%SU$a%%49-*PGYBSnBWX9VfdN{~Q%j{ZqH` zWJ*fan<6~D$o(qH{6DS#*vfre^x-ns^g3k#>cp2`vFpcfs+E)9T6B5+Cl+_8^ZcnX zgDQXANxJ*1#QG1Bgdw~4*_Y0*5;nH-tAL0_%-kzmzb(TO~-m53du2_ zfWy-@Ch=rjHnGw4ZiEBPoR?(N+e>@wjvet0Pcy~#^=l(J7A~ALgD>UgMMrPQyZw%5 zo|~vXUE=ZYkI1^lDRYDT-}3a=n#GO4$bs-qi(^n#^oKkDbL{k)GZrAY*w~1!zk8)5 zFCwMy4tv(g*e7VNSA{N${Z>uTKefNNX2VS#Y> z2zM&&gECkG51Rz9uNO8U*FW=jdH@(Qo=1oi9`P=e12gDW4Arm&ct~~tE0~_m8QZdJ|pPiqN zzLNSogP=Q*P;$$xzGTIlH9iik6x+nI-n*HZRxQ*%v~q}7x3haMZ2Q~8nfM3S*q4e5 zE=kFbHoJQc`9I2BOYCTuuEZxK#I7nQsLpf~kL4*`*{0xIE1;9# zSXTC+st?K#%e)R@V;3U{{sFboh%Dx~Z?7O|xt&w%28b@O0W2QhQ-(9Qot+;D-+!>W z4{;u-4G((2a4qakJYk3JP*l_0Y>qxG6km!owD>FAic#xKbgfKgTtAPt5Kt&Mb_Q2T z0a~<;vD7T@x;7Az>3>BVEon#vWAtSpr9Lo84x~#)~B~? zZqPUb-s-Fh7J$?6FUves$Vm8xtkvMi{y3a>V@PC$RoAgE#6^;9J_Sm9st!Bo>vVF* zSjM41Jb3Y(@ewxSC$pJT1DJHFD+Z?VCjq4MCd0{&0pyb`|C>(IuILl%>P9|o(^OS` z1M*I`Ipy~4bFsPG*^PZuam8vL+2nq48Y9Nq`K7hJmUB zK?p-D3xvZwOw(N3@4TnlLnoi|N<9e)r+&y@_@QMEM~5)8waJRL&}Yw1kb&zG*J?)N z;g3xmIAU~i5Hb?v3R~9oUi4o8)?%ET_iG16W{LhP5-KKBwDdr=ct01vZg8F&CUOYr zFdrJ-Ma0CSWY(skxZ{)A_x{GW;jI*AAk_i>PisbP4Y51;Z&6OmlWbLv*sIe>Wtn~S z*nTPD1??q0Jy#U1W9@cMljXamipsrA+vjxA3xK8bK}d8tZ=bcdpMSKoBB4Y;JNHj; ziR7u3h-XJeFb26iKy-sCHS-`JV<4eD>H-!Ob1K%}!2u{O8v3ta|Kiii(O}Hpcj(X% zq^Qi;a7IN5aJ&J_tun9!h0T0}5)G|f@ygso<-s5I^+!z9y*y*!h2Zwj+rq+SxI}8_ zP4Oc`k<56w@v9`;-*)p7E0_I>Ni8waQ(VY^r>8$lmgjV}*kwf?LBqTGo0nPR&IWwf zbGa8#e}}V%+y>3vzPLUt{mz1L&(i ze~d*@LqkLTMF}){xY<{id2a-aeNj#?%mFv0|M-2G4 z6HJo!e-NoGVQ=w5pAz&Ge8B3_%P}3&u}c|F2o$|>VHl`VJkPQ6X5#bLS4&qpIq$rmm{0QEsvRA4$Hbii9Vcuty_lzQhXVoEdxk(aCC%Ip13$bYwb&yc%IqQ>=D|JF<;f! zb+^JGMSbbh&b8V12*$Vt{NW1~Ai;8BGdvYE+R9{iy4YId}#S%DMPt3lw1l(cnN@^H_(KNH%Lr~eI z97H(Yy7OGOdq2Bkp&aPz(1yT-?dyr0-Wm6l#cLWN-kguH5K z0@BvpdEfcD&$483*u?m=U>y_BN)<@M1qB6mqb&=Shr@hb@@?(xTzfnR67nZ7HL*F* z94T9z%)M(!5S0D>uL%g$q7~(NtKr5=YEM%?Sg-?MfByWrH@nO4kDiZba9+VHz3fU* zumg3$iuTjcFC>r3z@v5mUhNdBhf(ReEG%`4F)Ci2hl3@?yLhy6?#=p-XjiTRSoc~2 zlI!7*l>lAZQLJM#=jFXWkF`v?pif{q^#^@Em%kB{MPEET?7`>5^k&{RBNVa6Q*N@& zWDPQYxDC(L$pb)fgi(Vdy3{{Xi@a%toENPVqc5qco#abbcUb%ciEmt_gTp`#&7dbl z!f7SNqVRU89ax)9^W)Qa_eJn0=q&#EQ)#+Ii+{@ebc>xrTx?MJqB&L#cSyUoFMYVZ zKSRM5g3EEt(P8_v#DsPv_im9Dz;75$Dny3sZd;kuAR z^y<^ckAUrLwM~W;*lAifE3faA_r}&%mxZJ zX(x}{MKEc=h%GWR?kenN|JIf>#+S*AmBJOWYg)O3iiO`SMh~U8w12$ucVf+Y@|Oe> z0($YH3p=YNHJP<#eYL&^`&XFa#g|YeP0SgE%?hh&Y5fI}Bb0w_swyr1_*sz1DR)X3 z*GwF7`t!m2M!_p>m(Ur78M9VDb)U(wc1Hr(8BQ)HrW+Ds|U1!B!c_Q}bj-Is5(=AZY1&@(V>iJ4g4 z!L@v<3j*7PmAkN_)RgeD!?btuWNd42e7*$7eqK}BS#PP<8!H!f5c%>B4u8^e=Ken0 zHt47aBxl|AA4Pafn4lN(!Y`AaCT{wQx`~OFm6h;HT3npr+hhw?`>xI>US6j%t3b?% zm!^RN7CbVkq9SO|QEDR;bN_R2ThNw1y;c44WkU@X-@38Gp09%wr2$LJ8X(!Po1^2ayYi#!Do#!` zMw({2yd~qfVcx4e_<)|wrGx}!LHy>lq$I>ANfk{_^}((yHSWV8@#_5O>F>`mC?|K= zMF{UP5JEcrS*WS#57#{y{?XW|shgRT6BMeU453g?v_Vf3=;ett58~py`^M&%)}*yz z{JzrO?J#j~Vr*sLl`#|;&ZFriu&AirvUMxUCv+85c_#8rPGVO+IKYwLAi(+@*K10PUOSO4HNU#+34Jba~#9uZ+H zTDbE1(*=BuQj3SUL{+z);<_fSC%Y0|AJ3PTpuSeTX(b>}zAj7@uABjt!hX$F*3oj& z{)@2fwS`SUz8-q|^AoTtMPs_edgwVIVXXg`dX>@kTo2lLfiW*oJP$Iz^oUJnP;0=Q zAGI&C(OHO>adP4nmHT9HL{ym>XVsB`ehL7Hvs6q9WLS&ZiVgYPGElv3vuRVwee#H87ls zp`kQbTk2{>#Ic>K}XNbp}o~epKuFBYRFz8aztgXyN^>KY7Ll8uHqdTku7!~iE zJpcs&aUtDaRBJCK?+sT5CdE@Dpu?gwpLDd9H(j9psrY5Z%PJ~ox0Vh}6N5l0iBF@> z(3=yd$M^>Ypn6)(Ypp|Y=dqkB+EpoQ5Z7S3LJyvZ#WMX{yDy#G7+3)sse?V^@D79;T7qt~2}NC=2>y;{aA-SEX%79m|3-RV2vZ zh5lyd^ewchXxlpno1oS{z~f+)b0R-6AI{Qmn#bx$r~Z_UIBFXAe&%{_?rOI*XXj0` z=KQ=OKK=`zmTqT8A7P48IjS{ioXRUx_Sqk2JW%A}KR({w&ZjuCa|(;|6*aXTa)6RR zK6I;RY=DBZz}ESU9X}>bH!U9pPDrwBEGz_PC-7{RyVvG)yGQn6+boe1EkrvGb2Z!X zWj7`*^=ReMwpn+SNj*uJ*l-A0saHt2TZ6$fQ?UbpG#(nT1hEA;(l!MMv%fZ~+l1r+ z>FKey+35OQrG2ONo43zxQ)7kX=~IZXjLLNT7r-lFRanRPr8zZNz2od3TYt`p^t%xnRYHRN!^V;6|(?x12OrK zEx=uzMUN`ZfPMFeQM^TPY61(sqH0O6HH=`UFZAaa&l_AyZy>~3RmWG zVD|+EX-;9G0m}b5_A5%LfP)n{@6Gqj$|1L&4znUY^=U4s4H0OSY1fgSJF(u-xzal_ zQh?1?ey=R-QPx)N{5$=_)zj@R$;ZYW%3p$E{o)zkIfv!RlBoqJPfnvrgI*s)EC3~| zvmfK4*aV#?@bsQP7y7=gWJ9%$F3PQ$^Qb{!2vECQZ#mf0>)dJY;ixsIIY`w(%GSZY ztYO8hW4;l9*B-Vnh}Mn!cesvRC8m9Gh3m;@bo)Jq{;Bi-(Mj9!tV zaeWDD=^w0iyaXk%e*bi)yxYg`;pBBUHTpCy-+a$4MhM5*dW5QQ-E#O z%v~MtyRbb0;Fx1v+l72b7ihZFJ@rGrVd5*AD0=T)=H-eFly5OpgfN^=9`9zizlPcO zVQy~pzEf|w1MgBW@M21S8HD`upA0<{6D1Bd44vn25m|?`E#D8#_8KX=uER>aMzJXq$1o z-L^Qmb^zSp9dgKns2MLpN>wjh&n}Q9Bqy^-IZ!@+h6f|7r0u5FxTF&MI|JSz}6uyJu5ioJNFdBJsUV*Bm;p`)YW z<&P&UZad+KKeaK(8dkjB@mj`qbRLhT!uD7ROpZ_**R%!)EMiUtqyfQ_gCz=O5Sahi z7ozP54D#jEmpxO)I9jvh&+-Em?N4(km$mgd|s( z{UUkiyi@@C3Ky|zpiG+u-%8B9>-Xe5`N=O2cYK{8y9VuGdAKtM;PmpX@1a>FP*Rvz;3(@GO~5WIp6W@_ z2ODjv2Rh*d`o%~(GE%puW1p-Hhyx6)Qpb=H8`sd^*SDLVeq9bm(lHDK2&WsKBN2C$ zfTIA*N91h)9TpE47c~RV^<{M)pFP2g{|P@4ybeE)jbfbi-;?3p&{J4jUn7UY zjQH^ff#i}DRP=EdZxFk95|TN?^^W_=q^8#?>wlt709TG2pn-|th_psbQqP=9&74_{htW1R)WEqOFF!i${aO?I|tiq1OnL$Z@sz3raqm_}T~-8{)? z$m3TkJyGG?KgpP+P|NY69MhFM3~SY$=SSqBDp6;MH&lg&Rj_)BrG>YZWrka z&_!pi<3Mhi;P8Q_mCjlor5%qM7gE(%G&yWwM77aZ?+$e}25OtzYU);Qk8#IUZYt=}yc#n@qsR>woTg3WLW z0QH{yYjG<(Hg<=<#j)#Qk{Avj5y4aauO}`o5ZKhCbm>=DUTs{_rKBd#XAjGI-m$J5 zGj9NdOv5_2)lX9_o|cCdc742BBDK4$+c+S8e15#%Ha|Q*ZXiMXcJU(d{e~fN@wV+8 zW@h%3>jT3rAtE^YTTLw&_}&!R!q4nY-Qsn5 zPfmu%MMbfL*?3^9Hp#O8;R0arol9$epCv+!6xN5ni*|fb6}^wj=|GP7_xV|HY+$YM zULKP*Z7yNv*@cI|;OJ;?|1aW6QiZd_Y~OerKuwFuUJ+I9P40D|KsW5PyFy&gPxZ#G zF04dv)$KA_9~x4Idy#uq^a2e=U1G4ykob{YdI)--KE>(^>e{Q|1ot1x(T^0y;lwpQ z&Ng^w-TQa5pKjWf1oR2T33#;PJJ&Xs16@G1_glgcLy~Pgoj8!}8c=6cE)o)IJWZvh zQFYr~0D%T>{Ky_&c6P2|prB`?TPe$C^Lc%y5K6x|n~ znDYmp=NlRx=Hca)v>sFjDMU(3{Xa6&$DRjLOGRTWSyd@=5)*wE?ZD_qkI%y&)DU87 zX9_(p|N3R-(JRy?H;^zDjkY_qg=qgD+b`!3c8UTfV<^S}g(yXcUpKD4u}3GRF3!St zv)g0FcT|Ch=OQ$RWj(Y+#4ivE1x~2H&!0I1nVl5$AZS&7wSA)g7;+StLy&PmCdGQD z#4_*8U6@8THZ@^Hfl8G@FJI!OaK!lN6~)7UAg6#R2)cHjK&M<%Yg_xWK8dnLxBL|sFG_V?h+!Y zLeNh})G73-U^FIs-4cyfX56()fvjh8TcfZezoj-h7DSbY>G)i%!4b-4@Bf?+CfUXn zBWmvexPzmOGTkZ(ULzr;E7`iqU7O#}&rj*X1zb6zM(=?(J?6*0sHt>vNAod~Tr&Rl ze~v|7N+^U$85swvV6X{~E}s~LEEblFQ!v~^vlEm%DM`r|ik;fKieK)Ld;QYz@D=n^ zJ!proCW9qNVbVq$eJ++~Q6AabXx##iu>JG%gxncWo6nu|Nl|%_R4PhMlFaO8r;54+ zD?T>sQ2QFSITi!1fxN?MI3Z4fQw8pwz){WT6P3Afw&;aT&0?WS;X^;VstOyTqojaU zA!vnc`<^61J^OHaPz-%bJw!`}D3$~rF}SCys$z2+=^_fl2!4J=&S%PNPnkp$3kt$$ zEoTN*+Fu*EyxfqkdU{SSEJM%=mSB9-FfyR;IXVUh1ra(6h- z7MHi~^s2bNb}#xVCln$e@WIx@*f`92>35Y-uWR~rl7)RZNK#IUH& zeDx4)WR<-95lTppnC^g0fPyGEi;z6zq9loQplPKQSTU z5HD{bieb;a7t1RuAoV2NX}e2gpmXL4gnetcgk6pG#fHqY!AgPWMA6ax`SY3_Q2{x0 z4x!)#qYUygSP4`9y=;9A1-JHahKd96$X9c76Pr`9991P~g%( zBy|CpXL}~?!Ql7shw;W=5mQC5$wA4*f#TVCv(RsXWKZSqhg;}c!oc8$hNCdujveWi zedo%k0PjI{NQf|TaM0g42MCod=*NG}IMJMC``32@PvzbhJT}J}N1G z-NKfJh5%h5o8d-`B3B@+U>RY$V%3?3Un1oE=VHGLCP6C-+sjvpCO3{8=U=L=`hU~6 z_G9#eeygLyy3z@wa`hBV=#Ld>zRHq)t*(YpuDYwp(YLEcQm0V`_8bIW{wHo6PY7mw zPVlNcrFA26OF!5BdkaR-xxJRIXM_@HIeP=s?f;vg)vr9~Qd@ofGceml8KKux5MW2` z2Dbrp+tD!rL!rnCb1x3M40s{_4pYC+f@k6XLy?*mxA$V~+qZOb7|Qt|Uszdz*x7zr zzI?{?(ut#DVznW{TzmJrkv~6+MIV>~D=jT8BjXu?^kKRiF4=9iM{gCm47bsis5NJ2 zvu~namgAt5cD#80WOwW)l2=@tQ=;J(+S$=T#u4QG?YQ-zfBkbb0BlBnz@HNHB0x{D z09;*N(TP>&29i_{ZzNsF%o5LlxXb#?w>VPIx4F*3e<@j?VHn^1^Da>V?( z(5w^4P)l1Id}l$Q+86K@FcT(F;DLxFT|@5@CMW-sDzw!$sv@zB1$`ly#Q;`Cd?{`h zMBPxa;s*b#V$H#r|6i$CKMa1RNNMiPSStkcK3TUYw5ku$*}wbx;1W){Z5xW$5GnG@ z(0-l8)S1+TnGr|^)L|HZ;^KsdB!L$?P5a&%dJUi-`ewXGsnB)uGF+B%gub~TH+LSS z&rH7UluBEsr@qpbt+c5t@*2AE=8SC+NR>-En2)OBE^X%khzS@Q4f)t$8YYp4wCDnv z=O6OgJ+&St_8KC;W`KQ|HKA;T-73l;b#>eR>OBXK?;!VtzuEzhWk`WM{6WsY1tUjD zet8*$*<+m{QbDhxIZZDuCFOcogcqR`9!$(si)~I!YKqMG^!swN*Zd*MG!w|3M|YF! zYwx0(wE>^!0#%k5`HHYE)Ld%j#D&T3n}tQvZoC8D#7&7* zH6g;M_tOE(gxvEE`Yeqf;b1^WuQ3&3s^jAF?#fo?PEJg8KwD(68?)=sK0isiUAta{ zgyi9OV2TH)pV&(aYpn$n^KXY&Tz^JYAH3BN5j1{wxAM(ro3?snc)NX3_S#IF=JWdR zulYw;iu7iYXP`Wx_Cd=3G4mGEO&Lb}9O-XHTi1uACf`_DotJ%bHZ(HX>igWR(|qa7 z?}rO9o2qH2O0UB$9g{<_Zs>2^*fqK{s7vaKR4LJ3I1kpFE)r54pt$abw3Y?)7UFe zZu2e)6qJ0185<&2ohlA4hR2X*PU2B8n-@OQe+^R=+K>xBAROKq2#rh=KuN@$v5Gf} z$64}oa)PSRom2Bo{6OHjA)19CTMr#^&yc7THn#H@QuEC|DIJka(_v~Y5Zm=}UOB7v zrgSGlNapgjt~+ST=*BhV&9I=?8-?>b8dkEpXfm=L!sH)mdP>BcS^f2^zH}Lizb8*9 zAAEQR1xwXMPg=|2#zUG1qhU-!4R`}6Tsi+jxk8BM!ZfpRB7W%9Ev+vF-)OElCO13ih@ zGh)HHYJBMTVp<~>N`v&5)OT-LU4`Rt^P%N)Y!MYNWb4$MW zecHPd6t!fYa#cFrvEQ!WMK3>$!#Wm_0iV2f8yThDmg(LA4zH@&^1C0MbR@45Bwg&G zRC&H38eCq8FkS{Zi}%o>?UanIEl+K zU_q_9=saz}bX)ARl^(B;AK!um+}!EolF)Qp?%{=l=>2A|>bD3xR-&yBEkV;7G^gyXjx z<6#~FD|Nibc&`a%NvZp0Yrl82@8q!$Vg2@DJsan2?03boSDh`GZywL>;emq*-J@^E z`0wzKsJ+|sy6N_Ijvs*!bpE}G4Lx#WN3>G~p6=`)`PK6sc6GbRPr|O*`QkI&u@M#c z=z>BrJJjGU&Gg+^bg4oixaG>eXulyzw%WJbX}!T2ff60J-exEi1sj}*Fc$!aMI5Zp zS;iXS1-y=$^Od0=u0ez0-N*O({Qvs4(a~2O9>VL@|A>4=r!b~Kh}Eo<{9@eT7qEJc zzBGKW+ID*4uyy@R+n5P|%G(du_OjZYF_v+H^8qk8CgjPl+pb`_Gy-ss-Z|I+s{r)V zY2US5S&Ty$$PB@I3i|~z3`OyXn$^FWSHyN(rJ8LyC~<#b@~fnx7pYsb-_GD4cq^k| z0`bM4BP$fh*g2H3d=WO#EWvZTL5vZvICjK9i$KA&Gt}X&zLCil@E49av7zt@ipw+_#B8M!pgE~b2tTD6d$47m6YiPS~A^QwSz zjRhG!x%K2-a;nV%AGe<#La7YSK3}AQ z#HBaauZ4z%;q8X73cI3PTj{T8klT1@FG8lbk!87f0i-FnEVo*KLcq1LL={*8@;XKMowy(Y&bH%n8}ks<_D=kiQgN zj)O@7%a*IiRghVoqzOIz9eFR*EnivSz%bf<3sQO1QQ|aisY`I@jh_Gd zmc%XcZ2 zUyaDAs929%zWH_q9?tJ}4Gxp}!knf2My~VGU*G#JADw4*?;!?{&?k#L%F{{7$!|^$ zC5LTZCt_-TtYn(Dnf$|jpgHLatoR{O3knX_A`kxlekZUA)cOYlzTV?f{M5U%^>|xe z7tjY*m-F&t4RLLLeTHV{dV3C7}B)KoJ5*`iHy88?mq`w+vER!1=uC1HRF!j|L$G{s=$^k}rd%pO@D*CLWI8cyK=sq@a-tmlfC_sA67-ib`u3igXx0B=*`M zHx$mTE-0Ge<}-)T2h^634hDR+Mui1o^=hAhYdRY?s}a~kpyzfzn7_zrLFi5t*MS2k zktic7dVJ{2n}*pbrxMeS+#_hrVIK>RxO~D`-11oHx1^u;Ep;Nce<9a$wflQkP*VYovx9%|P9@2^v%_j^ zeoz)DaMlx}G79hNWAD?4INoGsHGX#syU6N?+LhTX*PT}GaisXb!lRBIO!8BdkXV!jD;8)6igaWY>-bVSZ8UQ~F_{4Bu%QAbGj80>0ux==NE!e3LD}{=w@2O1JwN-YP`44Y*`hf@G zgbEVJ#C^pC&=jw&^9@}FNH00yp|o$`gW3T-a3*azZ#3i@A)A}fFM}7;H!z?jULwp$ z35E13$lM{z)j#ST?iK6dJta&$3A0GJWxch6sRQgDh(!SJeuR>iy80Zpp9r7cCoLVB zdd0+K1Q&8yTYL1~c?GYqDz$2{P1CF&mgkM`_gIE_+YQ!nJW>$C>sYnWUROtOfRCHb zKJ3Z2cX{!AWcyjz5nU|MCE^dxZYo>Nk&=AIil?2`5@{HeqQ4COfYxkHyfg z8!kBxx&O8F`I&Kl3Y4!`!tQ&9yR(FE8c!Uz``muI*h^Q{)NDZXR7{J*CIe#or(d3eK$t$%zTAtZ8Ox zOI$oZ*&ZoO!IJFU{^x}D7pAU1^n6k3pbvc`ljyajBqsjikt@0XjQ}8j>U3rf?zRU$ zfslImG*}=V4rh=3mAQ!tHqqazt4G1X;V?qy!+tcUw4cE6{_q#7xKpD~bn-C+#zgx} z26j*`pf`g|@uW;h*o7>Nz^+Ok`^U?2A%~8XhK&Vs3r5BkST^E;qJbcMt18?0B)e1CaL9~ia+CXBLBTTzDRz-huCA-Q@?{=;fVf08@n}r_7aQKLMrS(uXJ=TT zFlS`^WV3hu=CLRz08VL}S+q^vJo$8-NOR@Plhim!@mrjb`9MyT(3JS&N06EFK(Pte z25(?H`A4nwHWn%CAtT({4S_^d zGa~~78|~hnjq`HjKNZ)&6X?6`f%^!gmO_twRC&;17?@trCf&f~4_0j8!3yM1v-|o{t;IMIib?%vK)YONCxofH z_aykAsoM}*x+x*9%8g|0o7%F73b_ArK^qA9WpsUbsg;w|v`K%t$sFX_vEv!{Xf=U7xD+r^Xt(2PuO(lDs%r9_c2*MKpcKxdgTBkaS?884{Ha| zQr}#{y-3f(LPrdiji6d7R_B=44R`gfdM*VsVsu+rkTt8UUpEPd#}-oWhvolhpY9=j zbnfO&?q-hlZEi&zTDI{AN;&X823uVX2+veFxu0(9*60n9q+1k(df@$P^F`nr z{51Wy>o@-J7H^udj{Q8G&xpZn^zRq_FEDV^Jvx3>MC=hc`G!Ezdb_q%zLh^AS`<0E zj-b~afQ{ze@5{SMACYHl40L{v08)F7rN;_m>;Hb`v0X^TJdgXhGVNh{?oIO_IEz(f zw@43u_*gyqnELeeKQXOuL_;-aK8r`BpGzu9{;p>#`XcG-6SqSlyzelk`#du`eIe%Y zeWLoVP?VuCKcA^kpfHRz<*hx@uOAk5Y>y|EElqFG zKVG+eZ)l}BSY_vReln0{y_BQ|ow6zavoVDO{QUnE)f0ngM%G=df>hD3mo!~%wp4$I z__E)BxB%q81Z8@LFZ#$A%B*z15sE*mns8LrVt1YBjm*4^jPP#lRxFrM`6z*QerZp+Fq{5OueeQq@`0Z z)wVau`eae;+rAaLRD*rsh1@xJ-Eg4y-u9$N@bpcVEj=M&h=Y=pO6%X}HM(W4!c