Compare commits

...

6 Commits

Author SHA1 Message Date
Kavish Devar
b06d780eee android: fix xposed state being set true onResume 2026-04-27 13:13:40 +05:30
Kavish Devar
70f420dedb android: fix text color in email bottom sheet 2026-04-27 10:17:33 +05:30
Kavish Devar
23193ceb39 android: load native hook from split apks when base fails 2026-04-27 10:08:05 +05:30
Kavish Devar
cb246d1287 android: update dialog and add app info to incompatible page 2026-04-26 17:36:17 +05:30
Kavish Devar
95cd677da9 android: fix bypass on pixels on older A16 version; also check Xposed scope for compatibility 2026-04-26 16:23:29 +05:30
Kavish Devar
0d049d93fb ci: use latest release tag for changelog 2026-04-26 16:17:02 +05:30
10 changed files with 378 additions and 342 deletions

View File

@@ -87,7 +87,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
name: apk-release name: apk-release
@@ -107,12 +109,16 @@ jobs:
with: with:
name: root-module-debug name: root-module-debug
path: artifacts/root-module-debug path: artifacts/root-module-debug
- id: prev - id: prev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") TAG=$(gh release list \
echo "tag=$TAG" >> $GITHUB_OUTPUT --limit 1 \
--json tagName \
--jq '.[0].tagName')
echo "tag=$TAG" >> $GITHUB_OUTPUT
- id: changelog - id: changelog
run: | run: |
if [ -z "${{ steps.prev.outputs.tag }}" ]; then if [ -z "${{ steps.prev.outputs.tag }}" ]; then
@@ -134,4 +140,4 @@ jobs:
artifacts/**/* \ artifacts/**/* \
-t "Nightly ${{ needs.build.outputs.short_sha }}" \ -t "Nightly ${{ needs.build.outputs.short_sha }}" \
--notes "${{ steps.changelog.outputs.notes }}" \ --notes "${{ steps.changelog.outputs.notes }}" \
--prerelease --prerelease

View File

@@ -1,6 +1,6 @@
import java.util.Properties import java.util.Properties
val appVersionName = "0.2.4" val appVersionName = "0.2.6"
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
@@ -30,7 +30,7 @@ android {
applicationId = "me.kavishdevar.librepods" applicationId = "me.kavishdevar.librepods"
minSdk = 33 minSdk = 33
targetSdk = 37 targetSdk = 37
versionCode = 40 versionCode = 43
versionName = appVersionName versionName = appVersionName
} }
buildTypes { buildTypes {

View File

@@ -121,6 +121,7 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.rememberHazeState import dev.chrisbanes.haze.rememberHazeState
import me.kavishdevar.librepods.data.AirPodsNotifications import me.kavishdevar.librepods.data.AirPodsNotifications
import me.kavishdevar.librepods.data.ControlCommandRepository import me.kavishdevar.librepods.data.ControlCommandRepository
import me.kavishdevar.librepods.presentation.components.AppInfoCard
import me.kavishdevar.librepods.presentation.components.ConfirmationDialog import me.kavishdevar.librepods.presentation.components.ConfirmationDialog
import me.kavishdevar.librepods.presentation.components.DeviceInfoCard import me.kavishdevar.librepods.presentation.components.DeviceInfoCard
import me.kavishdevar.librepods.presentation.components.SelectItem import me.kavishdevar.librepods.presentation.components.SelectItem
@@ -223,7 +224,7 @@ class MainActivity : ComponentActivity() {
fun Main() { fun Main() {
val context = LocalContext.current val context = LocalContext.current
val sharedPreferences = context.getSharedPreferences("settings", MODE_PRIVATE) val sharedPreferences = context.getSharedPreferences("settings", MODE_PRIVATE)
if (!isSupported(sharedPreferences)) { if (!isSupported(sharedPreferences) && !XposedState.bluetoothScopeEnabled) {
val showDialog = remember { mutableStateOf(false) } val showDialog = remember { mutableStateOf(false) }
val showPlayBypassVisible = remember { mutableStateOf(false) } val showPlayBypassVisible = remember { mutableStateOf(false) }
val hazeState = rememberHazeState() val hazeState = rememberHazeState()
@@ -249,27 +250,25 @@ fun Main() {
verticalArrangement = Arrangement verticalArrangement = Arrangement
.spacedBy(16.dp) .spacedBy(16.dp)
) { ) {
val innerBackdrop = rememberLayerBackdrop()
Spacer(modifier = Modifier.height(48.dp)) Spacer(modifier = Modifier.height(48.dp))
Column( Column(
modifier = Modifier.layerBackdrop(innerBackdrop), modifier = Modifier,
verticalArrangement = Arrangement verticalArrangement = Arrangement
.spacedBy(16.dp) .spacedBy(16.dp)
) { ) {
Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = stringResource(R.string.not_supported), text = stringResource(R.string.not_supported),
style = TextStyle( style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)), fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = textColor, color = textColor,
fontSize = 20.sp, fontSize = 28.sp,
textAlign = TextAlign.Center textAlign = TextAlign.Center
), ),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
DeviceInfoCard()
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -281,7 +280,7 @@ fun Main() {
style = TextStyle( style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)), fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
color = if (isSystemInDarkTheme()) Color.White else Color.Black, color = if (isDarkTheme) Color.White else Color.Black,
fontSize = 16.sp fontSize = 16.sp
), ),
modifier = Modifier modifier = Modifier
@@ -289,22 +288,27 @@ fun Main() {
.padding(horizontal = 12.dp, vertical = 16.dp) .padding(horizontal = 12.dp, vertical = 16.dp)
) )
} }
} StyledButton(
StyledButton( onClick = { showDialog.value = true },
onClick = { showDialog.value = true }, backdrop = rememberLayerBackdrop(),
backdrop = innerBackdrop, modifier = Modifier
modifier = Modifier .fillMaxWidth(),
.fillMaxWidth() isInteractive = false,
) { surfaceColor = if (isDarkTheme) Color(0xFF862424) else Color(0xFFC94646)
Text( ) {
text = stringResource(R.string.bypass_compatibility_check), Text(
style = TextStyle( text = stringResource(R.string.bypass_compatibility_check),
fontFamily = FontFamily(Font(R.font.sf_pro)), style = TextStyle(
fontWeight = FontWeight.Medium, fontFamily = FontFamily(Font(R.font.sf_pro)),
color = if (isSystemInDarkTheme()) Color.White else Color.Black, fontWeight = FontWeight.Medium,
fontSize = 16.sp color = Color.White,
), fontSize = 16.sp
) ),
)
}
Spacer(modifier = Modifier.height(24.dp))
DeviceInfoCard()
AppInfoCard()
} }
Spacer(modifier = Modifier.height(48.dp)) Spacer(modifier = Modifier.height(48.dp))
} }
@@ -323,16 +327,17 @@ fun Main() {
} else { } else {
sharedPreferences.edit { sharedPreferences.edit {
putBoolean("bypass_device_check.v2", true) putBoolean("bypass_device_check.v2", true)
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
} }
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
context.startActivity(intent)
} }
}, },
onDismiss = { onDismiss = {
showDialog.value = false showDialog.value = false
}, },
hazeState = hazeState backdrop = backdrop
// hazeState = hazeState
) )
if (BuildConfig.PLAY_BUILD) { if (BuildConfig.PLAY_BUILD) {

View File

@@ -0,0 +1,193 @@
/*
LibrePods - AirPods liberated from Apples ecosystem
Copyright (C) 2025 LibrePods contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
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/>.
*/
package me.kavishdevar.librepods.presentation.components
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
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.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.librepods.BuildConfig
import me.kavishdevar.librepods.R
@Composable
fun AppInfoCard() {
val rowHeight = remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
val isDarkTheme = isSystemInDarkTheme()
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black
Column {
Box(
modifier = Modifier
.background(if (isDarkTheme) Color.Black else Color(0xFFF2F2F7))
.padding(start = 16.dp, bottom = 8.dp, end = 4.dp)
) {
Text(
text = stringResource(R.string.about), style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
Column(
modifier = Modifier
.clip(RoundedCornerShape(28.dp))
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(28.dp))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.onGloballyPositioned { coordinates ->
rowHeight.value = with(density) { coordinates.size.height.toDp() }
},
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.version), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.VERSION_NAME, style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.version_code), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.VERSION_CODE.toString(), style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.flavor), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.FLAVOR, style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.build_type), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.BUILD_TYPE,
style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
}
}
}

View File

@@ -18,37 +18,32 @@
package me.kavishdevar.librepods.presentation.components package me.kavishdevar.librepods.presentation.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
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.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidthIn import androidx.compose.foundation.layout.requiredWidthIn
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
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.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
@@ -56,13 +51,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import com.kyant.backdrop.backdrops.LayerBackdrop
import androidx.compose.ui.window.DialogProperties import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.HazeState import com.kyant.backdrop.drawBackdrop
import dev.chrisbanes.haze.hazeEffect import com.kyant.backdrop.effects.blur
import dev.chrisbanes.haze.materials.CupertinoMaterials import com.kyant.backdrop.effects.lens
import com.kyant.backdrop.effects.vibrancy
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import kotlinx.coroutines.launch
import me.kavishdevar.librepods.R import me.kavishdevar.librepods.R
@ExperimentalHazeMaterialsApi @ExperimentalHazeMaterialsApi
@@ -75,162 +70,107 @@ fun ConfirmationDialog(
dismissText: String = "Cancel", dismissText: String = "Cancel",
onConfirm: () -> Unit, onConfirm: () -> Unit,
onDismiss: () -> Unit = { showDialog.value = false }, onDismiss: () -> Unit = { showDialog.value = false },
hazeState: HazeState, backdrop: LayerBackdrop,
) { ) {
val isDarkTheme = isSystemInDarkTheme() val isDarkTheme = isSystemInDarkTheme()
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val accentColor = if (isDarkTheme) Color(0xFF007AFF) else Color(0xFF3C6DF5) val accentColor = if (isDarkTheme) Color(0xFF0091FF) else Color(0xFF0088FF)
val haptics = LocalHapticFeedback.current AnimatedVisibility(
val scope = rememberCoroutineScope() visible = showDialog.value,
enter = scaleIn(initialScale = 1.05f) + fadeIn(),
if (showDialog.value) { exit = scaleOut(targetScale = 1.05f) + fadeOut()
Dialog( ) {
onDismissRequest = { showDialog.value = false }, Box(
properties = DialogProperties( modifier = Modifier.fillMaxSize(),
dismissOnBackPress = false, contentAlignment = Alignment.Center
dismissOnClickOutside = false
)
) { ) {
val innerBackdrop = rememberLayerBackdrop()
Box( Box(
modifier = Modifier modifier = Modifier
// .fillMaxWidth(0.75f) .fillMaxSize()
.requiredWidthIn(min = 200.dp, max = 360.dp) .background(Color.Black.copy(alpha = 0.4f))
.background(Color.Transparent, RoundedCornerShape(14.dp)) .clickable(enabled = false, onClick = {}),
.clip(RoundedCornerShape(14.dp)) contentAlignment = Alignment.Center
.hazeEffect(
hazeState,
style = CupertinoMaterials.regular(
containerColor = if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.95f) else Color.White.copy(alpha = 0.95f)
)
)
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Box(
Spacer(modifier = Modifier.height(24.dp)) modifier = Modifier
Text( .requiredWidthIn(min = 200.dp, max = 360.dp)
title, .clip(RoundedCornerShape(48.dp))
style = TextStyle( .drawBackdrop(
fontSize = 16.sp, backdrop = backdrop,
fontWeight = FontWeight.Bold, exportedBackdrop = innerBackdrop,
color = textColor, shape = { RoundedCornerShape(48.dp) },
fontFamily = FontFamily(Font(R.font.sf_pro)) effects = {
), vibrancy()
textAlign = TextAlign.Center, blur(4f.dp.toPx())
modifier = Modifier.padding(horizontal = 16.dp) lens(12f.dp.toPx(), 48f.dp.toPx(), true)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
message,
style = TextStyle(
fontSize = 14.sp,
color = textColor.copy(alpha = 0.8f),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.fillMaxWidth()
)
var leftPressed by remember { mutableStateOf(false) }
var rightPressed by remember { mutableStateOf(false) }
val pressedColor = if (isDarkTheme) Color(0x40888888) else Color(0x40D9D9D9)
Row(
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
val position = event.changes.first().position
val width = size.width.toFloat()
val height = size.height.toFloat()
val isWithinBounds = position.y >= 0 && position.y <= height
val isLeft = position.x < width / 2
event.changes.first().consume()
when (event.type) {
PointerEventType.Press -> {
if (isWithinBounds) {
leftPressed = isLeft
rightPressed = !isLeft
} else {
leftPressed = false
rightPressed = false
}
}
PointerEventType.Move -> {
if (isWithinBounds) {
if (leftPressed != isLeft) scope.launch { haptics.performHapticFeedback(
HapticFeedbackType.SegmentTick) }
leftPressed = isLeft
rightPressed = !isLeft
} else {
leftPressed = false
rightPressed = false
}
}
PointerEventType.Release -> {
if (isWithinBounds) {
if (leftPressed) {
scope.launch { haptics.performHapticFeedback(
HapticFeedbackType.Reject) }
onDismiss()
} else if (rightPressed) {
scope.launch { haptics.performHapticFeedback(
HapticFeedbackType.Confirm) }
onConfirm()
}
}
leftPressed = false
rightPressed = false
}
}
}
}
}, },
horizontalArrangement = Arrangement.Start, onDrawSurface = {
verticalAlignment = Alignment.CenterVertically drawRect(
) { if (isDarkTheme) Color(0xFF1F1F1F).copy(alpha = 0.35f) else Color(0xFFE0E0E0).copy(alpha = 0.7f)
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.background(if (leftPressed) pressedColor else Color.Transparent),
contentAlignment = Alignment.Center
) {
Text(
text = dismissText,
style = TextStyle(
color = accentColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
) )
) })) {
} Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box( Spacer(modifier = Modifier.height(24.dp))
modifier = Modifier Text(
.width(1.dp) title,
.fillMaxHeight() style = TextStyle(
.background(Color(0x40888888)) fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
),
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp)
) )
Box( Spacer(modifier = Modifier.height(12.dp))
modifier = Modifier Text(
.weight(1f) message,
.fillMaxHeight() style = TextStyle(
.background(if (rightPressed) pressedColor else Color.Transparent), fontSize = 14.sp,
contentAlignment = Alignment.Center color = textColor.copy(alpha = 0.8f),
fontFamily = FontFamily(Font(R.font.sf_pro))
),
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(0.9f),
horizontalArrangement = Arrangement.spacedBy(24.dp)
) { ) {
Text( StyledButton(
text = confirmText, onClick = onDismiss,
style = TextStyle( backdrop = innerBackdrop,
color = accentColor, modifier = Modifier.weight(1f),
fontFamily = FontFamily(Font(R.font.sf_pro)) ) {
Text(
text = dismissText, style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = textColor
)
) )
) }
StyledButton(
onClick = onConfirm,
backdrop = innerBackdrop,
modifier = Modifier.weight(1f),
surfaceColor = accentColor
) {
Text(
text = confirmText, style = TextStyle(
fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = Color.White
)
)
}
} }
Spacer(modifier = Modifier.height(24.dp))
} }
} }
} }

View File

@@ -46,7 +46,7 @@ fun DeviceInfoCard() {
Box( Box(
modifier = Modifier modifier = Modifier
.background(if (isDarkTheme) Color.Black else Color(0xFFF2F2F7)) .background(if (isDarkTheme) Color.Black else Color(0xFFF2F2F7))
.padding(start = 16.dp, bottom = 2.dp, top = 24.dp, end = 4.dp) .padding(start = 16.dp, top = 24.dp, end = 4.dp)
) { ) {
Text( Text(
text = stringResource(R.string.device_info), style = TextStyle( text = stringResource(R.string.device_info), style = TextStyle(

View File

@@ -58,9 +58,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.Font
@@ -80,6 +78,7 @@ import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
import me.kavishdevar.librepods.BuildConfig import me.kavishdevar.librepods.BuildConfig
import me.kavishdevar.librepods.R import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.presentation.components.AppInfoCard
import me.kavishdevar.librepods.presentation.components.DeviceInfoCard import me.kavishdevar.librepods.presentation.components.DeviceInfoCard
import me.kavishdevar.librepods.presentation.components.NavigationButton import me.kavishdevar.librepods.presentation.components.NavigationButton
import me.kavishdevar.librepods.presentation.components.StyledBottomSheet import me.kavishdevar.librepods.presentation.components.StyledBottomSheet
@@ -513,145 +512,8 @@ fun AppSettingsScreen(
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
DeviceInfoCard() DeviceInfoCard()
Spacer(modifier = Modifier.height(16.dp))
Box( AppInfoCard()
modifier = Modifier
.background(if (isDarkTheme) Color.Black else Color(0xFFF2F2F7))
.padding(start = 16.dp, bottom = 2.dp, top = 24.dp, end = 4.dp)
) {
Text(
text = stringResource(R.string.about), style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
val rowHeight = remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
Spacer(modifier = Modifier.height(4.dp))
Column(
modifier = Modifier
.clip(RoundedCornerShape(28.dp))
.fillMaxWidth()
.background(backgroundColor, RoundedCornerShape(28.dp))
.padding(top = 2.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.onGloballyPositioned { coordinates ->
rowHeight.value = with(density) { coordinates.size.height.toDp() }
},
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.version), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.VERSION_NAME, style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.version_code), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.VERSION_CODE.toString(), style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.flavor), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.FLAVOR, style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
HorizontalDivider(
thickness = 1.dp,
color = Color(0x40888888),
modifier = Modifier.padding(horizontal = 12.dp)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(R.string.build_type), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = BuildConfig.BUILD_TYPE,
style = TextStyle(
fontSize = 16.sp,
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.8f
),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
@@ -763,7 +625,8 @@ fun AppSettingsScreen(
fontSize = 18.sp, fontSize = 18.sp,
fontFamily = FontFamily(Font(R.font.sf_pro)), fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center textAlign = TextAlign.Center,
color = if (isSystemInDarkTheme()) Color.White else Color.Black
) )
) )
StyledIconButton( StyledIconButton(

View File

@@ -270,7 +270,7 @@ fun HearingAidScreen(viewModel: AirPodsViewModel, navController: NavController)
hearingAidEnabled.value = false hearingAidEnabled.value = false
showDialog.value = false showDialog.value = false
}, },
hazeState = hazeStateS.value, // hazeState = hazeStateS.value,
// backdrop = backdrop backdrop = backdrop
) )
} }

View File

@@ -23,7 +23,7 @@ class LibrePodsApplication: Application(), XposedServiceHelper.OnServiceListener
override fun onResume(owner: LifecycleOwner) { override fun onResume(owner: LifecycleOwner) {
BillingManager.provider.queryPurchases() BillingManager.provider.queryPurchases()
XposedState.isAvailable = true XposedState.isAvailable = XposedServiceHolder.service != null
XposedState.bluetoothScopeEnabled = XposedServiceHolder.service?.scope?.contains("com.google.android.bluetooth") == true || XposedServiceHolder.service?.scope?.contains("com.android.bluetooth") == true XposedState.bluetoothScopeEnabled = XposedServiceHolder.service?.scope?.contains("com.google.android.bluetooth") == true || XposedServiceHolder.service?.scope?.contains("com.android.bluetooth") == true
} }

View File

@@ -20,6 +20,7 @@ class KotlinModule: XposedModule() {
log(Log.INFO, TAG, "framework: $frameworkName($frameworkVersionCode) API $apiVersion") log(Log.INFO, TAG, "framework: $frameworkName($frameworkVersionCode) API $apiVersion")
} }
@SuppressLint("UnsafeDynamicallyLoadedCode")
override fun onPackageLoaded(param: PackageLoadedParam) { override fun onPackageLoaded(param: PackageLoadedParam) {
log(Log.INFO, TAG, "onPackageLoaded :: ${param.packageName}") log(Log.INFO, TAG, "onPackageLoaded :: ${param.packageName}")
@@ -27,8 +28,36 @@ class KotlinModule: XposedModule() {
log(Log.INFO, TAG, "Bluetooth app detected, hooking l2c_fcr_chk_chan_modes") log(Log.INFO, TAG, "Bluetooth app detected, hooking l2c_fcr_chk_chan_modes")
try { try {
if (param.isFirstPackage) { if (param.isFirstPackage) {
log(Log.INFO, TAG, "Loading native library for Bluetooth hook") val abi = android.os.Build.SUPPORTED_ABIS.first()
System.loadLibrary("l2c_fcr_hook") val soName = "libl2c_fcr_hook.so"
val candidates = buildList {
add("${moduleApplicationInfo.sourceDir}!/lib/$abi/$soName")
moduleApplicationInfo.splitSourceDirs?.forEach { split ->
add("$split!/lib/$abi/$soName")
}
}
var loaded = false
for (path in candidates) {
try {
log(Log.INFO, TAG, "Trying to load native lib from $path")
System.load(path)
log(Log.INFO, TAG, "Loaded native lib from $path")
loaded = true
break
} catch (e: Throwable) {
log(Log.WARN, TAG, "Failed to load from $path: ${e.message}")
}
}
if (!loaded) {
log(Log.ERROR, TAG, "Could not load $soName from base or splits")
return
}
val remotePrefValue = getRemotePreferences("me.kavishdevar.librepods").getBoolean("vendor_id_hook", false) val remotePrefValue = getRemotePreferences("me.kavishdevar.librepods").getBoolean("vendor_id_hook", false)
log(Log.INFO, TAG, "sdp hook enabled (remote pref): $remotePrefValue") log(Log.INFO, TAG, "sdp hook enabled (remote pref): $remotePrefValue")
NativeBridge.setSdpHook(remotePrefValue) NativeBridge.setSdpHook(remotePrefValue)