mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-10 05:15:02 +00:00
try to design ios-like settings activity
This commit is contained in:
@@ -1,35 +1,48 @@
|
|||||||
package me.kavishdevar.aln
|
package me.kavishdevar.aln
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
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.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SegmentedButton
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.SegmentedButtonDefaults
|
import androidx.compose.material3.SliderDefaults
|
||||||
import androidx.compose.material3.ShapeDefaults
|
|
||||||
import androidx.compose.material3.Shapes
|
|
||||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -37,10 +50,22 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
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.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.luminance
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.res.imageResource
|
||||||
|
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.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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 me.kavishdevar.aln.ui.theme.ALNTheme
|
import me.kavishdevar.aln.ui.theme.ALNTheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@@ -49,17 +74,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
val address = mutableStateOf("28:2D:7F:C2:05:5B")
|
||||||
// val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
|
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java)
|
||||||
// val bluetoothAdapter = bluetoothManager.adapter
|
val bluetoothAdapter = bluetoothManager.adapter
|
||||||
// val device = bluetoothAdapter.getRemoteDevice("28:2d:7f:c2:05:5b")
|
val device = bluetoothAdapter.getRemoteDevice(address.value)
|
||||||
// val deviceName = device.name
|
|
||||||
// val deviceAddress = device.address
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
ALNTheme {
|
ALNTheme {
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold { innerPadding ->
|
||||||
AirPodsSettingsScreen(innerPadding)
|
AirPodsSettingsScreen(innerPadding, device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,81 +90,550 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AirPodsSettingsScreen(paddingValues: PaddingValues) {
|
fun StyledSwitch(
|
||||||
var ancMode by remember { mutableStateOf("Off") }
|
checked: Boolean,
|
||||||
var deviceName by remember { mutableStateOf(TextFieldValue("Kavish's AirPods Pro")) }
|
onCheckedChange: (Boolean) -> Unit
|
||||||
var conversationalAwarenessEnabled by remember { mutableStateOf(false) }
|
) {
|
||||||
var earDetectionEnabled by remember { mutableStateOf(true) }
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
|
val thumbColor = Color.White
|
||||||
|
val trackColor = if (checked) Color(0xFF34C759) else if (isDarkTheme) Color(0xFF262629) else Color(0xFFD1D1D6)
|
||||||
|
|
||||||
|
// Animate the horizontal offset of the thumb
|
||||||
|
val thumbOffsetX by animateDpAsState(targetValue = if (checked) 20.dp else 0.dp, label = "Test")
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(51.dp)
|
||||||
|
.height(31.dp)
|
||||||
|
.clip(RoundedCornerShape(15.dp))
|
||||||
|
.background(trackColor) // Dynamic track background
|
||||||
|
.padding(horizontal = 3.dp),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.offset(x = thumbOffsetX) // Animate the offset for smooth transition
|
||||||
|
.size(27.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(thumbColor) // Dynamic thumb color
|
||||||
|
.clickable { onCheckedChange(!checked) } // Make the switch clickable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StyledTextField(
|
||||||
|
name: String,
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF0E0E0E) else Color(0xFFFFFFFF)
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
val cursorColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
backgroundColor,
|
||||||
|
RoundedCornerShape(10.dp)
|
||||||
|
) // Dynamic background based on theme
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = textColor // Text color based on theme
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
textStyle = TextStyle(
|
||||||
|
color = textColor, // Dynamic text color
|
||||||
|
fontSize = 16.sp,
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(cursorColor), // Dynamic cursor color
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth() // Ensures text field takes remaining available space
|
||||||
|
.padding(start = 8.dp) // Padding to adjust spacing between text field and icon
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BatteryIndicator(batteryPercentage: Int) {
|
||||||
|
val batteryOutlineColor = Color(0xFFBFBFBF) // Light gray outline
|
||||||
|
val batteryFillColor = if (batteryPercentage > 30) Color(0xFF30D158) else Color(0xFFFC3C3C)
|
||||||
|
val batteryTextColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
|
||||||
|
// Battery indicator dimensions
|
||||||
|
val batteryWidth = 30.dp
|
||||||
|
val batteryHeight = 15.dp
|
||||||
|
val batteryCornerRadius = 4.dp
|
||||||
|
val tipWidth = 3.dp
|
||||||
|
val tipHeight = batteryHeight * 0.3f
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
// Row for battery icon and tip
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp) // Padding between icon and percentage text
|
||||||
|
) {
|
||||||
|
// Battery Icon
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(batteryWidth)
|
||||||
|
.height(batteryHeight)
|
||||||
|
.border(1.dp, batteryOutlineColor, RoundedCornerShape(batteryCornerRadius))
|
||||||
|
) {
|
||||||
|
// Battery Fill
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(2.dp)
|
||||||
|
.width(batteryWidth * (batteryPercentage / 100f))
|
||||||
|
.background(batteryFillColor, RoundedCornerShape(2.dp))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery Tip (Protrusion)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(tipWidth)
|
||||||
|
.height(tipHeight)
|
||||||
|
.padding(start = 1.dp)
|
||||||
|
.background(
|
||||||
|
batteryOutlineColor,
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = 0.dp,
|
||||||
|
topEnd = 5.dp,
|
||||||
|
bottomStart = 5.dp,
|
||||||
|
bottomEnd = 4.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery Percentage Text
|
||||||
|
Text(
|
||||||
|
text = "$batteryPercentage%",
|
||||||
|
color = batteryTextColor,
|
||||||
|
style = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Composable
|
||||||
|
fun AirPodsSettingsScreen(paddingValues: PaddingValues, device: BluetoothDevice?) {
|
||||||
|
var deviceName by remember { mutableStateOf(TextFieldValue(device?.name ?: "Kavish's AirPods Pro")) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
|
.padding(vertical = 24.dp, horizontal = 12.dp)
|
||||||
) {
|
) {
|
||||||
// Device Name Section
|
Row {
|
||||||
Text(text = "Name", style = MaterialTheme.typography.titleMedium)
|
Column (
|
||||||
BasicTextField(
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
value = deviceName,
|
){
|
||||||
onValueChange = { deviceName = it },
|
// using this temporarily until i can find an image of only the buds
|
||||||
modifier = Modifier
|
Image(
|
||||||
.fillMaxWidth()
|
bitmap = ImageBitmap.imageResource(R.drawable.pro_2),
|
||||||
.padding(vertical = 8.dp)
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxWidth(0.5f)
|
||||||
|
)
|
||||||
|
BatteryIndicator(batteryPercentage = 10)
|
||||||
|
}
|
||||||
|
Column (
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
){
|
||||||
|
Image(
|
||||||
|
bitmap = ImageBitmap.imageResource(R.drawable.pro_2),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
BatteryIndicator(batteryPercentage = 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StyledTextField(
|
||||||
|
name = "Name",
|
||||||
|
value = deviceName.text,
|
||||||
|
onValueChange = { deviceName = TextFieldValue(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(thickness = 2.dp, modifier = Modifier.padding(vertical = 8.dp), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
NoiseControlSettings()
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
AudioSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ANC Mode Section
|
@Composable
|
||||||
Text(text = "Noise Control", style = MaterialTheme.typography.titleMedium)
|
fun NoiseControlSlider() {
|
||||||
ANCOptions(ancMode) { selectedMode -> ancMode = selectedMode }
|
val sliderValue = remember { mutableStateOf(0f) }
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
HorizontalDivider(thickness = 2.dp, modifier = Modifier.padding(vertical = 8.dp), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f))
|
val trackColor = if (isDarkTheme) Color(0xFFB3B3B3) else Color(0xFFD9D9D9)
|
||||||
|
val activeTrackColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFF007AFF)
|
||||||
|
val thumbColor = if (isDarkTheme) Color(0xFFFFFFFF) else Color(0xFF007AFF)
|
||||||
|
val labelTextColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
|
||||||
// Conversational Awareness Toggle
|
Column(
|
||||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
modifier = Modifier
|
||||||
Text(text = "Conversational Awareness", modifier = Modifier.weight(1f))
|
.fillMaxWidth()
|
||||||
Switch(
|
.padding(horizontal = 8.dp),
|
||||||
checked = conversationalAwarenessEnabled,
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
onCheckedChange = { conversationalAwarenessEnabled = it }
|
) {
|
||||||
|
// Slider
|
||||||
|
Slider(
|
||||||
|
value = sliderValue.value,
|
||||||
|
onValueChange = { sliderValue.value = it },
|
||||||
|
valueRange = 0f..100f,
|
||||||
|
steps = 99,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
thumbColor = thumbColor,
|
||||||
|
activeTrackColor = activeTrackColor,
|
||||||
|
inactiveTrackColor = trackColor
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
|
|
||||||
HorizontalDivider(thickness = 2.dp, modifier = Modifier.padding(vertical = 8.dp), color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f))
|
// Labels
|
||||||
|
Row(
|
||||||
// Ear Detection Toggle
|
modifier = Modifier.fillMaxWidth(),
|
||||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
Text(text = "Automatic Ear Detection", modifier = Modifier.weight(1f))
|
) {
|
||||||
Switch(
|
Text(
|
||||||
checked = earDetectionEnabled,
|
text = "Less Noise",
|
||||||
onCheckedChange = { earDetectionEnabled = it }
|
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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ANCOptions(selectedMode: String, onModeChange: (String) -> Unit) {
|
fun AudioSettings() {
|
||||||
val modes = listOf("Off", "Transparency", "Noise Cancellation")
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
var conversationalAwarenessEnabled by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
SingleChoiceSegmentedButtonRow {
|
Text(
|
||||||
modes.forEach { mode ->
|
text = "AUDIO",
|
||||||
SegmentedButton(
|
style = TextStyle(
|
||||||
selected = (mode == selectedMode),
|
fontSize = 14.sp,
|
||||||
onClick = { onModeChange(mode) },
|
fontWeight = FontWeight.Light,
|
||||||
modifier = Modifier.weight(1f),
|
color = textColor.copy(alpha = 0.6f)
|
||||||
shape = ShapeDefaults.ExtraSmall
|
),
|
||||||
) {
|
modifier = Modifier.padding(8.dp, bottom = 2.dp)
|
||||||
Text(
|
)
|
||||||
text = mode,
|
val backgroundColor = if (isDarkTheme) Color(0xFF0E0E0E) else Color(0xFFFFFFFF)
|
||||||
textAlign = TextAlign.Center
|
val isPressed = remember { mutableStateOf(false) }
|
||||||
|
Column (
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor, RoundedCornerShape(12.dp))
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(shape = RoundedCornerShape(12.dp), color = if (isPressed.value) Color(0xFFE0E0E0) else Color.Transparent)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||||
|
.pointerInput(Unit) { // Detect press state for iOS-like effect
|
||||||
|
detectTapGestures(
|
||||||
|
onPress = {
|
||||||
|
isPressed.value = true
|
||||||
|
tryAwaitRelease() // Wait until release
|
||||||
|
isPressed.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.clickable(
|
||||||
|
indication = null, // Disable ripple effect
|
||||||
|
interactionSource = remember { MutableInteractionSource() } // Required for clickable
|
||||||
|
) {
|
||||||
|
conversationalAwarenessEnabled = !conversationalAwarenessEnabled
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(text = "Conversational Awareness", modifier = Modifier.weight(1f), fontSize = 16.sp)
|
||||||
|
StyledSwitch(
|
||||||
|
checked = conversationalAwarenessEnabled,
|
||||||
|
onCheckedChange = { conversationalAwarenessEnabled = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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 = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
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(8.dp, top = 2.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = textColor.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
NoiseControlSlider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoiseControlSettings() {
|
||||||
|
val isDarkTheme = MaterialTheme.colorScheme.surface.luminance() < 0.5
|
||||||
|
|
||||||
|
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFF2F2F7)
|
||||||
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
val textColorSelected = if (isDarkTheme) Color.White else Color.Black
|
||||||
|
val selectedBackground = if (isDarkTheme) Color(0xFF090909) else Color(0xFFFFFFFF)
|
||||||
|
|
||||||
|
val noiseControlMode = remember { mutableStateOf(NoiseControlMode.OFF) }
|
||||||
|
|
||||||
|
// val paddingAnim by animateDpAsState(
|
||||||
|
// targetValue = when (noiseControlMode.value) {
|
||||||
|
// NoiseControlMode.OFF -> 0.dp
|
||||||
|
// NoiseControlMode.TRANSPARENCY -> 150.dp
|
||||||
|
// NoiseControlMode.ADAPTIVE -> 250.dp
|
||||||
|
// NoiseControlMode.NOISE_CANCELLATION -> 350.dp
|
||||||
|
// }, label = ""
|
||||||
|
// )
|
||||||
|
|
||||||
|
val d1a = remember { mutableStateOf(0f) }
|
||||||
|
val d2a = remember { mutableStateOf(0f) }
|
||||||
|
val d3a = remember { mutableStateOf(0f) }
|
||||||
|
|
||||||
|
fun onModeSelected(mode: NoiseControlMode) {
|
||||||
|
noiseControlMode.value = mode
|
||||||
|
when (mode) {
|
||||||
|
NoiseControlMode.NOISE_CANCELLATION -> {
|
||||||
|
d1a.value = 1f
|
||||||
|
d2a.value = 1f
|
||||||
|
d3a.value = 0f
|
||||||
|
}
|
||||||
|
NoiseControlMode.OFF -> {
|
||||||
|
d1a.value = 0f
|
||||||
|
d2a.value = 1f
|
||||||
|
d3a.value = 1f
|
||||||
|
}
|
||||||
|
NoiseControlMode.ADAPTIVE -> {
|
||||||
|
d1a.value = 1f
|
||||||
|
d2a.value = 0f
|
||||||
|
d3a.value = 0f
|
||||||
|
}
|
||||||
|
NoiseControlMode.TRANSPARENCY -> {
|
||||||
|
d1a.value = 0f
|
||||||
|
d2a.value = 0f
|
||||||
|
d3a.value = 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(65.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
// Box(
|
||||||
|
// modifier = Modifier
|
||||||
|
// .fillMaxHeight()
|
||||||
|
// .width(80.dp)
|
||||||
|
// .offset(x = paddingAnim)
|
||||||
|
// .background(selectedBackground, RoundedCornerShape(8.dp))
|
||||||
|
// )
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(backgroundColor, RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = Icons.Default.Person, // Replace with your icon
|
||||||
|
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.value),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
)
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = Icons.Default.Person, // Replace with your icon
|
||||||
|
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.value),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
)
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = Icons.Default.Person, // Replace with your icon
|
||||||
|
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.value),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f),
|
||||||
|
)
|
||||||
|
NoiseControlButton(
|
||||||
|
icon = Icons.Default.Person, // Replace with your icon
|
||||||
|
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(showBackground = true)
|
@Composable
|
||||||
|
fun NoiseControlButton(
|
||||||
|
icon: ImageVector,
|
||||||
|
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(6.dp))
|
||||||
|
.clickable(onClick = onClick, indication = null, interactionSource = remember { MutableInteractionSource() }),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = textColor,
|
||||||
|
modifier = Modifier.size(32.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class NoiseControlMode {
|
||||||
|
OFF, TRANSPARENCY, ADAPTIVE, NOISE_CANCELLATION
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, name = "AirPods Settings",
|
||||||
|
uiMode = Configuration.UI_MODE_NIGHT_YES, showSystemUi = true,
|
||||||
|
device = "spec:width=411dp,height=891dp"
|
||||||
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewAirPodsSettingsScreen() {
|
fun PreviewAirPodsSettingsScreen() {
|
||||||
AirPodsSettingsScreen(PaddingValues(16.dp))
|
AirPodsSettingsScreen(PaddingValues(8.dp), null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="154.38dp"
|
||||||
|
android:height="148.1dp"
|
||||||
|
android:viewportWidth="154.38"
|
||||||
|
android:viewportHeight="148.1">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M0,0h154.38v148.1h-154.38z"
|
||||||
|
android:strokeAlpha="0"
|
||||||
|
android:fillAlpha="0"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M134.81,86.77C137.13,88.33 140,87.77 141.69,85.39C149.69,74.39 154.38,59.27 154.38,43.83C154.38,28.39 149.75,13.14 141.69,2.27C140,-0.17 137.13,-0.67 134.81,0.89C132.5,2.46 132.13,5.39 133.94,7.96C140.81,17.58 144.69,30.52 144.69,43.83C144.69,57.14 140.69,69.96 133.94,79.71C132.19,82.27 132.5,85.21 134.81,86.77Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillAlpha="0.85"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M111.69,70.46C113.75,71.89 116.69,71.46 118.38,69.02C123.19,62.71 126.06,53.39 126.06,43.83C126.06,34.27 123.19,25.02 118.38,18.58C116.69,16.21 113.75,15.71 111.69,17.21C109.13,18.96 108.75,22.08 110.69,24.64C114.31,29.64 116.38,36.58 116.38,43.83C116.38,51.08 114.25,57.96 110.69,63.02C108.81,65.64 109.13,68.64 111.69,70.46Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillAlpha="0.85"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M26.13,130.21L104.63,130.21C114.44,130.21 117.94,127.39 117.94,121.89C117.94,105.77 97.75,83.52 65.38,83.52C32.94,83.52 12.75,105.77 12.75,121.89C12.75,127.39 16.25,130.21 26.13,130.21ZM65.38,72.33C78.81,72.33 90.38,60.33 90.38,44.71C90.38,29.27 78.75,17.83 65.38,17.83C52,17.83 40.38,29.52 40.38,44.83C40.38,60.33 52,72.33 65.38,72.33Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillAlpha="0.85"/>
|
||||||
|
</vector>
|
||||||
BIN
android/app/src/main/res/drawable/pro_2.png
Normal file
BIN
android/app/src/main/res/drawable/pro_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
Reference in New Issue
Block a user