mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-01-28 22:01:50 +00:00
move files across computers
This commit is contained in:
@@ -35,10 +35,17 @@ import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -48,6 +55,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -183,43 +191,104 @@ fun Main() {
|
||||
context.registerReceiver(connectionStatusReceiver, filter)
|
||||
}
|
||||
Log.d("MainActivity", "Registered Receiver")
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "settings",
|
||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(300)) },
|
||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }, animationSpec = tween(300)) },
|
||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(300)) },
|
||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(300)) }
|
||||
Box (
|
||||
modifier = Modifier
|
||||
.padding(0.dp)
|
||||
.fillMaxSize()
|
||||
.background(if (isSystemInDarkTheme()) Color.Black else Color(0xFFF2F2F7))
|
||||
) {
|
||||
composable("settings") {
|
||||
if (airPodsService.value != null) {
|
||||
AirPodsSettingsScreen(
|
||||
dev = airPodsService.value?.device,
|
||||
service = airPodsService.value!!,
|
||||
navController = navController,
|
||||
isConnected = isConnected.value,
|
||||
isRemotelyConnected = isRemotelyConnected.value
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "settings",
|
||||
enterTransition = {
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { it },
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
) + scaleIn(
|
||||
initialScale = 0.85f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { -it },
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
) + scaleOut(
|
||||
targetScale = 0.85f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { -it },
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
) + scaleIn(
|
||||
initialScale = 0.85f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { it },
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
) + scaleOut(
|
||||
targetScale = 0.85f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
composable("debug") {
|
||||
DebugScreen(navController = navController)
|
||||
}
|
||||
composable("long_press/{bud}") { navBackStackEntry ->
|
||||
LongPress(
|
||||
navController = navController,
|
||||
name = navBackStackEntry.arguments?.getString("bud")!!
|
||||
)
|
||||
}
|
||||
composable("rename") { navBackStackEntry ->
|
||||
RenameScreen(navController)
|
||||
}
|
||||
composable("app_settings") {
|
||||
AppSettingsScreen(navController)
|
||||
) {
|
||||
composable("settings") {
|
||||
if (airPodsService.value != null) {
|
||||
AirPodsSettingsScreen(
|
||||
dev = airPodsService.value?.device,
|
||||
service = airPodsService.value!!,
|
||||
navController = navController,
|
||||
isConnected = isConnected.value,
|
||||
isRemotelyConnected = isRemotelyConnected.value
|
||||
)
|
||||
}
|
||||
}
|
||||
composable("debug") {
|
||||
DebugScreen(navController = navController)
|
||||
}
|
||||
composable("long_press/{bud}") { navBackStackEntry ->
|
||||
LongPress(
|
||||
navController = navController,
|
||||
name = navBackStackEntry.arguments?.getString("bud")!!
|
||||
)
|
||||
}
|
||||
composable("rename") { navBackStackEntry ->
|
||||
RenameScreen(navController)
|
||||
}
|
||||
composable("app_settings") {
|
||||
AppSettingsScreen(navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serviceConnection = remember {
|
||||
object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
|
||||
@@ -131,7 +131,7 @@ fun AccessibilitySettings(service: AirPodsService, sharedPreferences: SharedPref
|
||||
|
||||
SinglePodANCSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||
VolumeControlSwitch(service = service, sharedPreferences = sharedPreferences)
|
||||
TransparencySettings(service = service, sharedPreferences = sharedPreferences)
|
||||
// TransparencySettings(service = service, sharedPreferences = sharedPreferences)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
package me.kavishdevar.aln.composables
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -38,6 +40,7 @@ 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
|
||||
@@ -51,6 +54,8 @@ fun IndependentToggle(name: String, service: AirPodsService, functionName: Strin
|
||||
|
||||
val snakeCasedName = name.replace(Regex("[\\W\\s]+"), "_").lowercase()
|
||||
var checked by remember { mutableStateOf(default) }
|
||||
var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) }
|
||||
val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500))
|
||||
|
||||
LaunchedEffect(sharedPreferences) {
|
||||
checked = sharedPreferences.getBoolean(snakeCasedName, true)
|
||||
@@ -58,19 +63,25 @@ fun IndependentToggle(name: String, service: AirPodsService, functionName: Strin
|
||||
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()
|
||||
.background(animatedBackgroundColor, RoundedCornerShape(14.dp))
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9)
|
||||
tryAwaitRelease()
|
||||
backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||
},
|
||||
onTap = {
|
||||
checked = !checked
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putBoolean(snakeCasedName, checked)
|
||||
.apply()
|
||||
|
||||
val method = service::class.java.getMethod(functionName, Boolean::class.java)
|
||||
method.invoke(service, checked)
|
||||
val method = service::class.java.getMethod(functionName, Boolean::class.java)
|
||||
method.invoke(service, checked)
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
{
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
package me.kavishdevar.aln.composables
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -44,6 +46,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -62,9 +65,11 @@ fun NameField(
|
||||
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
|
||||
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||
var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) }
|
||||
val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500))
|
||||
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val cursorColor = if (isFocused) { // Show cursor only when focused
|
||||
val cursorColor = if (isFocused) {
|
||||
if (isDarkTheme) Color.White else Color.Black
|
||||
} else {
|
||||
Color.Transparent
|
||||
@@ -72,11 +77,19 @@ fun NameField(
|
||||
|
||||
Box (
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
navController.navigate("rename")
|
||||
}
|
||||
)
|
||||
.background(animatedBackgroundColor, RoundedCornerShape(14.dp))
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9)
|
||||
tryAwaitRelease()
|
||||
backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||
},
|
||||
onTap = {
|
||||
navController.navigate("rename")
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -84,7 +97,7 @@ fun NameField(
|
||||
.fillMaxWidth()
|
||||
.height(55.dp)
|
||||
.background(
|
||||
backgroundColor,
|
||||
animatedBackgroundColor,
|
||||
RoundedCornerShape(14.dp)
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
package me.kavishdevar.aln.composables
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -34,8 +36,13 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.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
|
||||
@@ -44,29 +51,38 @@ import androidx.navigation.NavController
|
||||
|
||||
@Composable
|
||||
fun NavigationButton(to: String, name: String, navController: NavController) {
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
var backgroundColor by remember { mutableStateOf(if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) }
|
||||
val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
if (isSystemInDarkTheme()) Color(
|
||||
0xFF1C1C1E
|
||||
) else Color(0xFFFFFFFF), RoundedCornerShape(14.dp)
|
||||
)
|
||||
.background(animatedBackgroundColor, RoundedCornerShape(14.dp))
|
||||
.height(55.dp)
|
||||
.clickable {
|
||||
navController.navigate(to)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
backgroundColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9)
|
||||
tryAwaitRelease()
|
||||
backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||
},
|
||||
onTap = {
|
||||
navController.navigate(to)
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||
color = if (isDarkTheme) 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
|
||||
contentColor = if (isDarkTheme) Color.White else Color.Black
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp)
|
||||
|
||||
@@ -170,7 +170,7 @@ fun NoiseControlSettings(service: AirPodsService) {
|
||||
context.registerReceiver(noiseControlReceiver, noiseControlIntentFilter)
|
||||
}
|
||||
|
||||
Text(// all caps
|
||||
Text(
|
||||
text = stringResource(R.string.noise_control).uppercase(),
|
||||
style = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
@@ -182,7 +182,7 @@ fun NoiseControlSettings(service: AirPodsService) {
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp) // Adjusted padding
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val buttonCount = if (offListeningMode.value) 4 else 3
|
||||
@@ -229,10 +229,9 @@ fun NoiseControlSettings(service: AirPodsService) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp) // Adjusted height
|
||||
.height(60.dp)
|
||||
.background(backgroundColor, RoundedCornerShape(14.dp))
|
||||
) {
|
||||
// First: Background Row (just for visual)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
@@ -327,7 +326,6 @@ fun NoiseControlSettings(service: AirPodsService) {
|
||||
)
|
||||
}
|
||||
|
||||
// Button row (top layer)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -387,12 +385,11 @@ fun NoiseControlSettings(service: AirPodsService) {
|
||||
}
|
||||
}
|
||||
|
||||
// Labels row
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
.padding(top = 2.dp)
|
||||
.padding(horizontal = 4.dp)
|
||||
.padding(top = 4.dp)
|
||||
) {
|
||||
if (offListeningMode.value) {
|
||||
Text(
|
||||
@@ -429,8 +426,8 @@ fun NoiseControlSettings(service: AirPodsService) {
|
||||
}
|
||||
}
|
||||
|
||||
@Preview@Composable
|
||||
@Preview()
|
||||
@Composable
|
||||
fun NoiseControlSettingsPreview() {
|
||||
NoiseControlSettings(AirPodsService())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -283,7 +283,7 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
||||
)
|
||||
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!)",
|
||||
text = "Please connect your AirPods to access settings.",
|
||||
style = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Light,
|
||||
|
||||
@@ -21,12 +21,7 @@
|
||||
package me.kavishdevar.aln.screens
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
@@ -65,6 +60,8 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -87,7 +84,7 @@ import dev.chrisbanes.haze.materials.CupertinoMaterials
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import me.kavishdevar.aln.R
|
||||
import me.kavishdevar.aln.services.AirPodsService
|
||||
import me.kavishdevar.aln.services.ServiceManager
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "UnspecifiedRegisterReceiverFlag")
|
||||
@@ -96,24 +93,13 @@ fun DebugScreen(navController: NavController) {
|
||||
val hazeState = remember { HazeState() }
|
||||
val context = LocalContext.current
|
||||
val listState = rememberLazyListState()
|
||||
val scrollOffset by remember { derivedStateOf { listState.firstVisibleItemScrollOffset } }
|
||||
val packetLogsFlow = remember { MutableStateFlow(emptySet<String>()) }
|
||||
val expandedItems = remember { mutableStateOf(setOf<Int>()) }
|
||||
|
||||
LaunchedEffect(context) {
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
val binder = service as AirPodsService.LocalBinder
|
||||
val airPodsService = binder.getService()
|
||||
packetLogsFlow.value = airPodsService.getPacketLogs()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {}
|
||||
}
|
||||
|
||||
val intent = Intent(context, AirPodsService::class.java)
|
||||
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||
LaunchedEffect(Unit) {
|
||||
ServiceManager.getService()?.packetLogsFlow?.collect { packetLogsFlow.value = it }
|
||||
}
|
||||
|
||||
val packetLogs = packetLogsFlow.collectAsState(setOf()).value
|
||||
|
||||
Scaffold(
|
||||
@@ -150,7 +136,7 @@ fun DebugScreen(navController: NavController) {
|
||||
state = hazeState,
|
||||
style = CupertinoMaterials.thick(),
|
||||
block = {
|
||||
alpha = if (listState.firstVisibleItemIndex > 0) {
|
||||
alpha = if (scrollOffset > 0) {
|
||||
1f
|
||||
} else {
|
||||
0f
|
||||
@@ -170,7 +156,7 @@ fun DebugScreen(navController: NavController) {
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.haze(hazeState)
|
||||
.padding(top = 0.dp)
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
@@ -186,7 +172,7 @@ fun DebugScreen(navController: NavController) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp) // Reduced padding
|
||||
.padding(vertical = 2.dp, horizontal = 4.dp)
|
||||
.clickable {
|
||||
expandedItems.value = if (isExpanded) {
|
||||
expandedItems.value - index
|
||||
@@ -194,21 +180,21 @@ fun DebugScreen(navController: NavController) {
|
||||
expandedItems.value + index
|
||||
}
|
||||
},
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), // Reduced elevation
|
||||
shape = RoundedCornerShape(4.dp), // Reduced corner radius
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color.Transparent
|
||||
)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(8.dp)) { // Reduced padding
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = if (isSent) Icons.AutoMirrored.Filled.KeyboardArrowLeft else Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = if (isSent) Color.Green else Color.Red,
|
||||
modifier = Modifier.size(24.dp) // Reduced icon size
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp)) // Reduced spacing
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Column {
|
||||
Text(
|
||||
text =
|
||||
@@ -217,7 +203,7 @@ fun DebugScreen(navController: NavController) {
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
if (isExpanded) {
|
||||
Spacer(modifier = Modifier.height(4.dp)) // Reduced spacing
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = message.substring(if (isSent) 5 else 9),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
@@ -232,22 +218,7 @@ fun DebugScreen(navController: NavController) {
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
||||
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
val binder = service as AirPodsService.LocalBinder
|
||||
airPodsService.value = binder.getService()
|
||||
Log.d("AirPodsService", "Service connected")
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName) {
|
||||
airPodsService.value = null
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(context, AirPodsService::class.java)
|
||||
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||
val airPodsService = ServiceManager.getService()?.let { mutableStateOf(it) }
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -267,7 +238,7 @@ fun DebugScreen(navController: NavController) {
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
airPodsService.value?.sendPacket(packet.value.text)
|
||||
airPodsService?.value?.sendPacket(packet.value.text)
|
||||
packet.value = TextFieldValue("")
|
||||
}
|
||||
) {
|
||||
|
||||
@@ -20,8 +20,10 @@ package me.kavishdevar.aln.screens
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -46,13 +48,16 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.imageResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -155,13 +160,13 @@ fun LongPress(navController: NavController, name: String) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
|
||||
LongPressElement("Off", offChecked, "long_press_off", offListeningMode, R.drawable.noise_cancellation)
|
||||
LongPressElement("Off", offChecked, "long_press_off", offListeningMode, R.drawable.noise_cancellation, isFirst = true)
|
||||
if (offListeningMode) RightDivider()
|
||||
LongPressElement("Transparency", transparencyChecked, "long_press_transparency", resourceId = R.drawable.transparency)
|
||||
LongPressElement("Transparency", transparencyChecked, "long_press_transparency", resourceId = R.drawable.transparency, isFirst = !offListeningMode)
|
||||
RightDivider()
|
||||
LongPressElement("Adaptive", adaptiveChecked, "long_press_adaptive", resourceId = R.drawable.adaptive)
|
||||
RightDivider()
|
||||
LongPressElement("Noise Cancellation", ncChecked, "long_press_nc", resourceId = R.drawable.noise_cancellation)
|
||||
LongPressElement("Noise Cancellation", ncChecked, "long_press_nc", resourceId = R.drawable.noise_cancellation, isLast = true)
|
||||
}
|
||||
Text(
|
||||
"Press and hold the stem to cycle between the selected noise control modes.",
|
||||
@@ -176,7 +181,7 @@ fun LongPress(navController: NavController, name: String) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LongPressElement (name: String, checked: MutableState<Boolean>, id: String, enabled: Boolean = true, resourceId: Int) {
|
||||
fun LongPressElement(name: String, checked: MutableState<Boolean>, id: String, enabled: Boolean = true, resourceId: Int, isFirst: Boolean = false, isLast: Boolean = false) {
|
||||
val sharedPreferences =
|
||||
LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val offListeningMode = sharedPreferences.getBoolean("off_listening_mode", false)
|
||||
@@ -213,15 +218,30 @@ fun LongPressElement (name: String, checked: MutableState<Boolean>, id: String,
|
||||
ServiceManager.getService()
|
||||
?.updateLongPress(originalLongPressArray, newLongPressArray, offListeningMode)
|
||||
}
|
||||
val shape = when {
|
||||
isFirst -> RoundedCornerShape(topStart = 14.dp, topEnd = 14.dp)
|
||||
isLast -> RoundedCornerShape(bottomStart = 14.dp, bottomEnd = 14.dp)
|
||||
else -> RoundedCornerShape(0.dp)
|
||||
}
|
||||
var backgroundColor by remember { mutableStateOf(if (darkMode) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)) }
|
||||
val animatedBackgroundColor by animateColorAsState(targetValue = backgroundColor, animationSpec = tween(durationMillis = 500))
|
||||
if (!enabled) {
|
||||
valueChanged(false)
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(72.dp)
|
||||
.clickable(
|
||||
onClick = { valueChanged() }
|
||||
)
|
||||
.background(animatedBackgroundColor, shape)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onPress = {
|
||||
backgroundColor = if (darkMode) Color(0x40888888) else Color(0x40D9D9D9)
|
||||
tryAwaitRelease()
|
||||
backgroundColor = if (darkMode) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
|
||||
},
|
||||
onTap = { valueChanged() }
|
||||
)
|
||||
}
|
||||
.padding(horizontal = 16.dp, vertical = 0.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
|
||||
@@ -54,10 +54,12 @@ import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -65,18 +67,20 @@ import androidx.navigation.NavController
|
||||
import me.kavishdevar.aln.R
|
||||
import me.kavishdevar.aln.services.ServiceManager
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RenameScreen(navController: NavController) {
|
||||
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
val name = remember { mutableStateOf(sharedPreferences.getString("name", "") ?: "") }
|
||||
val name = remember { mutableStateOf(TextFieldValue(sharedPreferences.getString("name", "") ?: "")) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
keyboardController?.show()
|
||||
name.value = name.value.copy(selection = TextRange(name.value.text.length))
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -102,7 +106,7 @@ fun RenameScreen(navController: NavController) {
|
||||
modifier = Modifier.scale(1.5f)
|
||||
)
|
||||
Text(
|
||||
text = name.value,
|
||||
text = name.value.text,
|
||||
style = TextStyle(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
@@ -146,8 +150,8 @@ fun RenameScreen(navController: NavController) {
|
||||
value = name.value,
|
||||
onValueChange = {
|
||||
name.value = it
|
||||
sharedPreferences.edit().putString("name", it).apply()
|
||||
ServiceManager.getService()?.setName(it)
|
||||
sharedPreferences.edit().putString("name", it.text).apply()
|
||||
ServiceManager.getService()?.setName(it.text)
|
||||
},
|
||||
textStyle = TextStyle(
|
||||
color = textColor,
|
||||
@@ -167,7 +171,7 @@ fun RenameScreen(navController: NavController) {
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
name.value = ""
|
||||
name.value = TextFieldValue("")
|
||||
sharedPreferences.edit().putString("name", "").apply()
|
||||
ServiceManager.getService()?.setName("")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
@@ -57,6 +58,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import me.kavishdevar.aln.BatteryWidget
|
||||
@@ -73,7 +76,6 @@ import me.kavishdevar.aln.utils.LongPressPackets
|
||||
import me.kavishdevar.aln.utils.MediaController
|
||||
import me.kavishdevar.aln.utils.Window
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import java.io.OutputStream
|
||||
|
||||
object ServiceManager {
|
||||
private var service: AirPodsService? = null
|
||||
@@ -110,10 +112,13 @@ class AirPodsService: Service() {
|
||||
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private val packetLogKey = "packet_log"
|
||||
private val _packetLogsFlow = MutableStateFlow<Set<String>>(emptySet())
|
||||
val packetLogsFlow: StateFlow<Set<String>> get() = _packetLogsFlow
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
sharedPreferences = getSharedPreferences("packet_logs", Context.MODE_PRIVATE)
|
||||
sharedPreferences = getSharedPreferences("packet_logs", MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private fun logPacket(packet: ByteArray, source: String) {
|
||||
@@ -121,6 +126,7 @@ class AirPodsService: Service() {
|
||||
val logEntry = "$source: $packetHex"
|
||||
val logs = sharedPreferences.getStringSet(packetLogKey, mutableSetOf())?.toMutableSet() ?: mutableSetOf()
|
||||
logs.add(logEntry)
|
||||
_packetLogsFlow.value = logs
|
||||
sharedPreferences.edit().putStringSet(packetLogKey, logs).apply()
|
||||
}
|
||||
|
||||
@@ -134,6 +140,7 @@ class AirPodsService: Service() {
|
||||
|
||||
fun clearLogs() {
|
||||
clearPacketLogs() // Expose a method to clear logs
|
||||
_packetLogsFlow.value = emptySet()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
@@ -151,32 +158,6 @@ class AirPodsService: Service() {
|
||||
popupShown = true
|
||||
}
|
||||
|
||||
private fun handleMessage(message: String, outputStream: OutputStream) {
|
||||
when (message) {
|
||||
"PAUSE_MEDIA" -> MediaController.sendPause()
|
||||
"PLAY_MEDIA" -> MediaController.sendPlay()
|
||||
"CONNECT_AIRPODS" -> connectToAirPods()
|
||||
"DISCONNECT_AIRPODS" -> disconnectFromAirPods()
|
||||
else -> {
|
||||
forwardPacket(message, outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun forwardPacket(packet: String, outputStream: OutputStream) {
|
||||
val byteArray = packet.toByteArray()
|
||||
outputStream.write(byteArray)
|
||||
logPacket(byteArray, "Sent")
|
||||
}
|
||||
|
||||
private fun connectToAirPods() {
|
||||
Log.d("AirPodsQuickSwitchService", "Should connect to AirPods now.")
|
||||
}
|
||||
|
||||
private fun disconnectFromAirPods() {
|
||||
Log.d("AirPodsQuickSwitchService", "Should disconnect from AirPods now.")
|
||||
}
|
||||
|
||||
|
||||
@Suppress("ClassName")
|
||||
private object bluetoothReceiver: BroadcastReceiver() {
|
||||
@SuppressLint("MissingPermission")
|
||||
@@ -254,6 +235,7 @@ class AirPodsService: Service() {
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun startForegroundNotification() {
|
||||
val notificationChannel = NotificationChannel(
|
||||
"background_service_status",
|
||||
@@ -262,9 +244,17 @@ class AirPodsService: Service() {
|
||||
)
|
||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
|
||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(this, "background_service_status")
|
||||
.setSmallIcon(R.drawable.airpods)
|
||||
.setContentTitle("AirPods not connected")
|
||||
.setContentText("Tap to open app")
|
||||
.setContentIntent(pendingIntent)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
@@ -272,8 +262,7 @@ class AirPodsService: Service() {
|
||||
|
||||
try {
|
||||
startForeground(1, notification)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@@ -309,73 +298,88 @@ class AirPodsService: Service() {
|
||||
it.setTextViewText(
|
||||
R.id.left_battery_widget,
|
||||
batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }?.let {
|
||||
// if (it.status != BatteryStatus.DISCONNECTED) {
|
||||
"${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
// } else {
|
||||
// ""
|
||||
// }
|
||||
"${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
} ?: ""
|
||||
)
|
||||
it.setProgressBar(
|
||||
R.id.left_battery_progress,
|
||||
100,
|
||||
batteryNotification.getBattery().find { it.component == BatteryComponent.LEFT }?.level ?: 0,
|
||||
false
|
||||
)
|
||||
it.setTextViewText(
|
||||
R.id.right_battery_widget,
|
||||
batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }?.let {
|
||||
// if (it.status != BatteryStatus.DISCONNECTED) {
|
||||
"${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
// } else {
|
||||
// ""
|
||||
// }
|
||||
"${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
} ?: ""
|
||||
)
|
||||
it.setProgressBar(
|
||||
R.id.right_battery_progress,
|
||||
100,
|
||||
batteryNotification.getBattery().find { it.component == BatteryComponent.RIGHT }?.level ?: 0,
|
||||
false
|
||||
)
|
||||
it.setTextViewText(
|
||||
R.id.case_battery_widget,
|
||||
batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }?.let {
|
||||
// if (it.status != BatteryStatus.DISCONNECTED) {
|
||||
"${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
// } else {
|
||||
// ""
|
||||
// }
|
||||
"${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
} ?: ""
|
||||
)
|
||||
it.setProgressBar(
|
||||
R.id.case_battery_progress,
|
||||
100,
|
||||
batteryNotification.getBattery().find { it.component == BatteryComponent.CASE }?.level ?: 0,
|
||||
false
|
||||
)
|
||||
}
|
||||
Log.d("AirPodsService", "Updating battery widget")
|
||||
appWidgetManager.updateAppWidget(widgetIds, remoteViews)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun updateNotificationContent(connected: Boolean, airpodsName: String? = null, batteryList: List<Battery>? = null) {
|
||||
val notificationManager = getSystemService(NotificationManager::class.java)
|
||||
var updatedNotification: Notification? = null
|
||||
|
||||
val notificationIntent = Intent(this, MainActivity::class.java)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
if (connected) {
|
||||
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
||||
.setSmallIcon(R.drawable.airpods)
|
||||
.setContentTitle("""$airpodsName –${batteryList?.find { it.component == BatteryComponent.LEFT }?.let {
|
||||
.setContentTitle(airpodsName)
|
||||
.setContentText("""${batteryList?.find { it.component == BatteryComponent.LEFT }?.let {
|
||||
if (it.status != BatteryStatus.DISCONNECTED) {
|
||||
" L:${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
"L: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} ?: ""}${batteryList?.find { it.component == BatteryComponent.RIGHT }?.let {
|
||||
} ?: ""} ${batteryList?.find { it.component == BatteryComponent.RIGHT }?.let {
|
||||
if (it.status != BatteryStatus.DISCONNECTED) {
|
||||
" R:${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
"R: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} ?: ""}${batteryList?.find { it.component == BatteryComponent.CASE }?.let {
|
||||
} ?: ""} ${batteryList?.find { it.component == BatteryComponent.CASE }?.let {
|
||||
if (it.status != BatteryStatus.DISCONNECTED) {
|
||||
" C:${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
"Case: ${if (it.status == BatteryStatus.CHARGING) "⚡" else ""} ${it.level}%"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} ?: ""}""")
|
||||
.setContentIntent(pendingIntent)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
|
||||
} else {
|
||||
updatedNotification = NotificationCompat.Builder(this, "background_service_status")
|
||||
.setSmallIcon(R.drawable.airpods)
|
||||
.setContentTitle("AirPods not connected")
|
||||
.setContentText("Tap to open app")
|
||||
.setContentIntent(pendingIntent)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setOngoing(true)
|
||||
@@ -952,11 +956,6 @@ class AirPodsService: Service() {
|
||||
|
||||
var earDetectionEnabled = true
|
||||
|
||||
fun setCaseChargingSounds(enabled: Boolean) {
|
||||
val bytes = byteArrayOf(0x12, 0x3a, 0x00, 0x01, 0x00, 0x08, if (enabled) 0x00 else 0x01)
|
||||
sendPacket(bytes)
|
||||
}
|
||||
|
||||
fun setEarDetection(enabled: Boolean) {
|
||||
earDetectionEnabled = enabled
|
||||
}
|
||||
|
||||
7
android/app/src/main/res/drawable/blur_background.xml
Normal file
7
android/app/src/main/res/drawable/blur_background.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#DA000000" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
15
android/app/src/main/res/drawable/circular_progress_bar.xml
Normal file
15
android/app/src/main/res/drawable/circular_progress_bar.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromDegrees="-90"
|
||||
android:toDegrees="270">
|
||||
<shape
|
||||
android:shape="ring"
|
||||
android:innerRadiusRatio="3"
|
||||
android:thicknessRatio="8"
|
||||
android:useLevel="true">
|
||||
<gradient
|
||||
android:type="sweep"
|
||||
android:useLevel="true"
|
||||
android:startColor="#00ff00"
|
||||
android:endColor="#00ff00" />
|
||||
</shape>
|
||||
</rotate>
|
||||
10
android/app/src/main/res/drawable/ring_background.xml
Normal file
10
android/app/src/main/res/drawable/ring_background.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="6dp"
|
||||
android:color="#00ff00" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -4,70 +4,104 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/battery_widget"
|
||||
android:theme="@style/Theme.ALN.AppWidgetContainer">
|
||||
android:theme="@style/Theme.ALN.AppWidgetContainer"
|
||||
android:background="@drawable/blur_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="0dp"
|
||||
android:gravity="center">
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:contentDescription="something"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:src="@drawable/airpods_pro_left_notification"
|
||||
android:tint="@android:color/system_accent2_400"
|
||||
tools:ignore="HardcodedText" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/ring_background"
|
||||
android:layout_gravity="center">
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/airpods_pro_left_notification"
|
||||
android:tint="@android:color/system_accent2_400"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="HardcodedText" />
|
||||
</ImageView>
|
||||
<TextView
|
||||
android:id="@+id/left_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/system_accent2_400"
|
||||
android:gravity="center"
|
||||
android:text="Left"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/left_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@android:color/system_accent2_400"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Left"
|
||||
tools:ignore="HardcodedText" />
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/airpods_pro_right_notification"
|
||||
android:tint="@android:color/system_accent2_400"
|
||||
android:layout_margin="0dp"
|
||||
android:contentDescription="something"
|
||||
android:gravity="center_vertical"
|
||||
tools:ignore="HardcodedText" />
|
||||
<TextView
|
||||
android:id="@+id/right_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/ring_background"
|
||||
android:layout_gravity="center">
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/airpods_pro_right_notification"
|
||||
android:tint="@android:color/system_accent2_400"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="HardcodedText" />
|
||||
</ImageView>
|
||||
<TextView
|
||||
android:id="@+id/right_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/system_accent2_400"
|
||||
android:gravity="center"
|
||||
android:text="Right"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@android:color/system_accent2_400"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Right"
|
||||
tools:ignore="HardcodedText" />
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/airpods_pro_case_notification"
|
||||
android:tint="@android:color/system_accent2_400"
|
||||
android:contentDescription="something"
|
||||
android:layout_margin="0dp"
|
||||
android:gravity="center_vertical"
|
||||
tools:ignore="HardcodedText" />
|
||||
<TextView
|
||||
android:id="@+id/case_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@android:color/system_accent2_400"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Case"
|
||||
tools:ignore="HardcodedText" />
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/ring_background"
|
||||
android:layout_gravity="center">
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/airpods_pro_case_notification"
|
||||
android:tint="@android:color/system_accent2_400"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="HardcodedText" />
|
||||
</ImageView>
|
||||
<TextView
|
||||
android:id="@+id/case_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/system_accent2_400"
|
||||
android:gravity="center"
|
||||
android:text="Case"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
@@ -4,72 +4,125 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/battery_widget"
|
||||
android:theme="@style/Theme.ALN.AppWidgetContainer">
|
||||
android:theme="@style/Theme.ALN.AppWidgetContainer"
|
||||
android:background="@drawable/blur_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="0dp"
|
||||
android:gravity="center">
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:contentDescription="something"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:src="@drawable/airpods_pro_left_notification"
|
||||
android:tint="@color/popup_text"
|
||||
tools:ignore="HardcodedText" />
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/left_battery_widget"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@color/popup_text"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Left"
|
||||
tools:ignore="HardcodedText" />
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/airpods_pro_right_notification"
|
||||
android:tint="@color/popup_text"
|
||||
android:layout_margin="0dp"
|
||||
android:contentDescription="something"
|
||||
android:gravity="center_vertical"
|
||||
tools:ignore="HardcodedText" />
|
||||
<TextView
|
||||
android:id="@+id/right_battery_widget"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<FrameLayout
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center">
|
||||
<ProgressBar
|
||||
android:id="@+id/left_battery_progress"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:progressDrawable="@drawable/circular_progress_bar" />
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/airpods_pro_left_notification"
|
||||
android:tint="@color/popup_text"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="HardcodedText" />
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/left_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/popup_text"
|
||||
android:gravity="center"
|
||||
android:text="Left"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@color/popup_text"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Right"
|
||||
tools:ignore="HardcodedText" />
|
||||
<ImageView
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:src="@drawable/airpods_pro_case_notification"
|
||||
android:tint="@color/popup_text"
|
||||
android:contentDescription="something"
|
||||
android:layout_margin="0dp"
|
||||
android:gravity="center_vertical"
|
||||
tools:ignore="HardcodedText" />
|
||||
<TextView
|
||||
android:id="@+id/case_battery_widget"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<FrameLayout
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center">
|
||||
<ProgressBar
|
||||
android:id="@+id/right_battery_progress"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:progressDrawable="@drawable/circular_progress_bar" />
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/airpods_pro_right_notification"
|
||||
android:tint="@color/popup_text"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="HardcodedText" />
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/right_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/popup_text"
|
||||
android:gravity="center"
|
||||
android:text="Right"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@color/popup_text"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="Case"
|
||||
tools:ignore="HardcodedText" />
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
<FrameLayout
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center">
|
||||
<ProgressBar
|
||||
android:id="@+id/case_battery_progress"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:progressDrawable="@drawable/circular_progress_bar" />
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/airpods_pro_case_notification"
|
||||
android:tint="@color/popup_text"
|
||||
android:layout_gravity="center"
|
||||
tools:ignore="HardcodedText" />
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/case_battery_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/popup_text"
|
||||
android:gravity="center"
|
||||
android:text="Case"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:description="@string/app_widget_description"
|
||||
android:initialKeyguardLayout="@layout/battery_widget"
|
||||
android:initialLayout="@layout/battery_widget"
|
||||
android:minWidth="40dp"
|
||||
android:minHeight="40dp"
|
||||
android:previewImage="@drawable/example_appwidget_preview"
|
||||
android:previewLayout="@layout/battery_widget"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:targetCellWidth="1"
|
||||
android:targetCellHeight="1"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:widgetCategory="home_screen|keyguard" />
|
||||
@@ -10,5 +10,5 @@
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:targetCellWidth="1"
|
||||
android:targetCellHeight="1"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:updatePeriodMillis="300000"
|
||||
android:widgetCategory="home_screen|keyguard" />
|
||||
Reference in New Issue
Block a user