Compare commits

...

11 Commits

Author SHA1 Message Date
Kavish Devar 53679cc902 android: bump version
just doing this because play looks "version code up, let's update!" even if it's not installed from Play, and I can't make another rc1.
2026-09-01 00:13:05 +05:30
Kavish Devar b4cacb89f1 android: adjust pre-first connect text style in m3e 2026-08-27 15:50:48 +05:30
Kavish Devar cf6d226fca android: add demo to demo airpods name 2026-08-27 15:44:10 +05:30
Kavish Devar 4c7a3cb203 android: don't force connectionSuccessful to true in demo state 2026-08-27 15:41:02 +05:30
Kavish Devar 1783bb742e android: remove bypass when going back to 'not supported' page in onboarding 2026-08-27 15:29:43 +05:30
Kavish Devar 3cc4d92d62 android: bump version 2026-08-27 15:26:19 +05:30
Kavish Devar 95ecc0eb3d android: show bypassed message on settings page before first connect 2026-08-27 15:26:10 +05:30
Kavish Devar a7537f2361 android: make bypass compatibility check button scroll to next onboarding page 2026-08-27 15:24:52 +05:30
Kavish Devar 21babd819f android: hide reconnect button in m3e when no successful aacp connection 2026-08-27 15:16:28 +05:30
Kavish Devar b5a3eaee8f docs: add token for star history 2026-07-13 16:16:25 +05:30
Kavish Devar c3f537b0e9 android: fix non-round icon
lol...
2026-07-10 02:54:25 +05:30
7 changed files with 115 additions and 85 deletions
+5 -5
View File
@@ -224,11 +224,11 @@ A huge thank you to everyone supporting the project!
# Star History
<a href="https://www.star-history.com/#kavishdevar/librepods&type=date&legend=top-left">
<a href="https://www.star-history.com/?repos=librepods-org%2Flibrepods&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kavishdevar/librepods&type=date&theme=dark&legend=top-left" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kavishdevar/librepods&type=date&legend=top-left" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kavishdevar/librepods&type=date&legend=top-left" />
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=librepods-org/librepods&type=date&theme=dark&legend=top-left&sealed_token=CS9QF2Wcx-_EN6M7lBhwvTa2WBd5eB8FbQa5FWt3SV14v1OcQdy2aCGsXxHoytVaoylP_pTxj1h2U-odoXDV_EwTqMxM-MQwG_WpvV_1g_yH-Jh3ux2BuWCmS98LxLqfox2ibLOlFDHu_-geTLFsTmqTc1SjX6-NYxVHXF5DaqnxFOAyjLPL5_WtXVew" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=librepods-org/librepods&type=date&legend=top-left&sealed_token=CS9QF2Wcx-_EN6M7lBhwvTa2WBd5eB8FbQa5FWt3SV14v1OcQdy2aCGsXxHoytVaoylP_pTxj1h2U-odoXDV_EwTqMxM-MQwG_WpvV_1g_yH-Jh3ux2BuWCmS98LxLqfox2ibLOlFDHu_-geTLFsTmqTc1SjX6-NYxVHXF5DaqnxFOAyjLPL5_WtXVew" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=librepods-org/librepods&type=date&legend=top-left&sealed_token=CS9QF2Wcx-_EN6M7lBhwvTa2WBd5eB8FbQa5FWt3SV14v1OcQdy2aCGsXxHoytVaoylP_pTxj1h2U-odoXDV_EwTqMxM-MQwG_WpvV_1g_yH-Jh3ux2BuWCmS98LxLqfox2ibLOlFDHu_-geTLFsTmqTc1SjX6-NYxVHXF5DaqnxFOAyjLPL5_WtXVew" />
</picture>
</a>
@@ -258,4 +258,4 @@ If you see any misuse of the LibrePods name or logo, please report it to [me@kav
The SF Pro font used in the Android app is the property of Apple Inc.. This will be removed in future versions of the app and replaced with an open alternative soon.
AirPods, AirPods Pro, AirPods Max, and the AirPods logo are trademarks of Apple Inc. The LibrePods project is not affiliated with or endorsed by Apple Inc. in any way.
AirPods, AirPods Pro, AirPods Max, and the AirPods logo are trademarks of Apple Inc. The LibrePods project is not affiliated with or endorsed by Apple Inc. in any way.
@@ -736,96 +736,116 @@ fun AirPodsSettingsScreen(
val animatedShapeColor by animateColorAsState(if (reconnecting) primaryContainerColor else secondaryContainerColor)
Box(
modifier = Modifier
.size(240.dp)
.background(
MaterialTheme.colorScheme.surfaceBright,
CircleShape
)
.clickable(
interactionSource = null,
indication = ripple(
bounded = false,
radius = 120.dp
),
enabled = !reconnecting,
onClick = {}
)
.pointerInput(Unit) {
detectTapGestures(
onTap = {
if (!reconnecting) {
currentMorphIndex = 1
reconnecting = true
reconnectFromSavedMac()
}
},
onPress = {
if (!reconnecting) {
morphProgress.animateTo(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
if (state.connectionSuccessful) {
Box(
modifier = Modifier
.size(240.dp)
.background(
MaterialTheme.colorScheme.surfaceBright,
CircleShape
)
.clickable(
interactionSource = null,
indication = ripple(
bounded = false,
radius = 120.dp
),
enabled = !reconnecting,
onClick = {}
)
.pointerInput(Unit) {
detectTapGestures(
onTap = {
if (!reconnecting) {
currentMorphIndex = 1
reconnecting = true
reconnectFromSavedMac()
}
},
onPress = {
if (!reconnecting) {
morphProgress.animateTo(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
)
tryAwaitRelease()
morphProgress.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
tryAwaitRelease()
morphProgress.animateTo(
targetValue = 0f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
)
}
}
}
)
}
.drawWithContent {
val activeMorph = morphs[currentMorphIndex]
)
}
.drawWithContent {
val activeMorph = morphs[currentMorphIndex]
val shapePath = activeMorph.toPath(
progress = morphProgress.value,
path = path
)
val shapePath = activeMorph.toPath(
progress = morphProgress.value,
path = path
)
val bounds = shapePath.getBounds()
val bounds = shapePath.getBounds()
val scale = min(size.width/bounds.width, size.height/bounds.height) * 0.8f
val scale = min(
size.width / bounds.width,
size.height / bounds.height
) * 0.8f
scaleMatrix.reset()
scaleMatrix.reset()
scaleMatrix.scale(x = scale, y = scale)
scaleMatrix.scale(x = scale, y = scale)
shapePath.transform(scaleMatrix)
shapePath.transform(scaleMatrix)
shapePath.translate(size.center - shapePath.getBounds().center)
shapePath.translate(size.center - shapePath.getBounds().center)
drawPath(
path = shapePath,
color = animatedShapeColor
)
drawPath(
path = shapePath,
color = animatedShapeColor
)
drawContent()
},
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (reconnecting) MaterialIcons.bluetooth_searching else MaterialIcons.headset_off,
contentDescription = null,
modifier = Modifier.size(84.dp),
tint = if (reconnecting) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSecondaryContainer
drawContent()
},
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (reconnecting) MaterialIcons.bluetooth_searching else MaterialIcons.headset_off,
contentDescription = null,
modifier = Modifier.size(84.dp),
tint = if (reconnecting) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSecondaryContainer
)
}
Spacer(Modifier.height(40.dp))
Text(
text = if (reconnecting) stringResource(R.string.reconnecting) else stringResource(R.string.tap_to_reconnect),
style = MaterialTheme.typography.labelSmallEmphasized,
color = MaterialTheme.colorScheme.primary
)
} else {
Text(
text = stringResource(R.string.airpods_not_connected),
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary
)
if (sharedPreferences.getBoolean("bypass_device_check.v2", false)) {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.compatibility_check_bypassed),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center
)
}
}
Spacer(Modifier.height(40.dp))
Text(
text = if (reconnecting) stringResource(R.string.reconnecting) else stringResource(R.string.tap_to_reconnect),
style = MaterialTheme.typography.labelSmallEmphasized,
color = MaterialTheme.colorScheme.primary
)
}
if (!BuildConfig.PLAY_BUILD) {
@@ -47,6 +47,7 @@ import me.kavishdevar.librepods.presentation.theme.LibrePodsTheme
import me.kavishdevar.librepods.utils.XposedState
import me.kavishdevar.librepods.utils.bypassDeviceCheck
import me.kavishdevar.librepods.utils.isSupported
import me.kavishdevar.librepods.utils.removeDeviceCheckBypass
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
@Composable
@@ -191,6 +192,9 @@ fun OnboardingScreen(
NotSupportedPage(
bypassCompatibilityCheck = {
bypassDeviceCheck(sharedPreferences)
animationScope.launch {
state.animateScrollToItem(3)
}
}
)
}
@@ -199,6 +203,9 @@ fun OnboardingScreen(
onBackward = {
animationScope.launch {
if (state.canScrollBackward) state.animateScrollToItem(if (isSupported) 1 else 2)
if (!isSupported) {
removeDeviceCheckBypass(sharedPreferences)
}
}
},
onForward = onOnboardingComplete
@@ -105,7 +105,7 @@ data class AirPodsUiState(
)
val demoInstance = AirPodsInstance(
name = "AirPods Pro",
name = "[DEMO] AirPods Pro",
model = AirPodsModels.getModelByModelNumber("A3064")!!,
actualModelNumber = "A3064",
serialNumber = "JXF9Q94A40",
@@ -160,8 +160,6 @@ val demoState = AirPodsUiState(
dynamicEndOfCharge = true,
connectionSuccessful = true,
customEq = CustomEq(state = 2, low = 65, mid = 50, high = 70),
controlStates = mapOf(
@@ -42,3 +42,7 @@ fun isSupported(sharedPreferences: SharedPreferences): Boolean {
fun bypassDeviceCheck(sharedPreferences: SharedPreferences) {
sharedPreferences.edit{ putBoolean("bypass_device_check.v2", true) }
}
fun removeDeviceCheckBypass(sharedPreferences: SharedPreferences) {
sharedPreferences.edit{ putBoolean("bypass_device_check.v2", false) }
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_monochrome" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>
@@ -290,4 +290,5 @@
<string name="permissions">Permissions</string>
<string name="privacy_policy">Privacy Policy</string>
<string name="i_agree">I agree</string>
<string name="compatibility_check_bypassed">Compatibility check bypassed. Are you sure your device is compatible?</string>
</resources>