mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-30 10:05:28 +00:00
split compose functions, and organize stuff
This commit is contained in:
@@ -67,7 +67,7 @@
|
|||||||
android:foregroundServiceType="connectedDevice"
|
android:foregroundServiceType="connectedDevice"
|
||||||
android:permission="android.permission.BLUETOOTH_CONNECT" />
|
android:permission="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<service
|
<service
|
||||||
android:name=".AirPodsQSService"
|
android:name=".services.AirPodsQSService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:icon="@drawable/airpods"
|
android:icon="@drawable/airpods"
|
||||||
android:label="ANC Mode"
|
android:label="ANC Mode"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".BootReceiver"
|
android:name=".receivers.BootReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ import android.widget.RemoteViews
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.kavishdevar.aln.utils.MediaController
|
||||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||||
|
|
||||||
object ServiceManager {
|
object ServiceManager {
|
||||||
@@ -36,11 +38,11 @@ object ServiceManager {
|
|||||||
fun setService(service: AirPodsService?) {
|
fun setService(service: AirPodsService?) {
|
||||||
this.service = service
|
this.service = service
|
||||||
}
|
}
|
||||||
@Synchronized
|
// @Synchronized
|
||||||
fun restartService(context: Context) {
|
// fun restartService(context: Context) {
|
||||||
service?.stopSelf()
|
// service?.stopSelf()
|
||||||
context.startService(Intent(context, AirPodsService::class.java))
|
// context.startService(Intent(context, AirPodsService::class.java))
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@@ -76,6 +78,10 @@ class AirPodsService: Service() {
|
|||||||
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
||||||
Log.d("AirPodsService", "Received bluetooth connection broadcast")
|
Log.d("AirPodsService", "Received bluetooth connection broadcast")
|
||||||
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
||||||
|
if (ServiceManager.getService()?.isConnected == true) {
|
||||||
|
ServiceManager.getService()?.manuallyCheckForAudioSource()
|
||||||
|
return
|
||||||
|
}
|
||||||
val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||||
if (bluetoothDevice.uuids.contains(uuid)) {
|
if (bluetoothDevice.uuids.contains(uuid)) {
|
||||||
val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
|
val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
|
||||||
@@ -84,8 +90,6 @@ class AirPodsService: Service() {
|
|||||||
context?.sendBroadcast(intent)
|
context?.sendBroadcast(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Airpods disconnected, remove notification but leave the scanner going.
|
|
||||||
if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action
|
if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action
|
||||||
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action
|
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action
|
||||||
) {
|
) {
|
||||||
@@ -203,7 +207,19 @@ class AirPodsService: Service() {
|
|||||||
Log.d("AirPodsService", "Service started")
|
Log.d("AirPodsService", "Service started")
|
||||||
ServiceManager.setService(this)
|
ServiceManager.setService(this)
|
||||||
startForegroundNotification()
|
startForegroundNotification()
|
||||||
registerReceiver(bluetoothReceiver, BluetoothReceiver.buildFilter(), RECEIVER_EXPORTED)
|
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")
|
||||||
|
}
|
||||||
|
registerReceiver(bluetoothReceiver, serviceIntentFilter, RECEIVER_EXPORTED)
|
||||||
|
|
||||||
connectionReceiver = object: BroadcastReceiver() {
|
connectionReceiver = object: BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
@@ -225,11 +241,11 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val intentFilter = IntentFilter().apply {
|
val deviceIntentFilter = IntentFilter().apply {
|
||||||
addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
|
addAction(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED)
|
||||||
addAction(AirPodsNotifications.AIRPODS_DISCONNECTED)
|
addAction(AirPodsNotifications.AIRPODS_DISCONNECTED)
|
||||||
}
|
}
|
||||||
registerReceiver(connectionReceiver, intentFilter, RECEIVER_EXPORTED)
|
registerReceiver(connectionReceiver, deviceIntentFilter, RECEIVER_EXPORTED)
|
||||||
|
|
||||||
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
|
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
|
||||||
bluetoothAdapter.bondedDevices.forEach { device ->
|
bluetoothAdapter.bondedDevices.forEach { device ->
|
||||||
@@ -258,6 +274,13 @@ class AirPodsService: Service() {
|
|||||||
|
|
||||||
private lateinit var socket: BluetoothSocket
|
private lateinit var socket: BluetoothSocket
|
||||||
|
|
||||||
|
fun manuallyCheckForAudioSource() {
|
||||||
|
if (earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) {
|
||||||
|
Log.d("AirPodsService", "For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!")
|
||||||
|
disconnectAudio(this, device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
|
||||||
fun connectToSocket(device: BluetoothDevice) {
|
fun connectToSocket(device: BluetoothDevice) {
|
||||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
||||||
@@ -303,18 +326,32 @@ class AirPodsService: Service() {
|
|||||||
this@AirPodsService.device = device
|
this@AirPodsService.device = device
|
||||||
isConnected = true
|
isConnected = true
|
||||||
socket.let { it ->
|
socket.let { it ->
|
||||||
|
// sometimes doesn't work ;-;
|
||||||
|
// i though i move it to the coroutine
|
||||||
|
// but, the socket sometimes disconnects if i don't send a packet outside of the routine first
|
||||||
|
// so, sending *again*, with a delay, in the coroutine
|
||||||
it.outputStream.write(Enums.HANDSHAKE.value)
|
it.outputStream.write(Enums.HANDSHAKE.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
||||||
it.outputStream.flush()
|
it.outputStream.flush()
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
// this is so stupid, why does it disconnect if i don't send a packet outside of the coroutine first
|
||||||
|
it.outputStream.write(Enums.HANDSHAKE.value)
|
||||||
|
it.outputStream.flush()
|
||||||
|
delay(200)
|
||||||
|
it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value)
|
||||||
|
it.outputStream.flush()
|
||||||
|
delay(200)
|
||||||
|
it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value)
|
||||||
|
it.outputStream.flush()
|
||||||
|
delay(200)
|
||||||
sendBroadcast(
|
sendBroadcast(
|
||||||
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
Intent(AirPodsNotifications.AIRPODS_CONNECTED)
|
||||||
.putExtra("device", device)
|
.putExtra("device", device)
|
||||||
)
|
)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
while (socket.isConnected == true) {
|
while (socket.isConnected == true) {
|
||||||
socket.let {
|
socket.let {
|
||||||
val audioManager =
|
val audioManager =
|
||||||
@@ -516,6 +553,7 @@ class AirPodsService: Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setANCMode(mode: Int) {
|
fun setANCMode(mode: Int) {
|
||||||
|
Log.d("AirPodsService", "setANCMode: $mode")
|
||||||
when (mode) {
|
when (mode) {
|
||||||
1 -> {
|
1 -> {
|
||||||
socket.outputStream?.write(Enums.NOISE_CANCELLATION_OFF.value)
|
socket.outputStream?.write(Enums.NOISE_CANCELLATION_OFF.value)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
|||||||
package me.kavishdevar.aln
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
class BootReceiver: BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
when (intent?.action) {
|
|
||||||
Intent.ACTION_MY_PACKAGE_REPLACED -> try { context?.startForegroundService(Intent(context, AirPodsService::class.java)) } catch (e: Exception) { e.printStackTrace() }
|
|
||||||
Intent.ACTION_BOOT_COMPLETED -> try { context?.startForegroundService(Intent(context, AirPodsService::class.java)) } catch (e: Exception) { e.printStackTrace() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,6 +34,9 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
|
import me.kavishdevar.aln.screens.AirPodsSettingsScreen
|
||||||
|
import me.kavishdevar.aln.screens.DebugScreen
|
||||||
|
import me.kavishdevar.aln.screens.LongPress
|
||||||
import me.kavishdevar.aln.ui.theme.ALNTheme
|
import me.kavishdevar.aln.ui.theme.ALNTheme
|
||||||
|
|
||||||
lateinit var serviceConnection: ServiceConnection
|
lateinit var serviceConnection: ServiceConnection
|
||||||
@@ -66,6 +69,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MainActivity", "Error while unregistering receiver: $e")
|
Log.e("MainActivity", "Error while unregistering receiver: $e")
|
||||||
}
|
}
|
||||||
|
sendBroadcast(Intent(AirPodsNotifications.DISCONNECT_RECEIVERS))
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +151,10 @@ fun Main() {
|
|||||||
DebugScreen(navController = navController)
|
DebugScreen(navController = navController)
|
||||||
}
|
}
|
||||||
composable("long_press/{bud}") { navBackStackEntry ->
|
composable("long_press/{bud}") { navBackStackEntry ->
|
||||||
LongPress(navController = navController, name = navBackStackEntry.arguments?.getString("bud")!!)
|
LongPress(
|
||||||
|
navController = navController,
|
||||||
|
name = navBackStackEntry.arguments?.getString("bud")!!
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class AirPodsNotifications {
|
|||||||
const val CA_DATA = "me.kavishdevar.aln.CA_DATA"
|
const val CA_DATA = "me.kavishdevar.aln.CA_DATA"
|
||||||
const val AIRPODS_DISCONNECTED = "me.kavishdevar.aln.AIRPODS_DISCONNECTED"
|
const val AIRPODS_DISCONNECTED = "me.kavishdevar.aln.AIRPODS_DISCONNECTED"
|
||||||
const val AIRPODS_CONNECTION_DETECTED = "me.kavishdevar.aln.AIRPODS_CONNECTION_DETECTED"
|
const val AIRPODS_CONNECTION_DETECTED = "me.kavishdevar.aln.AIRPODS_CONNECTION_DETECTED"
|
||||||
|
const val DISCONNECT_RECEIVERS = "me.kavishdevar.aln.DISCONNECT_RECEIVERS"
|
||||||
}
|
}
|
||||||
|
|
||||||
class EarDetection {
|
class EarDetection {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
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.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
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.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AccessibilitySettings(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "ACCESSIBILITY",
|
||||||
|
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))
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "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(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Dropdown menu with 3 options, Default, Slower, Slowest – Press speed
|
||||||
|
// TODO: Dropdown menu with 3 options, Default, Slower, Slowest – Press and hold duration
|
||||||
|
// TODO: Dropdown menu with 3 options, Default, Slower, Slowest – Volume Swipe Speed
|
||||||
|
|
||||||
|
SinglePodANCSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
VolumeControlSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AccessibilitySettingsPreview() {
|
||||||
|
AccessibilitySettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
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.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.platform.LocalContext
|
||||||
|
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.aln.AirPodsService
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AdaptiveStrengthSlider(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
val sliderValue = remember { mutableFloatStateOf(0f) }
|
||||||
|
LaunchedEffect(sliderValue) {
|
||||||
|
if (sharedPreferences.contains("adaptive_strength")) {
|
||||||
|
sliderValue.floatValue = sharedPreferences.getInt("adaptive_strength", 0).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(sliderValue.floatValue) {
|
||||||
|
sharedPreferences.edit().putInt("adaptive_strength", sliderValue.floatValue.toInt()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Slider(
|
||||||
|
value = sliderValue.floatValue,
|
||||||
|
onValueChange = {
|
||||||
|
sliderValue.floatValue = it
|
||||||
|
service.setAdaptiveStrength(100 - it.toInt())
|
||||||
|
},
|
||||||
|
valueRange = 0f..100f,
|
||||||
|
onValueChangeFinished = {
|
||||||
|
sliderValue.floatValue = sliderValue.floatValue.roundToInt().toFloat()
|
||||||
|
},
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AdaptiveStrengthSliderPreview() {
|
||||||
|
AdaptiveStrengthSlider(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
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.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
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.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AudioSettings(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "AUDIO",
|
||||||
|
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))
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
PersonalizedVolumeSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
ConversationalAwarenessSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
LoudSoundReductionSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp, vertical = 10.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Adaptive Audio",
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp, bottom = 2.dp, start = 2.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Adaptive audio dynamically responds to your environment and cancels or allows external noise. You can customize Adaptive Audio to allow more or less noise.",
|
||||||
|
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(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AudioSettingsPreview() {
|
||||||
|
AudioSettings(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", Context.MODE_PRIVATE))
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import me.kavishdevar.aln.R
|
import me.kavishdevar.aln.R
|
||||||
@@ -106,3 +107,9 @@ fun BatteryIndicator(batteryPercentage: Int, charging: Boolean = false) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun BatteryIndicatorPreview() {
|
||||||
|
BatteryIndicator(batteryPercentage = 48, charging = true)
|
||||||
|
}
|
||||||
@@ -22,12 +22,12 @@ import androidx.compose.ui.draw.scale
|
|||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.imageResource
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import me.kavishdevar.aln.AirPodsNotifications
|
import me.kavishdevar.aln.AirPodsNotifications
|
||||||
import me.kavishdevar.aln.AirPodsService
|
import me.kavishdevar.aln.AirPodsService
|
||||||
import me.kavishdevar.aln.Battery
|
import me.kavishdevar.aln.Battery
|
||||||
import me.kavishdevar.aln.BatteryComponent
|
import me.kavishdevar.aln.BatteryComponent
|
||||||
import me.kavishdevar.aln.composables.BatteryIndicator
|
|
||||||
import me.kavishdevar.aln.BatteryStatus
|
import me.kavishdevar.aln.BatteryStatus
|
||||||
import me.kavishdevar.aln.R
|
import me.kavishdevar.aln.R
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
@Suppress("DEPRECATION") val batteryReceiver = remember {
|
@Suppress("DEPRECATION") val batteryReceiver = remember {
|
||||||
object : BroadcastReceiver() {
|
object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == AirPodsNotifications.BATTERY_DATA) {
|
||||||
batteryStatus.value =
|
batteryStatus.value =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
intent.getParcelableArrayListExtra("data", Battery::class.java)
|
intent.getParcelableArrayListExtra("data", Battery::class.java)
|
||||||
@@ -44,12 +45,20 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
intent.getParcelableArrayListExtra("data")
|
intent.getParcelableArrayListExtra("data")
|
||||||
}?.toList() ?: listOf()
|
}?.toList() ?: listOf()
|
||||||
}
|
}
|
||||||
|
else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
||||||
|
context.unregisterReceiver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(context) {
|
LaunchedEffect(context) {
|
||||||
val batteryIntentFilter = IntentFilter(AirPodsNotifications.BATTERY_DATA)
|
val batteryIntentFilter = IntentFilter()
|
||||||
|
.apply {
|
||||||
|
addAction(AirPodsNotifications.BATTERY_DATA)
|
||||||
|
addAction(AirPodsNotifications.DISCONNECT_RECEIVERS)
|
||||||
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.registerReceiver(
|
context.registerReceiver(
|
||||||
batteryReceiver,
|
batteryReceiver,
|
||||||
@@ -133,3 +142,9 @@ fun BatteryView(service: AirPodsService, preview: Boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun BatteryViewPreview() {
|
||||||
|
BatteryView(AirPodsService(), preview = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.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.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.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ConversationalAwarenessSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
var conversationalAwarenessEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("conversational_awareness", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateConversationalAwareness(enabled: Boolean) {
|
||||||
|
conversationalAwarenessEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("conversational_awareness", enabled).apply()
|
||||||
|
service.setCAEnabled(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() }
|
||||||
|
) {
|
||||||
|
updateConversationalAwareness(!conversationalAwarenessEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Conversational Awareness",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Lowers media volume and reduces background noise when you start speaking to other people.",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = textColor.copy(0.6f),
|
||||||
|
lineHeight = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
StyledSwitch(
|
||||||
|
checked = conversationalAwarenessEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
updateConversationalAwareness(it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ConversationalAwarenessSwitchPreview() {
|
||||||
|
ConversationalAwarenessSwitch(AirPodsService(), LocalContext.current.getSharedPreferences("preview", 0))
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
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.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.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.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IndependentToggle(name: String, service: AirPodsService, functionName: String, sharedPreferences: SharedPreferences, default: Boolean = false) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
val snakeCasedName = name.replace(Regex("[\\W\\s]+"), "_").lowercase()
|
||||||
|
var checked by remember { mutableStateOf(default) }
|
||||||
|
|
||||||
|
LaunchedEffect(sharedPreferences) {
|
||||||
|
checked = sharedPreferences.getBoolean(snakeCasedName, true)
|
||||||
|
}
|
||||||
|
Box (
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.background(
|
||||||
|
if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF),
|
||||||
|
RoundedCornerShape(14.dp)
|
||||||
|
)
|
||||||
|
.clickable {
|
||||||
|
checked = !checked
|
||||||
|
sharedPreferences
|
||||||
|
.edit()
|
||||||
|
.putBoolean(snakeCasedName, checked)
|
||||||
|
.apply()
|
||||||
|
|
||||||
|
val method = service::class.java.getMethod(functionName, Boolean::class.java)
|
||||||
|
method.invoke(service, checked)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
sharedPreferences.edit().putBoolean(snakeCasedName, it).apply()
|
||||||
|
val method = service::class.java.getMethod(functionName, Boolean::class.java)
|
||||||
|
method.invoke(service, it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun IndependentTogglePreview() {
|
||||||
|
IndependentToggle("Test", AirPodsService(), "test", LocalContext.current.getSharedPreferences("preview", 0), true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.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.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.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoudSoundReductionSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
var loudSoundReductionEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("loud_sound_reduction", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateLoudSoundReduction(enabled: Boolean) {
|
||||||
|
loudSoundReductionEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("loud_sound_reduction", enabled).apply()
|
||||||
|
service.setLoudSoundReduction(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() }
|
||||||
|
) {
|
||||||
|
updateLoudSoundReduction(!loudSoundReductionEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Loud Sound Reduction",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Reduces loud sounds you are exposed to.",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = textColor.copy(0.6f),
|
||||||
|
lineHeight = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledSwitch(
|
||||||
|
checked = loudSoundReductionEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
updateLoudSoundReduction(it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun LoudSoundReductionSwitchPreview() {
|
||||||
|
LoudSoundReductionSwitch(AirPodsService(), LocalContext.current.getSharedPreferences("preview", 0))
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
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.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NavigationButton(to: String, name: String, navController: NavController) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
if (isSystemInDarkTheme()) Color(
|
||||||
|
0xFF1C1C1E
|
||||||
|
) else Color(0xFFFFFFFF), RoundedCornerShape(14.dp)
|
||||||
|
)
|
||||||
|
.height(55.dp)
|
||||||
|
.clickable {
|
||||||
|
navController.navigate(to)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
IconButton(
|
||||||
|
onClick = { navController.navigate(to) },
|
||||||
|
colors = IconButtonDefaults.iconButtonColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
) {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.KeyboardArrowRight,
|
||||||
|
contentDescription = name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun NavigationButtonPreview() {
|
||||||
|
NavigationButton("to", "Name", NavController(LocalContext.current))
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
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.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoiseControlButton(
|
||||||
|
icon: ImageBitmap,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
textColor: Color,
|
||||||
|
backgroundColor: Color,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(horizontal = 4.dp, vertical = 4.dp)
|
||||||
|
.background(color = backgroundColor, shape = RoundedCornerShape(11.dp))
|
||||||
|
.clickable(
|
||||||
|
onClick = onClick,
|
||||||
|
indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
bitmap = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = textColor,
|
||||||
|
modifier = Modifier.size(40.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun NoiseControlButtonPreview() {
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
|
||||||
|
onClick = {},
|
||||||
|
textColor = Color.White,
|
||||||
|
backgroundColor = Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.imageResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
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
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsNotifications
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
import me.kavishdevar.aln.NoiseControlMode
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||||
|
@Composable
|
||||||
|
fun NoiseControlSettings(service: AirPodsService) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFE3E3E8)
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
val textColorSelected = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
val selectedBackground = if (isDarkTheme) Color(0xFF5C5A5F) else Color(0xFFFFFFFF)
|
||||||
|
|
||||||
|
val noiseControlMode = remember { mutableStateOf(NoiseControlMode.OFF) }
|
||||||
|
|
||||||
|
|
||||||
|
val d1a = remember { mutableFloatStateOf(0f) }
|
||||||
|
val d2a = remember { mutableFloatStateOf(0f) }
|
||||||
|
val d3a = remember { mutableFloatStateOf(0f) }
|
||||||
|
|
||||||
|
fun onModeSelected(mode: NoiseControlMode, received: Boolean = false) {
|
||||||
|
noiseControlMode.value = mode
|
||||||
|
if (!received) service.setANCMode(mode.ordinal+1)
|
||||||
|
when (mode) {
|
||||||
|
NoiseControlMode.NOISE_CANCELLATION -> {
|
||||||
|
d1a.floatValue = 1f
|
||||||
|
d2a.floatValue = 1f
|
||||||
|
d3a.floatValue = 0f
|
||||||
|
}
|
||||||
|
NoiseControlMode.OFF -> {
|
||||||
|
d1a.floatValue = 0f
|
||||||
|
d2a.floatValue = 1f
|
||||||
|
d3a.floatValue = 1f
|
||||||
|
}
|
||||||
|
NoiseControlMode.ADAPTIVE -> {
|
||||||
|
d1a.floatValue = 1f
|
||||||
|
d2a.floatValue = 0f
|
||||||
|
d3a.floatValue = 0f
|
||||||
|
}
|
||||||
|
NoiseControlMode.TRANSPARENCY -> {
|
||||||
|
d1a.floatValue = 0f
|
||||||
|
d2a.floatValue = 0f
|
||||||
|
d3a.floatValue = 1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val noiseControlReceiver = remember {
|
||||||
|
object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == AirPodsNotifications.ANC_DATA) {
|
||||||
|
noiseControlMode.value = NoiseControlMode.entries.toTypedArray()[intent.getIntExtra("data", 3) - 1]
|
||||||
|
onModeSelected(noiseControlMode.value, true)
|
||||||
|
}
|
||||||
|
else if (intent.action == AirPodsNotifications.DISCONNECT_RECEIVERS) {
|
||||||
|
context.unregisterReceiver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val noiseControlIntentFilter = IntentFilter()
|
||||||
|
.apply {
|
||||||
|
addAction(AirPodsNotifications.ANC_DATA)
|
||||||
|
addAction(AirPodsNotifications.DISCONNECT_RECEIVERS)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter, Context.RECEIVER_EXPORTED)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "NOISE CONTROL",
|
||||||
|
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()
|
||||||
|
.padding(vertical = 16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(75.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor, RoundedCornerShape(14.dp))
|
||||||
|
) {
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
|
||||||
|
onClick = { onModeSelected(NoiseControlMode.OFF) },
|
||||||
|
textColor = if (noiseControlMode.value == NoiseControlMode.OFF) textColorSelected else textColor,
|
||||||
|
backgroundColor = if (noiseControlMode.value == NoiseControlMode.OFF) selectedBackground else Color.Transparent,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
VerticalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 10.dp)
|
||||||
|
.alpha(d1a.floatValue),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
)
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = ImageBitmap.imageResource(R.drawable.transparency),
|
||||||
|
onClick = { onModeSelected(NoiseControlMode.TRANSPARENCY) },
|
||||||
|
textColor = if (noiseControlMode.value == NoiseControlMode.TRANSPARENCY) textColorSelected else textColor,
|
||||||
|
backgroundColor = if (noiseControlMode.value == NoiseControlMode.TRANSPARENCY) selectedBackground else Color.Transparent,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
VerticalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 10.dp)
|
||||||
|
.alpha(d2a.floatValue),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
)
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = ImageBitmap.imageResource(R.drawable.adaptive),
|
||||||
|
onClick = { onModeSelected(NoiseControlMode.ADAPTIVE) },
|
||||||
|
textColor = if (noiseControlMode.value == NoiseControlMode.ADAPTIVE) textColorSelected else textColor,
|
||||||
|
backgroundColor = if (noiseControlMode.value == NoiseControlMode.ADAPTIVE) selectedBackground else Color.Transparent,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
VerticalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 10.dp)
|
||||||
|
.alpha(d3a.floatValue),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
)
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = ImageBitmap.imageResource(R.drawable.noise_cancellation),
|
||||||
|
onClick = { onModeSelected(NoiseControlMode.NOISE_CANCELLATION) },
|
||||||
|
textColor = if (noiseControlMode.value == NoiseControlMode.NOISE_CANCELLATION) textColorSelected else textColor,
|
||||||
|
backgroundColor = if (noiseControlMode.value == NoiseControlMode.NOISE_CANCELLATION) selectedBackground else Color.Transparent,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.padding(top = 1.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Off",
|
||||||
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Transparency",
|
||||||
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Adaptive",
|
||||||
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Noise Cancellation",
|
||||||
|
style = TextStyle(fontSize = 12.sp, color = textColor),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun NoiseControlSettingsPreview() {
|
||||||
|
NoiseControlSettings(AirPodsService())
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.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.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.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PersonalizedVolumeSwitch(service: AirPodsService, sharedPreferences: SharedPreferences) {
|
||||||
|
var personalizedVolumeEnabled by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
sharedPreferences.getBoolean("personalized_volume", true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePersonalizedVolume(enabled: Boolean) {
|
||||||
|
personalizedVolumeEnabled = enabled
|
||||||
|
sharedPreferences.edit().putBoolean("personalized_volume", enabled).apply()
|
||||||
|
service.setPVEnabled(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() }
|
||||||
|
) {
|
||||||
|
updatePersonalizedVolume(!personalizedVolumeEnabled)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Personalized Volume",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Adjusts the volume of media in response to your environment.",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = textColor.copy(0.6f),
|
||||||
|
lineHeight = 14.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledSwitch(
|
||||||
|
checked = personalizedVolumeEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
updatePersonalizedVolume(it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PersonalizedVolumeSwitchPreview() {
|
||||||
|
PersonalizedVolumeSwitch(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package me.kavishdevar.aln.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.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.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
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.aln.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PressAndHoldSettings(navController: NavController) {
|
||||||
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "PRESS AND HOLD AIRPODS",
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor, RoundedCornerShape(14.dp))
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(55.dp)
|
||||||
|
.background(
|
||||||
|
backgroundColor,
|
||||||
|
RoundedCornerShape(14.dp)
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
navController.navigate("long_press/Left")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Left",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = textColor,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
// TODO: Implement voice assistant on long press; for now, it's noise control
|
||||||
|
text = "Noise Control",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 2.dp,
|
||||||
|
color = Color(0xFF4D4D4D).copy(alpha = 0.4f),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(55.dp)
|
||||||
|
.background(
|
||||||
|
backgroundColor,
|
||||||
|
RoundedCornerShape(18.dp)
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
navController.navigate("long_press/Right")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Right",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.sp,
|
||||||
|
color = textColor,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text(
|
||||||
|
// TODO: Implement voice assistant on long press; for now, it's noise control
|
||||||
|
text = "Noise Control",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.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
|
||||||
|
@Composable
|
||||||
|
fun PressAndHoldSettingsPreview() {
|
||||||
|
PressAndHoldSettings(navController = NavController(LocalContext.current))
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -12,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -22,8 +22,9 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import me.kavishdevar.aln.AirPodsService
|
import me.kavishdevar.aln.AirPodsService
|
||||||
@@ -42,7 +43,7 @@ fun SinglePodANCSwitch(service: AirPodsService, sharedPreferences: SharedPrefere
|
|||||||
service.setNoiseCancellationWithOnePod(enabled)
|
service.setNoiseCancellationWithOnePod(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
val isPressed = remember { mutableStateOf(false) }
|
val isPressed = remember { mutableStateOf(false) }
|
||||||
@@ -98,3 +99,9 @@ fun SinglePodANCSwitch(service: AirPodsService, sharedPreferences: SharedPrefere
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SinglePodANCSwitchPreview() {
|
||||||
|
SinglePodANCSwitch(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package me.kavishdevar.aln.composables
|
|||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
@@ -11,14 +12,13 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -26,7 +26,7 @@ fun StyledSwitch(
|
|||||||
checked: Boolean,
|
checked: Boolean,
|
||||||
onCheckedChange: (Boolean) -> Unit
|
onCheckedChange: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
|
||||||
val thumbColor = Color.White
|
val thumbColor = Color.White
|
||||||
val trackColor = if (checked) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6)
|
val trackColor = if (checked) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF5B5B5E) else Color(0xFFD1D1D6)
|
||||||
@@ -53,3 +53,9 @@ fun StyledSwitch(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun StyledSwitchPreview() {
|
||||||
|
StyledSwitch(checked = true, onCheckedChange = {})
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package me.kavishdevar.aln.composables
|
package me.kavishdevar.aln.composables
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -8,7 +9,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -20,8 +20,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.graphics.luminance
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ fun StyledTextField(
|
|||||||
) {
|
) {
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
@@ -87,3 +87,9 @@ fun StyledTextField(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun StyledTextFieldPreview() {
|
||||||
|
StyledTextField(name = "Name", value = "AirPods Pro", onValueChange = {})
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package me.kavishdevar.aln.composables
|
|||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -12,7 +13,6 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Slider
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.SliderDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -24,11 +24,12 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import me.kavishdevar.aln.AirPodsService
|
import me.kavishdevar.aln.AirPodsService
|
||||||
@@ -48,7 +49,7 @@ fun ToneVolumeSlider(service: AirPodsService, sharedPreferences: SharedPreferenc
|
|||||||
sharedPreferences.edit().putInt("tone_volume", sliderValue.floatValue.toInt()).apply()
|
sharedPreferences.edit().putInt("tone_volume", sliderValue.floatValue.toInt()).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
|
|
||||||
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491)
|
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFF929491)
|
||||||
val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5)
|
val activeTrackColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5)
|
||||||
@@ -133,3 +134,9 @@ fun ToneVolumeSlider(service: AirPodsService, sharedPreferences: SharedPreferenc
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ToneVolumeSliderPreview() {
|
||||||
|
ToneVolumeSlider(AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -12,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -22,8 +22,9 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import me.kavishdevar.aln.AirPodsService
|
import me.kavishdevar.aln.AirPodsService
|
||||||
@@ -41,7 +42,7 @@ fun VolumeControlSwitch(service: AirPodsService, sharedPreferences: SharedPrefer
|
|||||||
service.setVolumeControl(enabled)
|
service.setVolumeControl(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
val isPressed = remember { mutableStateOf(false) }
|
val isPressed = remember { mutableStateOf(false) }
|
||||||
@@ -97,3 +98,9 @@ fun VolumeControlSwitch(service: AirPodsService, sharedPreferences: SharedPrefer
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun VolumeControlSwitchPreview() {
|
||||||
|
VolumeControlSwitch(service = AirPodsService(), sharedPreferences = LocalContext.current.getSharedPreferences("preview", 0))
|
||||||
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package me.kavishdevar.aln
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
|
|
||||||
class BluetoothReceiver : BroadcastReceiver() {
|
|
||||||
fun onConnect(bluetoothDevice: BluetoothDevice?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDisconnect(bluetoothDevice: BluetoothDevice?) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
override fun onReceive(context: Context?, intent: Intent) {
|
|
||||||
val bluetoothDevice =
|
|
||||||
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java)
|
|
||||||
val action = intent.action
|
|
||||||
|
|
||||||
// Airpods filter
|
|
||||||
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
|
||||||
// Airpods connected, show notification.
|
|
||||||
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
|
||||||
onConnect(bluetoothDevice)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Airpods disconnected, remove notification but leave the scanner going.
|
|
||||||
if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action
|
|
||||||
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action
|
|
||||||
) {
|
|
||||||
onDisconnect(bluetoothDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* When the service is created, we register to get as many bluetooth and airpods related events as possible.
|
|
||||||
* ACL_CONNECTED and ACL_DISCONNECTED should have been enough, but you never know with android these days.
|
|
||||||
*/
|
|
||||||
fun buildFilter(): IntentFilter {
|
|
||||||
val intentFilter = IntentFilter()
|
|
||||||
intentFilter.addAction("android.bluetooth.device.action.ACL_CONNECTED")
|
|
||||||
intentFilter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED")
|
|
||||||
intentFilter.addAction("android.bluetooth.device.action.BOND_STATE_CHANGED")
|
|
||||||
intentFilter.addAction("android.bluetooth.device.action.NAME_CHANGED")
|
|
||||||
intentFilter.addAction("android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED")
|
|
||||||
intentFilter.addAction("android.bluetooth.adapter.action.STATE_CHANGED")
|
|
||||||
intentFilter.addAction("android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED")
|
|
||||||
intentFilter.addAction("android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT")
|
|
||||||
intentFilter.addAction("android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
|
|
||||||
intentFilter.addAction("android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED")
|
|
||||||
intentFilter.addCategory("android.bluetooth.headset.intent.category.companyid.76")
|
|
||||||
return intentFilter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package me.kavishdevar.aln.receivers
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
|
||||||
|
class BootReceiver: BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
when (intent?.action) {
|
||||||
|
Intent.ACTION_MY_PACKAGE_REPLACED -> try { context?.startForegroundService(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
AirPodsService::class.java
|
||||||
|
)
|
||||||
|
) } catch (e: Exception) { e.printStackTrace() }
|
||||||
|
Intent.ACTION_BOOT_COMPLETED -> try { context?.startForegroundService(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
AirPodsService::class.java
|
||||||
|
)
|
||||||
|
) } catch (e: Exception) { e.printStackTrace() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
package me.kavishdevar.aln.screens
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.content.Context.MODE_PRIVATE
|
||||||
|
import android.content.Intent
|
||||||
|
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.fillMaxWidth
|
||||||
|
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.CenterAlignedTopAppBar
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
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
|
||||||
|
import dev.chrisbanes.haze.HazeState
|
||||||
|
import dev.chrisbanes.haze.haze
|
||||||
|
import dev.chrisbanes.haze.hazeChild
|
||||||
|
import dev.chrisbanes.haze.materials.CupertinoMaterials
|
||||||
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
|
import me.kavishdevar.aln.AirPodsNotifications
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
import me.kavishdevar.aln.composables.AccessibilitySettings
|
||||||
|
import me.kavishdevar.aln.composables.AudioSettings
|
||||||
|
import me.kavishdevar.aln.composables.BatteryView
|
||||||
|
import me.kavishdevar.aln.composables.IndependentToggle
|
||||||
|
import me.kavishdevar.aln.composables.NavigationButton
|
||||||
|
import me.kavishdevar.aln.composables.NoiseControlSettings
|
||||||
|
import me.kavishdevar.aln.composables.PressAndHoldSettings
|
||||||
|
import me.kavishdevar.aln.composables.StyledTextField
|
||||||
|
import me.kavishdevar.aln.ui.theme.ALNTheme
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
|
||||||
|
@SuppressLint("MissingPermission", "NewApi")
|
||||||
|
@Composable
|
||||||
|
fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
||||||
|
navController: NavController, isConnected: Boolean) {
|
||||||
|
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
|
var device by remember { mutableStateOf(dev) }
|
||||||
|
var deviceName by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
TextFieldValue(
|
||||||
|
sharedPreferences.getString("name", device?.name ?: "AirPods Pro").toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val verticalScrollState = rememberScrollState()
|
||||||
|
val hazeState = remember { HazeState() }
|
||||||
|
|
||||||
|
@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
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.hazeChild(
|
||||||
|
state = hazeState,
|
||||||
|
style = CupertinoMaterials.regular(),
|
||||||
|
block = {
|
||||||
|
alpha =
|
||||||
|
if (verticalScrollState.value > 55.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 > 55.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 = {
|
||||||
|
// val context = LocalContext.current
|
||||||
|
// IconButton(
|
||||||
|
// onClick = {
|
||||||
|
// ServiceManager.restartService(context)
|
||||||
|
// },
|
||||||
|
// colors = IconButtonDefaults.iconButtonColors(
|
||||||
|
// containerColor = Color.Transparent,
|
||||||
|
// contentColor = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
// )
|
||||||
|
// ) {
|
||||||
|
// Icon(
|
||||||
|
// imageVector = Icons.Default.Refresh,
|
||||||
|
// contentDescription = "Settings",
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
if (isConnected == true) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.haze(hazeState)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.verticalScroll(
|
||||||
|
state = verticalScrollState,
|
||||||
|
enabled = true,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Spacer(Modifier.height(75.dp))
|
||||||
|
LaunchedEffect(service) {
|
||||||
|
service.let {
|
||||||
|
it.sendBroadcast(Intent(AirPodsNotifications.Companion.BATTERY_DATA).apply {
|
||||||
|
putParcelableArrayListExtra("data", ArrayList(it.getBattery()))
|
||||||
|
})
|
||||||
|
it.sendBroadcast(Intent(AirPodsNotifications.Companion.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))
|
||||||
|
|
||||||
|
StyledTextField(
|
||||||
|
name = "Name",
|
||||||
|
value = deviceName.text,
|
||||||
|
onValueChange = {
|
||||||
|
deviceName = TextFieldValue(it)
|
||||||
|
sharedPreferences.edit().putString("name", it).apply()
|
||||||
|
service.setName(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
NoiseControlSettings(service = service)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
PressAndHoldSettings(navController = navController)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
AudioSettings(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
IndependentToggle(
|
||||||
|
name = "Automatic Ear Detection",
|
||||||
|
service = service,
|
||||||
|
functionName = "setEarDetection",
|
||||||
|
sharedPreferences = sharedPreferences,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
IndependentToggle(
|
||||||
|
name = "Off Listening Mode",
|
||||||
|
service = service,
|
||||||
|
functionName = "setOffListeningMode",
|
||||||
|
sharedPreferences = sharedPreferences,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
AccessibilitySettings(service = service, sharedPreferences = sharedPreferences)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
NavigationButton("debug", "Debug", navController)
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.verticalScroll(
|
||||||
|
state = verticalScrollState,
|
||||||
|
enabled = true,
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "AirPods not connected",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = "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!)",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun AirPodsSettingsScreenPreview() {
|
||||||
|
ALNTheme (
|
||||||
|
darkTheme = true
|
||||||
|
) {
|
||||||
|
AirPodsSettingsScreen(dev = null, service = AirPodsService(), navController = rememberNavController(), isConnected = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@file:OptIn(ExperimentalHazeMaterialsApi::class)
|
@file:OptIn(ExperimentalHazeMaterialsApi::class)
|
||||||
|
|
||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln.screens
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
@@ -13,6 +13,7 @@ import android.os.Build
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -34,7 +35,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
import androidx.compose.material3.TextField
|
||||||
@@ -49,7 +49,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.luminance
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
@@ -62,6 +61,9 @@ import dev.chrisbanes.haze.haze
|
|||||||
import dev.chrisbanes.haze.hazeChild
|
import dev.chrisbanes.haze.hazeChild
|
||||||
import dev.chrisbanes.haze.materials.CupertinoMaterials
|
import dev.chrisbanes.haze.materials.CupertinoMaterials
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
|
import me.kavishdevar.aln.AirPodsNotifications
|
||||||
|
import me.kavishdevar.aln.AirPodsService
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@@ -97,10 +99,10 @@ fun DebugScreen(navController: NavController) {
|
|||||||
),
|
),
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = Color.Transparent
|
containerColor = Color.Transparent
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF000000)
|
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
|
||||||
else Color(0xFFF2F2F7),
|
else Color(0xFFF2F2F7),
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val receiver = object : BroadcastReceiver() {
|
val receiver = object : BroadcastReceiver() {
|
||||||
@@ -113,7 +115,7 @@ fun DebugScreen(navController: NavController) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(context) {
|
LaunchedEffect(context) {
|
||||||
val intentFilter = IntentFilter(AirPodsNotifications.AIRPODS_DATA)
|
val intentFilter = IntentFilter(AirPodsNotifications.Companion.AIRPODS_DATA)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
|
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
|
||||||
}
|
}
|
||||||
@@ -166,7 +168,7 @@ fun DebugScreen(navController: NavController) {
|
|||||||
Text(
|
Text(
|
||||||
text = if (isSent) message.substring(1) else message,
|
text = if (isSent) message.substring(1) else message,
|
||||||
fontFamily = FontFamily(Font(R.font.hack)),
|
fontFamily = FontFamily(Font(R.font.hack)),
|
||||||
color = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(
|
color = if (isSystemInDarkTheme()) Color(
|
||||||
0xFF000000
|
0xFF000000
|
||||||
)
|
)
|
||||||
else Color(0xFF000000),
|
else Color(0xFF000000),
|
||||||
@@ -202,7 +204,7 @@ fun DebugScreen(navController: NavController) {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF1C1B20) else Color(0xFFF2F2F7)),
|
.background(if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7)),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
val packet = remember { mutableStateOf(TextFieldValue("")) }
|
val packet = remember { mutableStateOf(TextFieldValue("")) }
|
||||||
@@ -227,14 +229,14 @@ fun DebugScreen(navController: NavController) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TextFieldDefaults.colors(
|
colors = TextFieldDefaults.colors(
|
||||||
focusedContainerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF1C1B20) else Color(0xFFF2F2F7),
|
focusedContainerColor = if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7),
|
||||||
unfocusedContainerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF1C1B20) else Color(0xFFF2F2F7),
|
unfocusedContainerColor = if (isSystemInDarkTheme()) Color(0xFF1C1B20) else Color(0xFFF2F2F7),
|
||||||
focusedIndicatorColor = Color.Transparent,
|
focusedIndicatorColor = Color.Transparent,
|
||||||
unfocusedIndicatorColor = Color.Transparent,
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
focusedTextColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black,
|
focusedTextColor = if (isSystemInDarkTheme()) Color.White else Color.Black,
|
||||||
unfocusedTextColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black.copy(alpha = 0.6f),
|
unfocusedTextColor = if (isSystemInDarkTheme()) Color.White else Color.Black.copy(alpha = 0.6f),
|
||||||
focusedLabelColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White.copy(alpha = 0.6f) else Color.Black,
|
focusedLabelColor = if (isSystemInDarkTheme()) Color.White.copy(alpha = 0.6f) else Color.Black,
|
||||||
unfocusedLabelColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f),
|
unfocusedLabelColor = if (isSystemInDarkTheme()) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f),
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
)
|
)
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln.screens
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -21,7 +22,6 @@ import androidx.compose.material3.CheckboxDefaults
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -35,7 +35,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
import androidx.compose.ui.graphics.luminance
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.imageResource
|
import androidx.compose.ui.res.imageResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
@@ -45,6 +44,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import me.kavishdevar.aln.R
|
||||||
|
import me.kavishdevar.aln.ServiceManager
|
||||||
|
|
||||||
@Composable()
|
@Composable()
|
||||||
fun RightDivider() {
|
fun RightDivider() {
|
||||||
@@ -64,7 +65,7 @@ fun LongPress(navController: NavController, name: String) {
|
|||||||
val transparencyChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_transparency", false)) }
|
val transparencyChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_transparency", false)) }
|
||||||
val adaptiveChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_adaptive", false)) }
|
val adaptiveChecked = remember { mutableStateOf(sharedPreferences.getBoolean("long_press_adaptive", false)) }
|
||||||
Log.d("LongPress", "offChecked: ${offChecked.value}, ncChecked: ${ncChecked.value}, transparencyChecked: ${transparencyChecked.value}, adaptiveChecked: ${adaptiveChecked.value}")
|
Log.d("LongPress", "offChecked: ${offChecked.value}, ncChecked: ${ncChecked.value}, transparencyChecked: ${transparencyChecked.value}, adaptiveChecked: ${adaptiveChecked.value}")
|
||||||
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val isDarkTheme = isSystemInDarkTheme()
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -81,7 +82,7 @@ fun LongPress(navController: NavController, name: String) {
|
|||||||
onClick = {
|
onClick = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
@@ -105,7 +106,7 @@ fun LongPress(navController: NavController, name: String) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(0xFF000000)
|
containerColor = if (isSystemInDarkTheme()) Color(0xFF000000)
|
||||||
else Color(0xFFF2F2F7),
|
else Color(0xFFF2F2F7),
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||||
@@ -160,7 +161,7 @@ fun LongPressElement (name: String, checked: MutableState<Boolean>, id: String,
|
|||||||
val sharedPreferences =
|
val sharedPreferences =
|
||||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||||
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
|
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
|
||||||
val darkMode = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
val darkMode = isSystemInDarkTheme()
|
||||||
val textColor = if (darkMode) Color.White else Color.Black
|
val textColor = if (darkMode) Color.White else Color.Black
|
||||||
val desc = when (name) {
|
val desc = when (name) {
|
||||||
"Off" -> "Turns off noise management"
|
"Off" -> "Turns off noise management"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln.services
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
@@ -8,6 +8,9 @@ import android.content.IntentFilter
|
|||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import me.kavishdevar.aln.AirPodsNotifications
|
||||||
|
import me.kavishdevar.aln.NoiseControlMode
|
||||||
|
import me.kavishdevar.aln.ServiceManager
|
||||||
|
|
||||||
class AirPodsQSService: TileService() {
|
class AirPodsQSService: TileService() {
|
||||||
private val ancModes = listOf(NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name)
|
private val ancModes = listOf(NoiseControlMode.NOISE_CANCELLATION.name, NoiseControlMode.TRANSPARENCY.name, NoiseControlMode.ADAPTIVE.name)
|
||||||
@@ -46,18 +49,19 @@ class AirPodsQSService: TileService() {
|
|||||||
|
|
||||||
availabilityReceiver = object : BroadcastReceiver() {
|
availabilityReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (intent.action == AirPodsNotifications.AIRPODS_CONNECTED) {
|
if (intent.action == AirPodsNotifications.Companion.AIRPODS_CONNECTED) {
|
||||||
qsTile.state = Tile.STATE_ACTIVE
|
qsTile.state = Tile.STATE_ACTIVE
|
||||||
qsTile.updateTile()
|
qsTile.updateTile()
|
||||||
}
|
}
|
||||||
else if (intent.action == AirPodsNotifications.AIRPODS_DISCONNECTED) {
|
else if (intent.action == AirPodsNotifications.Companion.AIRPODS_DISCONNECTED) {
|
||||||
qsTile.state = Tile.STATE_UNAVAILABLE
|
qsTile.state = Tile.STATE_UNAVAILABLE
|
||||||
qsTile.updateTile()
|
qsTile.updateTile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerReceiver(ancStatusReceiver, IntentFilter(AirPodsNotifications.ANC_DATA), RECEIVER_EXPORTED)
|
registerReceiver(ancStatusReceiver,
|
||||||
|
IntentFilter(AirPodsNotifications.Companion.ANC_DATA), RECEIVER_EXPORTED)
|
||||||
|
|
||||||
qsTile.state = if (ServiceManager.getService()?.isConnected == true) Tile.STATE_ACTIVE else Tile.STATE_UNAVAILABLE
|
qsTile.state = if (ServiceManager.getService()?.isConnected == true) Tile.STATE_ACTIVE else Tile.STATE_UNAVAILABLE
|
||||||
val ancIndex = ServiceManager.getService()?.getANC()
|
val ancIndex = ServiceManager.getService()?.getANC()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln.utils
|
||||||
|
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -16,16 +16,36 @@ object MediaController {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
fun sendPause() {
|
fun sendPause() {
|
||||||
if (audioManager.isMusicActive) {
|
if (audioManager.isMusicActive) {
|
||||||
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PAUSE))
|
audioManager.dispatchMediaKeyEvent(
|
||||||
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PAUSE))
|
KeyEvent(
|
||||||
|
KeyEvent.ACTION_DOWN,
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PAUSE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
audioManager.dispatchMediaKeyEvent(
|
||||||
|
KeyEvent(
|
||||||
|
KeyEvent.ACTION_UP,
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PAUSE
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun sendPlay() {
|
fun sendPlay() {
|
||||||
if (!audioManager.isMusicActive) {
|
if (!audioManager.isMusicActive) {
|
||||||
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY))
|
audioManager.dispatchMediaKeyEvent(
|
||||||
audioManager.dispatchMediaKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY))
|
KeyEvent(
|
||||||
|
KeyEvent.ACTION_DOWN,
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PLAY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
audioManager.dispatchMediaKeyEvent(
|
||||||
|
KeyEvent(
|
||||||
|
KeyEvent.ACTION_UP,
|
||||||
|
KeyEvent.KEYCODE_MEDIA_PLAY
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user