android: add xposed check and email form

too many emails with absolutely no content
This commit is contained in:
Kavish Devar
2026-04-26 04:45:45 +05:30
parent f5d92768e2
commit 469d948061
16 changed files with 689 additions and 442 deletions

View File

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

View File

@@ -65,6 +65,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
@@ -86,11 +87,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@@ -120,9 +123,12 @@ import me.kavishdevar.librepods.data.AirPodsNotifications
import me.kavishdevar.librepods.data.ControlCommandRepository
import me.kavishdevar.librepods.presentation.components.ConfirmationDialog
import me.kavishdevar.librepods.presentation.components.DeviceInfoCard
import me.kavishdevar.librepods.presentation.components.PlayBypassSheet
import me.kavishdevar.librepods.presentation.components.SelectItem
import me.kavishdevar.librepods.presentation.components.StyledBottomSheet
import me.kavishdevar.librepods.presentation.components.StyledButton
import me.kavishdevar.librepods.presentation.components.StyledIconButton
import me.kavishdevar.librepods.presentation.components.StyledInputField
import me.kavishdevar.librepods.presentation.components.StyledSelectList
import me.kavishdevar.librepods.presentation.screens.AccessibilitySettingsScreen
import me.kavishdevar.librepods.presentation.screens.AdaptiveStrengthScreen
import me.kavishdevar.librepods.presentation.screens.AirPodsSettingsScreen
@@ -146,6 +152,7 @@ import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel
import me.kavishdevar.librepods.presentation.viewmodel.AppSettingsViewModel
import me.kavishdevar.librepods.presentation.viewmodel.PurchaseViewModel
import me.kavishdevar.librepods.services.AirPodsService
import me.kavishdevar.librepods.utils.XposedState
import me.kavishdevar.librepods.utils.isSupported
import kotlin.io.encoding.ExperimentalEncodingApi
@@ -157,7 +164,7 @@ lateinit var connectionStatusReceiver: BroadcastReceiver
class MainActivity : ComponentActivity() {
companion object {
init {
if (BuildConfig.FLAVOR == "xposed") {
if (XposedState.isAvailable && XposedState.bluetoothScopeEnabled) {
System.loadLibrary("l2c_fcr_hook")
}
}
@@ -329,23 +336,118 @@ fun Main() {
)
if (BuildConfig.PLAY_BUILD) {
PlayBypassSheet(
StyledBottomSheet(
visible = showPlayBypassVisible.value,
onDismiss = {
showPlayBypassVisible.value = false
showDialog.value = true
},
onConfirm = {
showPlayBypassVisible.value = false
sharedPreferences.edit {
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)
}
},
backdrop = backdrop
)
) { innerBackdrop, _ ->
val contentColor = if (isDarkTheme) Color.White else Color.Black
var acknowledged by remember { mutableStateOf(false) }
val inputState = rememberTextFieldState("")
val isValid = acknowledged && inputState.text.trim() == "OK"
val sfPro = FontFamily(Font(R.font.sf_pro))
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
text = stringResource(R.string.bypass_compatibility_check),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp,
color = contentColor
),
modifier = Modifier.padding(horizontal = 12.dp)
)
Text(
text = stringResource(R.string.compatibility_play_dialog_confirmation),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = contentColor
),
modifier = Modifier.padding(horizontal = 12.dp)
)
StyledSelectList(
items = listOf(
SelectItem(
name = stringResource(R.string.read_compatibility_requirements),
selected = acknowledged,
onClick = { acknowledged = !acknowledged }
)
)
)
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(Unit) {
focusRequester.requestFocus()
keyboardController?.show()
}
StyledInputField(
inputState = inputState,
focusRequester = focusRequester,
placeholder = stringResource(R.string.type_ok_to_continue, "OK")
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(24.dp)
) {
StyledButton(
onClick = { showPlayBypassVisible.value = false },
backdrop = innerBackdrop,
modifier = Modifier.weight(1f),
) {
Text(
text = stringResource(R.string.no),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = contentColor
)
)
}
StyledButton(
onClick = {
showPlayBypassVisible.value = false
sharedPreferences.edit {
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)
}
},
backdrop = innerBackdrop,
isInteractive = isValid,
modifier = Modifier.weight(1f),
enabled = isValid,
surfaceColor = if (isDarkTheme) Color(0xFF0091FF) else Color(0xFF0088FF)
) {
Text(
text = stringResource(R.string.proceed),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = if (isValid) contentColor else contentColor.copy(alpha = 0.4f)
)
)
}
}
}
}
}
return

View File

@@ -4,6 +4,7 @@ import android.os.Build
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
@@ -27,6 +28,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.utils.XposedState
@Composable
fun DeviceInfoCard() {
@@ -41,14 +43,20 @@ fun DeviceInfoCard() {
Column (
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = stringResource(R.string.device_info), style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
), modifier = Modifier.padding(start = 16.dp)
)
Box(
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.device_info), 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))
@@ -166,6 +174,62 @@ fun DeviceInfoCard() {
)
)
}
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.xposed_available), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = if (XposedState.isAvailable) stringResource(R.string.yes) else stringResource(R.string.no), 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.app_enabled_in_xposed), style = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
Text(
text = if (XposedState.bluetoothScopeEnabled) stringResource(R.string.yes) else stringResource(R.string.no), 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

@@ -1,254 +0,0 @@
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
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 com.kyant.backdrop.backdrops.LayerBackdrop
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import com.kyant.backdrop.drawBackdrop
import com.kyant.backdrop.effects.blur
import com.kyant.backdrop.effects.lens
import com.kyant.backdrop.effects.vibrancy
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import me.kavishdevar.librepods.R
@ExperimentalHazeMaterialsApi
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlayBypassSheet(
visible: Boolean,
onDismiss: () -> Unit,
onConfirm: () -> Unit,
backdrop: LayerBackdrop
) {
if (!visible) return
val dark = isSystemInDarkTheme()
val contentColor = if (dark) Color.White else Color.Black
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var acknowledged by remember { mutableStateOf(false) }
val inputState = rememberTextFieldState("")
val isValid = acknowledged && inputState.text.trim() == "OK"
val sfPro = FontFamily(Font(R.font.sf_pro))
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
containerColor = Color.Transparent,
dragHandle = { },
shape = RoundedCornerShape(48.dp),
scrimColor = Color.Transparent,
modifier = Modifier.padding(16.dp)
) {
val innerBackdrop = rememberLayerBackdrop()
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(48.dp))
.drawBackdrop(
backdrop = backdrop,
exportedBackdrop = innerBackdrop,
shape = { RoundedCornerShape(48.dp) },
effects = {
vibrancy()
blur(6f.dp.toPx())
lens(12f.dp.toPx(), 48f.dp.toPx(), true)
},
onDrawSurface = {
drawRect(
if (dark) Color.DarkGray.copy(alpha = 0.3f) else Color.White.copy(alpha = 0.6f)
)
}
)
.padding(24.dp)
) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
text = stringResource(R.string.bypass_compatibility_check),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp,
color = contentColor
),
modifier = Modifier.padding(horizontal = 12.dp)
)
Text(
text = stringResource(R.string.compatibility_play_dialog_confirmation),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = contentColor
),
modifier = Modifier.padding(horizontal = 12.dp)
)
StyledSelectList(
items = listOf(
SelectItem(
name = stringResource(R.string.read_compatibility_requirements),
selected = acknowledged,
onClick = { acknowledged = !acknowledged }
)
)
)
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(Unit) {
focusRequester.requestFocus()
keyboardController?.show()
}
val backgroundColor = if (dark) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
val textColor = if (dark) Color.White else Color.Black
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(58.dp)
.background(
backgroundColor,
RoundedCornerShape(28.dp)
)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
BasicTextField(
state = inputState,
textStyle = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
),
cursorBrush = SolidColor(textColor),
decorator = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.weight(1f)
) {
Box {
if (inputState.text == "") {
Text(
text = stringResource(R.string.type_ok_to_continue, "OK"),
style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Light,
fontFamily = sfPro,
color = textColor.copy(alpha = 0.8f)
)
)
}
innerTextField()
}
}
IconButton(
onClick = {
inputState.clearText()
}
) {
Text(
text = "􀁡",
style = TextStyle(
fontSize = 16.sp,
fontFamily = FontFamily(Font(R.font.sf_pro)),
color = if (dark) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f)
),
)
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp)
.focusRequester(focusRequester)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(24.dp)
) {
StyledButton(
onClick = onDismiss,
backdrop = innerBackdrop,
modifier = Modifier.weight(1f),
) {
Text(
text = stringResource(R.string.no),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = contentColor
)
)
}
StyledButton(
onClick = onConfirm,
backdrop = innerBackdrop,
isInteractive = isValid,
modifier = Modifier.weight(1f),
enabled = isValid,
surfaceColor = if (dark) Color(0xFF0091FF) else Color(0xFF0088FF)
) {
Text(
text = stringResource(R.string.proceed),
style = TextStyle(
fontFamily = sfPro,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
color = if (isValid) contentColor else contentColor.copy(alpha = 0.4f)
)
)
}
}
}
}
}
}

View File

@@ -0,0 +1,87 @@
package me.kavishdevar.librepods.presentation.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import com.kyant.backdrop.backdrops.LayerBackdrop
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import com.kyant.backdrop.drawBackdrop
import com.kyant.backdrop.effects.blur
import com.kyant.backdrop.effects.lens
import com.kyant.backdrop.effects.vibrancy
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StyledBottomSheet(
visible: Boolean,
onDismiss: () -> Unit,
backdrop: LayerBackdrop,
content: @Composable (innerBackdrop: LayerBackdrop, progress: Float) -> Unit
) {
if (!visible) return
val isDarkTheme = isSystemInDarkTheme()
val sheetState = rememberModalBottomSheetState(false) // move this to parent composable
val isExpanded = sheetState.targetValue == SheetValue.Expanded
val progress by animateFloatAsState(
targetValue = if (isExpanded) 1f else 0f,
label = "sheetProgress"
)
val animatedCorner = lerp(48.dp, 42.dp, progress)
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
containerColor = Color.Transparent,
dragHandle = { },
shape = RoundedCornerShape(animatedCorner),
scrimColor = Color.Transparent,
modifier = Modifier.padding(4.dp)
) {
val innerBackdrop = rememberLayerBackdrop()
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(animatedCorner))
.drawBackdrop(
backdrop = backdrop,
exportedBackdrop = innerBackdrop,
shape = { RoundedCornerShape(animatedCorner) },
effects = {
vibrancy()
blur(4f.dp.toPx())
lens(12f.dp.toPx(), 48f.dp.toPx(), true)
},
onDrawSurface = {
drawRect(
if (isDarkTheme) Color.DarkGray.copy(alpha = 0.3f) else Color(
0xFFE0E0E0
).copy(alpha = 0.45f)
)
}
)
.padding(top = 24.dp)
.padding(horizontal = 16.dp)
) {
content(innerBackdrop, progress)
}
}
}

View File

@@ -52,6 +52,7 @@ import androidx.compose.ui.graphics.rememberGraphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
@@ -81,9 +82,11 @@ import kotlin.math.tanh
fun StyledIconButton(
modifier: Modifier = Modifier,
icon: String,
tint: Color = Color.Unspecified,
iconTint: Color = Color.Unspecified,
surfaceColor: Color = Color.Unspecified,
backdrop: LayerBackdrop = rememberLayerBackdrop(),
onClick: () -> Unit
onClick: () -> Unit,
enabled: Boolean = true
) {
val haptics = LocalHapticFeedback.current
val darkMode = isSystemInDarkTheme()
@@ -96,6 +99,7 @@ fun StyledIconButton(
val innerShadowLayer = rememberGraphicsLayer().apply {
compositingStrategy = CompositingStrategy.Offscreen
}
val density = LocalDensity.current
val interactiveHighlightShader = remember {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -120,8 +124,10 @@ half4 main(float2 coord) {
val isDarkTheme = isSystemInDarkTheme()
TextButton(
onClick = {
scope.launch { haptics.performHapticFeedback(HapticFeedbackType.ContextClick) }
onClick()
if (enabled) {
scope.launch { haptics.performHapticFeedback(HapticFeedbackType.ContextClick) }
onClick()
}
},
shape = RoundedCornerShape(56.dp),
modifier = modifier
@@ -137,6 +143,7 @@ half4 main(float2 coord) {
)
},
layerBlock = {
if (!enabled) return@drawBackdrop
val width = size.width
val height = size.height
@@ -161,6 +168,12 @@ half4 main(float2 coord) {
(height / width).fastCoerceAtMost(1f)
},
onDrawSurface = {
if (!enabled) {
drawRect(
(if (isDarkTheme) Color(0xFFAFAFAF) else Color.White).copy(0.5f)
)
return@drawBackdrop
}
val progress = progressAnimation.value.coerceIn(0f, 1f)
val shape = RoundedCornerShape(56.dp)
@@ -187,6 +200,10 @@ half4 main(float2 coord) {
}
drawLayer(innerShadowLayer)
if (surfaceColor.isSpecified) {
drawRect(surfaceColor)
}
drawRect(
(if (isDarkTheme) Color(0xFFAFAFAF) else Color.White).copy(
progress.coerceIn(
@@ -197,6 +214,7 @@ half4 main(float2 coord) {
)
},
onDrawFront = {
if (!enabled) return@drawBackdrop
val progress = progressAnimation.value.fastCoerceIn(0f, 1f)
if (progress > 0f) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && interactiveHighlightShader != null) {
@@ -241,40 +259,46 @@ half4 main(float2 coord) {
)
.pointerInput(scope) {
val onDragStop: () -> Unit = {
scope.launch {
launch { haptics.performHapticFeedback(HapticFeedbackType.Reject) }
launch { progressAnimation.animateTo(0f, progressAnimationSpec) }
launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) }
if (enabled) {
scope.launch {
launch { haptics.performHapticFeedback(HapticFeedbackType.Reject) }
launch { progressAnimation.animateTo(0f, progressAnimationSpec) }
launch { offsetAnimation.animateTo(Offset.Zero, offsetAnimationSpec) }
}
}
}
inspectDragGestures(
onDragStart = { down ->
pressStartPosition = down.position
scope.launch {
launch { haptics.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick) }
launch { progressAnimation.animateTo(1f, progressAnimationSpec) }
launch { offsetAnimation.snapTo(Offset.Zero) }
if (enabled) {
pressStartPosition = down.position
scope.launch {
launch { haptics.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick) }
launch { progressAnimation.animateTo(1f, progressAnimationSpec) }
launch { offsetAnimation.snapTo(Offset.Zero) }
}
}
},
onDragEnd = { onDragStop() },
onDragCancel = onDragStop
) { _, dragAmount ->
scope.launch {
if (dragAmount.getDistanceSquared() > 350) haptics.performHapticFeedback(
HapticFeedbackType.SegmentFrequentTick
)
offsetAnimation.snapTo(offsetAnimation.value + dragAmount)
if (enabled) {
if (dragAmount.getDistanceSquared() > 350) haptics.performHapticFeedback(
HapticFeedbackType.SegmentFrequentTick
)
offsetAnimation.snapTo(offsetAnimation.value + dragAmount)
}
}
}
}
.size(48.dp),
.size(with(density) { 48.sp.toDp() }),
) {
Text(
text = icon,
style = TextStyle(
fontSize = 16.sp,
fontSize = 20.sp,
fontWeight = FontWeight.Normal,
color = if (tint.isSpecified) tint else if (darkMode) Color.White else Color.Black,
color = if (iconTint.isSpecified) iconTint else if (darkMode) Color.White else Color.Black,
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)

View File

@@ -0,0 +1,154 @@
package me.kavishdevar.librepods.presentation.components
import android.R.attr.singleLine
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.clearText
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
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.R
@Composable
fun StyledInputField(
inputState: TextFieldState,
focusRequester: FocusRequester,
placeholder: String = "",
singleLine: Boolean = true
){
val isDarkTheme = isSystemInDarkTheme()
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black
val minHeight = if (singleLine) 58.dp else 120.dp
val verticalAlignment = if (singleLine) Alignment.CenterVertically else Alignment.Top
val hasText = inputState.text.isNotEmpty()
val density = LocalDensity.current
val spacerHeight by animateDpAsState(
targetValue = if (hasText) with(density) { 32.sp.toDp() } else 0.dp,
label = "labelSpacer"
)
val transition = updateTransition(hasText, label = "floating")
val yOffset by transition.animateDp(label = "y") {
if (it) with (density) { (-48).sp.toDp() } else 0.dp
}
Spacer(modifier = Modifier.height(spacerHeight))
Box(
modifier = Modifier.fillMaxWidth()
) {
Row(
verticalAlignment = verticalAlignment,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = minHeight)
.background(
backgroundColor,
RoundedCornerShape(28.dp)
)
.padding(horizontal = 16.dp, vertical = 8.dp)
.pointerInput(Unit) {
detectTapGestures {
focusRequester.requestFocus()
}
}
) {
BasicTextField(
state = inputState,
lineLimits = if (singleLine) TextFieldLineLimits.SingleLine else TextFieldLineLimits.Default,
textStyle = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
),
cursorBrush = SolidColor(textColor),
decorator = { innerTextField ->
Row(
modifier = Modifier.padding(top = if (singleLine) 0.dp else 16.dp),
verticalAlignment = verticalAlignment,
) {
Row(
modifier = Modifier
.weight(1f)
) {
Box(
modifier = Modifier
.weight(1f),
contentAlignment = if (singleLine) Alignment.CenterStart else Alignment.TopStart
) {
Text(
text = placeholder,
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Light,
fontFamily = FontFamily(Font(R.font.sf_pro)),
color = textColor.copy(alpha = 0.8f)
),
modifier = Modifier
.offset(y = yOffset)
)
innerTextField()
}
}
if (singleLine && !inputState.text.isEmpty()) {
IconButton(
onClick = {
inputState.clearText()
}
) {
Text(
text = "􀁡",
style = TextStyle(
fontSize = 16.sp,
fontFamily = FontFamily(Font(R.font.sf_pro)),
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(
alpha = 0.6f
)
),
)
}
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp)
.focusRequester(focusRequester)
)
}
}
}

View File

@@ -28,7 +28,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -98,7 +98,7 @@ fun StyledSelectList(
Row(
modifier = Modifier
.height(if (hasIcon) 72.dp else 55.dp)
.heightIn(min = if (hasIcon) 72.dp else 55.dp)
.background(animatedBackgroundColor, shape)
.pointerInput(Unit) {
detectTapGestures(
@@ -128,7 +128,7 @@ fun StyledSelectList(
contentDescription = "Icon",
tint = Color(0xFF007AFF),
modifier = Modifier
.height(48.dp)
.heightIn(min = 48.dp)
.wrapContentWidth()
)
}

View File

@@ -402,7 +402,7 @@ fun AccessibilitySettingsScreen(viewModel: AirPodsViewModel, navController: NavC
}
}
// if (!hearingAidEnabled && BuildConfig.FLAVOR == "xposed") {
// if (!hearingAidEnabled && XposedState.isAvailable) {
// Text(
// text = stringResource(R.string.apply_eq_to), style = TextStyle(
// fontSize = 14.sp,

View File

@@ -25,6 +25,7 @@ import android.widget.Toast
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.Spacer
@@ -35,8 +36,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
@@ -49,8 +53,10 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext
@@ -62,7 +68,9 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -74,12 +82,17 @@ import me.kavishdevar.librepods.BuildConfig
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.presentation.components.DeviceInfoCard
import me.kavishdevar.librepods.presentation.components.NavigationButton
import me.kavishdevar.librepods.presentation.components.StyledBottomSheet
import me.kavishdevar.librepods.presentation.components.StyledButton
import me.kavishdevar.librepods.presentation.components.StyledIconButton
import me.kavishdevar.librepods.presentation.components.StyledInputField
import me.kavishdevar.librepods.presentation.components.StyledScaffold
import me.kavishdevar.librepods.presentation.components.StyledSlider
import me.kavishdevar.librepods.presentation.components.StyledToggle
import me.kavishdevar.librepods.presentation.viewmodel.AppSettingsViewModel
import me.kavishdevar.librepods.utils.XposedState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppSettingsScreen(
navController: NavController, viewModel: AppSettingsViewModel = viewModel()
@@ -90,6 +103,12 @@ fun AppSettingsScreen(
val backdrop = rememberLayerBackdrop()
val contactBottomSheet = remember { mutableStateOf(false) }
val subjectState = remember { TextFieldState() }
val descriptionState = remember { TextFieldState() }
val subjectFocusRequester = remember { FocusRequester() }
val descriptionFocusRequester = remember { FocusRequester() }
StyledScaffold(
title = stringResource(R.string.settings)
) { topPadding, hazeState, bottomPadding ->
@@ -367,24 +386,28 @@ fun AppSettingsScreen(
independent = true,
enabled = state.isPremium
)
Spacer(modifier = Modifier.height(16.dp))
} else {
Text(
text = stringResource(R.string.customizations_unavailable),
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
fontFamily = FontFamily(Font(R.font.sf_pro)),
color = textColor.copy(alpha = 0.6f),
),
Box(
modifier = Modifier
.background(if (isDarkTheme) Color.Black else Color(0xFFF2F2F7))
.padding(horizontal = 16.dp)
.padding(top = 16.dp)
)
.padding(top = 16.dp, bottom = 2.dp)
) {
Text(
text = stringResource(R.string.customizations_unavailable),
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
fontFamily = FontFamily(Font(R.font.sf_pro)),
color = textColor.copy(alpha = 0.6f),
),
modifier = Modifier
)
}
}
if (BuildConfig.FLAVOR == "xposed") {
Spacer(modifier = Modifier.height(16.dp))
if (XposedState.isAvailable && XposedState.bluetoothScopeEnabled) {
val restartBluetoothText =
stringResource(R.string.found_offset_restart_bluetooth)
StyledToggle(
@@ -417,14 +440,20 @@ fun AppSettingsScreen(
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.contact), style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
), modifier = Modifier.padding(16.dp, bottom = 2.dp, top = 24.dp)
)
Box(
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.contact), style = TextStyle(
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = textColor.copy(alpha = 0.6f),
fontFamily = FontFamily(Font(R.font.sf_pro))
)
)
}
Spacer(modifier = Modifier.height(4.dp))
Column(
@@ -439,29 +468,7 @@ fun AppSettingsScreen(
to = "",
name = stringResource(R.string.email),
navController = navController,
onClick = {
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = "mailto:".toUri()
putExtra(Intent.EXTRA_EMAIL, arrayOf("contact@kavish.xyz"))
putExtra(Intent.EXTRA_SUBJECT, "LibrePods: <SUBJECT>")
putExtra(
Intent.EXTRA_TEXT,
"Describe your issue here:" +
"\n\n\n\n----------" +
"\nPhone details:" +
"\nMANUFACTURER: ${Build.MANUFACTURER}" +
"\nMODEL: ${Build.MODEL} (${Build.PRODUCT})" +
"\nDISPLAY_VERSION: ${Build.DISPLAY} (${Build.PRODUCT})" +
"\nID: ${Build.ID} (SDK ${Build.VERSION.SDK_INT_FULL})" +
"\n\nApp details:" +
"\nVERSION: ${BuildConfig.VERSION_NAME}" +
"\nVERSION_CODE: ${BuildConfig.VERSION_CODE}" +
"\nFLAVOR: ${BuildConfig.FLAVOR}" +
"\nBUILD_TYPE: ${BuildConfig.BUILD_TYPE}"
)
}
context.startActivity(intent)
},
onClick = { contactBottomSheet.value = true },
independent = false
)
@@ -507,14 +514,20 @@ fun AppSettingsScreen(
Spacer(modifier = Modifier.height(20.dp))
DeviceInfoCard()
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))
), modifier = Modifier.padding(start = 16.dp, bottom = 2.dp, top = 24.dp)
)
Box(
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
@@ -719,5 +732,93 @@ fun AppSettingsScreen(
})
}
}
StyledBottomSheet(
visible = contactBottomSheet.value,
onDismiss = { contactBottomSheet.value = false },
backdrop = backdrop
) { innerBackdrop, progress ->
val animatedPadding = lerp(16.dp, 2.dp, progress)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = animatedPadding)
.padding(bottom = 16.dp),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
StyledIconButton(
icon = "\uDBC0\uDD84",
backdrop = innerBackdrop,
onClick = { contactBottomSheet.value = false }
)
Text (
text = stringResource(R.string.describe_your_issue),
style = TextStyle(
fontSize = 18.sp,
fontFamily = FontFamily(Font(R.font.sf_pro)),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
)
StyledIconButton(
icon = "\uDBC0\uDE1F",
backdrop = innerBackdrop,
surfaceColor = if (isSystemInDarkTheme()) Color(0xFF0091FF) else Color(0xFF0088FF),
iconTint = if (subjectState.text.isNotEmpty() && descriptionState.text.isNotEmpty()) Color.White else Color.Gray,
enabled = subjectState.text.isNotEmpty() && descriptionState.text.isNotEmpty(),
onClick = {
contactBottomSheet.value = false
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = "mailto:".toUri()
putExtra(Intent.EXTRA_EMAIL, arrayOf("contact@kavish.xyz"))
putExtra(Intent.EXTRA_SUBJECT, "LibrePods: ${subjectState.text}")
putExtra(
Intent.EXTRA_TEXT,
"${descriptionState.text}" +
"\n\n----------" +
"\nPhone details:" +
"\nMANUFACTURER: ${Build.MANUFACTURER}" +
"\nMODEL: ${Build.MODEL} (${Build.PRODUCT})" +
"\nDISPLAY_VERSION: ${Build.DISPLAY}" +
"\nID: ${Build.ID} (SDK ${Build.VERSION.SDK_INT_FULL})" +
"\nXposed enabled/active: ${XposedState.isAvailable}/${XposedState.bluetoothScopeEnabled}" +
"\n\nApp details:" +
"\nVERSION: ${BuildConfig.VERSION_NAME}" +
"\nVERSION_CODE: ${BuildConfig.VERSION_CODE}" +
"\nFLAVOR: ${BuildConfig.FLAVOR}" +
"\nBUILD_TYPE: ${BuildConfig.BUILD_TYPE}"
)
}
context.startActivity(intent)
subjectState.clearText()
descriptionState.clearText()
}
)
}
Spacer(modifier = Modifier.height(8.dp))
StyledInputField(
inputState = subjectState,
focusRequester = subjectFocusRequester,
placeholder = stringResource(R.string.subject),
)
Spacer(modifier = Modifier.height(12.dp))
StyledInputField(
inputState = descriptionState,
focusRequester = descriptionFocusRequester,
placeholder = stringResource(R.string.describe_your_issue),
singleLine = false
)
}
}
}
}

View File

@@ -21,43 +21,26 @@
package me.kavishdevar.librepods.presentation.screens
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
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.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.edit
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import me.kavishdevar.librepods.R
import me.kavishdevar.librepods.presentation.components.StyledInputField
import me.kavishdevar.librepods.presentation.components.StyledScaffold
import me.kavishdevar.librepods.presentation.viewmodel.AirPodsViewModel
import kotlin.io.encoding.ExperimentalEncodingApi
@@ -67,14 +50,12 @@ import kotlin.io.encoding.ExperimentalEncodingApi
@Composable
fun RenameScreen(viewModel: AirPodsViewModel) {
val sharedPreferences = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
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))
}
StyledScaffold(
@@ -86,67 +67,18 @@ fun RenameScreen(viewModel: AirPodsViewModel) {
.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(spacerHeight))
val isDarkTheme = isSystemInDarkTheme()
val backgroundColor = if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black
val cursorColor = if (isDarkTheme) Color.White else Color.Black
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(58.dp)
.background(
backgroundColor,
RoundedCornerShape(28.dp)
)
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
BasicTextField(
value = name.value,
onValueChange = {
name.value = it
sharedPreferences.edit {putString("name", it.text)}
viewModel.setName(it.text)
},
textStyle = TextStyle(
fontSize = 16.sp,
color = textColor,
fontFamily = FontFamily(Font(R.font.sf_pro))
),
singleLine = true,
cursorBrush = SolidColor(cursorColor),
decorationBox = { innerTextField ->
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.weight(1f)
) {
innerTextField()
}
IconButton(
onClick = {
name.value = TextFieldValue("")
}
) {
Text(
text = "􀁡",
style = TextStyle(
fontSize = 16.sp,
fontFamily = FontFamily(Font(R.font.sf_pro)),
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.6f)
),
)
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp)
.focusRequester(focusRequester)
)
val textFieldState = rememberTextFieldState()
textFieldState.edit { sharedPreferences.getString("name", "") ?: "" }
LaunchedEffect(textFieldState.text) {
sharedPreferences.edit {putString("name", textFieldState.text as String?)}
viewModel.setName(textFieldState.text.toString())
}
StyledInputField(
textFieldState,
focusRequester
)
}
}
}

View File

@@ -10,10 +10,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import me.kavishdevar.librepods.BuildConfig
import me.kavishdevar.librepods.billing.BillingManager
import me.kavishdevar.librepods.data.XposedRemotePrefProvider
import me.kavishdevar.librepods.utils.NativeBridge
import me.kavishdevar.librepods.utils.XposedState
import kotlin.math.roundToInt
data class AppSettingsUiState(
@@ -91,7 +91,7 @@ class AppSettingsViewModel(application: Application) : AndroidViewModel(applicat
connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false)
)
}
if (BuildConfig.FLAVOR == "xposed") {
if (XposedState.isAvailable && XposedState.bluetoothScopeEnabled) {
NativeBridge.setSdpHook(_uiState.value.vendorIdHook)
}
}

View File

@@ -0,0 +1,6 @@
package me.kavishdevar.librepods.utils
object XposedState {
var isAvailable: Boolean = false
var bluetoothScopeEnabled: Boolean = false
}

View File

@@ -263,4 +263,8 @@
<string name="digital_assistant_on_long_press">Digital Assistant on Long Press</string>
<string name="digital_assistant_on_long_press_description">Invoke Digital Assistant when long pressing the AirPods Pro stem.</string>
<string name="customizations_unavailable">Customizations unavailable. Connect your AirPods at least once to access.</string>
<string name="xposed_available">Xposed available</string>
<string name="app_enabled_in_xposed">App enabled in Xposed</string>
<string name="subject">Subject</string>
<string name="describe_your_issue">Describe your issue</string>
</resources>

View File

@@ -9,6 +9,7 @@ import io.github.libxposed.service.XposedServiceHelper
import me.kavishdevar.librepods.billing.BillingManager
import me.kavishdevar.librepods.billing.BillingProviderFactory
import me.kavishdevar.librepods.utils.XposedServiceHolder
import me.kavishdevar.librepods.utils.XposedState
class LibrePodsApplication: Application(), XposedServiceHelper.OnServiceListener, DefaultLifecycleObserver {
override fun onCreate() {
@@ -22,13 +23,18 @@ class LibrePodsApplication: Application(), XposedServiceHelper.OnServiceListener
override fun onResume(owner: LifecycleOwner) {
BillingManager.provider.queryPurchases()
XposedState.isAvailable = true
XposedState.bluetoothScopeEnabled = XposedServiceHolder.service?.scope?.contains("com.google.android.bluetooth") == true || XposedServiceHolder.service?.scope?.contains("com.android.bluetooth") == true
}
override fun onServiceBind(p0: XposedService) {
XposedServiceHolder.service = p0
override fun onServiceBind(service: XposedService) {
XposedServiceHolder.service = service
XposedState.isAvailable = true
XposedState.bluetoothScopeEnabled = XposedServiceHolder.service?.scope?.contains("com.google.android.bluetooth") == true || XposedServiceHolder.service?.scope?.contains("com.android.bluetooth") == true
}
override fun onServiceDied(p0: XposedService) {
XposedServiceHolder.service = null
XposedState.isAvailable = false
}
}