Compare commits

...

18 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
Kavish Devar e6ebbed674 docs: add trademark notice and librepods.org warning 2026-06-24 21:40:23 +05:30
Kavish Devar e34474f765 android: update gradle and AGP 2026-06-24 20:59:08 +05:30
Kavish Devar 4f910136dc android: remove unnecessary background modifier on m3 noise control settings 2026-06-23 17:29:56 +05:30
Kavish Devar 721819e792 android: change release notes variable name
0.3.1 -> 1.0.0
2026-06-23 17:26:26 +05:30
Kavish Devar bf3920718c android: remove '-play' from version name for release notes 2026-06-21 03:55:54 +05:30
Kavish Devar cb0c46dc33 android: fix left/right serial numbers in m3e 2026-06-21 03:51:15 +05:30
Kavish Devar cd40975a1f android: check premium for enabling Apple UI 2026-06-20 20:40:22 +05:30
16 changed files with 141 additions and 104 deletions
+17 -9
View File
@@ -1,7 +1,7 @@
>[!IMPORTANT]
Development paused due to lack of time until June 2026 (JEE Advanced). PRs and issues might not be responded to until then.
---
> [!WARNING]
> librepods.org is not an official website of the LibrePods project. It inaccurately claims to be the official website of the project by claiming copyrights and using the LibrePods logo in the footer. And at the same time, they say that the project is not affiliated with the LibrePods project or its developers.
>
> Please report any other such websites to [me@kavish.xyz](mailto:me@kavish.xyz)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./imgs/banner-dark.png" />
@@ -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>
@@ -250,4 +250,12 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
All trademarks, logos, and brand names are the property of their respective owners. Use of them does not imply any affiliation with or endorsement by them. All AirPods images, symbols, and the SF Pro font are the property of Apple Inc.
# Trademark Notice
The GPL does not grant any rights to use the LibrePods name, logo, or branding. The LibrePods name and logo may not be used for software, websites, domains, products, services, or other projects in a manner that suggests affiliation with, endorsement by, or association with the official LibrePods project without prior permission.
If you see any misuse of the LibrePods name or logo, please report it to [me@kavish.xyz](mailto:me@kavish.xyz).
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.
+2 -2
View File
@@ -1,6 +1,6 @@
import java.util.Properties
val appVersionName = "1.0.0-rc1"
val appVersionName = "1.0.0-rc2"
plugins {
alias(libs.plugins.android.application)
@@ -49,7 +49,7 @@ android {
defaultConfig {
applicationId = "me.kavishdevar.librepods"
targetSdk = 37
versionCode = 61
versionCode = 63
versionName = appVersionName
}
buildTypes {
@@ -181,7 +181,7 @@ fun Main() {
val onboardingComplete = sharedPreferences.getBoolean("onboarding_complete", false)
val releaseNotesShownPrefKey = "release_notes_shown_${BuildConfig.VERSION_NAME.removeSuffix("-debug")}"
val releaseNotesShownPrefKey = "release_notes_shown_${BuildConfig.VERSION_NAME.removeSuffix("-debug").removeSuffix("-play")}"
val releaseNotesShown = sharedPreferences.getBoolean(releaseNotesShownPrefKey, false)
fun bindService() {
@@ -8,7 +8,7 @@ import me.kavishdevar.librepods.presentation.screens.EqualizerScreenPreviewMater
import me.kavishdevar.librepods.presentation.theme.DesignSystem
import me.kavishdevar.librepods.presentation.theme.LocalDesignSystem
val update0_3_1 = listOf(
val update1_0_0 = listOf(
UpdateItem(
titleRes = R.string.material3e,
descriptionRes = R.string.update_m3e_description,
@@ -32,4 +32,4 @@ val update0_3_1 = listOf(
),
)
val updates = update0_3_1
val updates = update1_0_0
@@ -46,8 +46,8 @@ fun AboutCard(
DesignSystem.Material -> listOf(
serialNumbers[0],
stringResource(R.string.left) + " " + {serialNumbers[1]},
stringResource(R.string.right) + " " + {serialNumbers[2]},
stringResource(R.string.left) + " " + serialNumbers[1],
stringResource(R.string.right) + " " + serialNumbers[2],
)
}
@@ -131,7 +131,6 @@ fun NoiseControlSettings(
Column {
Box(
modifier = Modifier
.background(Color.Transparent)
.padding(horizontal = 16.dp)
.padding(top = 4.dp, bottom = 12.dp)
) {
@@ -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) {
@@ -190,7 +190,7 @@ fun AppSettingsScreen(
label = stringResource(R.string.use_material3e),
checked = state.m3eEnabled,
onCheckedChange = viewModel::setm3eEnabled,
// enabled = state.isPremium
enabled = state.isPremium
)
if (state.connectionSuccessful) {
@@ -78,7 +78,7 @@ import androidx.graphics.shapes.Morph
import me.kavishdevar.librepods.BuildConfig
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.data.updates.UpdateItem
import me.kavishdevar.librepods.data.updates.update0_3_1
import me.kavishdevar.librepods.data.updates.updates
import me.kavishdevar.librepods.presentation.theme.DesignSystem
import me.kavishdevar.librepods.presentation.theme.LibrePodsTheme
import me.kavishdevar.librepods.presentation.theme.LocalDesignSystem
@@ -116,7 +116,7 @@ fun ReleaseNotesScreen(
textAlign = TextAlign.Center
)
val versionName = BuildConfig.VERSION_NAME.removeSuffix("-debug")
val versionName = BuildConfig.VERSION_NAME.removeSuffix("-debug").removeSuffix("-play")
val url = "https://github.com/kavishdevar/librepods/releases/v$versionName"
val fullText = "${stringResource(R.string.version)} $versionName"
val textColor = MaterialTheme.colorScheme.primary
@@ -375,7 +375,7 @@ fun ReleaseNotesScreenPreview() {
m3eEnabled = false
) {
ReleaseNotesScreen(
updates = update0_3_1,
updates = updates,
releaseNotesShown = { }
)
}
@@ -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>
+1 -1
View File
@@ -1,6 +1,6 @@
[versions]
accompanistPermissions = "0.37.3"
agp = "9.1.1"
agp = "9.2.1"
kotlin = "2.3.21"
coreKtx = "1.18.0"
lifecycleRuntimeKtx = "2.10.0"
+1 -1
View File
@@ -1,6 +1,6 @@
#Mon Oct 07 22:30:36 IST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists