fix: Better location icon

This commit is contained in:
ΞTΞRNAL
2025-10-06 18:06:58 +05:30
parent f86cb2e632
commit a423451715
2 changed files with 89 additions and 24 deletions

View File

@@ -1,7 +1,6 @@
package me.rhunk.snapenhance.ui.manager.pages.location
import android.os.Parcel
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -9,13 +8,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.DeleteOutline
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.EditLocation
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -102,6 +99,7 @@ class BetterLocationRoot : Routes.Route() {
}
} ?: friendsLocation
}
ElevatedCard(
shape = MaterialTheme.shapes.large,
modifier = Modifier.padding(top = 32.dp, bottom = 32.dp)
@@ -151,24 +149,11 @@ class BetterLocationRoot : Routes.Route() {
}
}
@Composable
private fun ThemedEditLocationButton(onClick: () -> Unit) {
FilledIconButton(
modifier = Modifier.size(40.dp),
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = if (isSystemInDarkTheme()) Color.White else Color(0xFF151A1A),
),
onClick = onClick
) {
Icon(Icons.Default.EditLocation, contentDescription = null)
}
}
override val content: @Composable (NavBackStackEntry) -> Unit = {
val coordinatesProperty = remember {
context.config.root.global.betterLocation.getPropertyPair("coordinates")
}
val updateDispatcher = rememberAsyncUpdateDispatcher()
val savedCoordinates = rememberAsyncMutableStateList(
defaultValue = listOf(),
@@ -179,14 +164,17 @@ class BetterLocationRoot : Routes.Route() {
var showMap by remember { mutableStateOf(false) }
var addSavedCoordinateDialog by remember { mutableStateOf(false) }
var showTeleportDialog by remember { mutableStateOf(false) }
val marker = remember { mutableStateOf<Marker?>(null) }
val mapView = remember { mutableStateOf<MapView?>(null) }
var spoofedCoordinates by remember(showTeleportDialog, showMap) { mutableStateOf(coordinatesProperty.value.get() as? Pair<*, *>) }
fun addSavedCoordinate(id: Int?, locationCoordinates: LocationCoordinates, onSuccess: suspend (id: Int) -> Unit = {}) {
context.coroutineScope.launch {
onSuccess(context.database.addOrUpdateLocationCoordinate(id, locationCoordinates))
}
}
if (showTeleportDialog) {
me.rhunk.snapenhance.ui.util.Dialog(
properties = DialogProperties(usePlatformDefaultWidth = false),
@@ -201,6 +189,7 @@ class BetterLocationRoot : Routes.Route() {
}
)
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -218,6 +207,7 @@ class BetterLocationRoot : Routes.Route() {
.fillMaxWidth()
.padding(8.dp)
)
if (addSavedCoordinateDialog) {
me.rhunk.snapenhance.ui.util.Dialog(
onDismissRequest = { addSavedCoordinateDialog = false },
@@ -240,6 +230,7 @@ class BetterLocationRoot : Routes.Route() {
}
)
}
if (showMap) {
me.rhunk.snapenhance.ui.util.Dialog(
onDismissRequest = { showMap = false },
@@ -258,12 +249,13 @@ class BetterLocationRoot : Routes.Route() {
}
)
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.clipToBounds(),
contentPadding = PaddingValues(bottom = routes.bottomPadding)
.clipToBounds()
) {
item {
@Composable
fun ConfigToggle(
@@ -353,6 +345,7 @@ class BetterLocationRoot : Routes.Route() {
val isSelected = spoofedCoordinates == mutableCoordinates.latitude to mutableCoordinates.longitude
var showDeleteDialog by remember { mutableStateOf(false) }
var showEditDialog by remember { mutableStateOf(false) }
fun setSpoofedCoordinates() {
spoofedCoordinates = mutableCoordinates.latitude to mutableCoordinates.longitude
coordinatesProperty.value.setAny(spoofedCoordinates)
@@ -360,6 +353,7 @@ class BetterLocationRoot : Routes.Route() {
context.config.writeConfig()
}
}
if (showDeleteDialog) {
me.rhunk.snapenhance.ui.util.Dialog(
onDismissRequest = { showDeleteDialog = false },
@@ -379,6 +373,7 @@ class BetterLocationRoot : Routes.Route() {
}
)
}
if (showEditDialog) {
me.rhunk.snapenhance.ui.util.Dialog(
onDismissRequest = { showEditDialog = false },
@@ -406,6 +401,7 @@ class BetterLocationRoot : Routes.Route() {
}
)
}
ElevatedCard(
onClick = {
mutableCoordinates = coordinates
@@ -451,7 +447,7 @@ class BetterLocationRoot : Routes.Route() {
FilledIconButton(onClick = {
showEditDialog = true
}) {
Icon(Icons.Default.Edit, contentDescription = "Edit")
Icon(Icons.Default.Edit, contentDescription = "Delete")
}
Spacer(modifier = Modifier.width(4.dp))
FilledIconButton(onClick = {
@@ -465,4 +461,4 @@ class BetterLocationRoot : Routes.Route() {
}
}
}
}
}

View File

@@ -5,6 +5,18 @@ import android.location.LocationManager
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.EditLocation
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import me.rhunk.snapenhance.common.ui.OverlayType
import me.rhunk.snapenhance.common.ui.createComposeView
import me.rhunk.snapenhance.common.util.protobuf.EditorContext
@@ -42,21 +54,26 @@ data class FriendLocation(
fun distanceTo(other: FriendLocation): Double {
val deltaLat = Math.toRadians(other.latitude - this.latitude)
val deltaLong = Math.toRadians(other.longitude - this.longitude)
val a = sin(deltaLat / 2) * sin(deltaLat / 2) +
cos(Math.toRadians(this.latitude)) * cos(Math.toRadians(other.latitude)) *
sin(deltaLong / 2) * sin(deltaLong / 2)
return 6371 * 2 * atan2(sqrt(a), sqrt(1 - a))
}
}
class BetterLocation : Feature("Better Location") {
val locationHistory = mutableMapOf<String, FriendLocation>()
private val walkRadius by lazy {
context.config.global.betterLocation.walkRadius.getNullable()
}
private val randomWalking by lazy {
RandomWalking(walkRadius?.toDoubleOrNull())
}
private fun getLat() : Double {
var spoofedLatitude = context.config.global.betterLocation.coordinates.get().first
walkRadius?.let {
@@ -64,6 +81,7 @@ class BetterLocation : Feature("Better Location") {
}
return spoofedLatitude
}
private fun getLong() : Double {
var spoofedLongitude = context.config.global.betterLocation.coordinates.get().second
walkRadius?.let {
@@ -71,8 +89,10 @@ class BetterLocation : Feature("Better Location") {
}
return spoofedLongitude
}
private fun editClientUpdate(editor: EditorContext) {
val config = context.config.global.betterLocation
editor.apply {
// SCVSLocationUpdate
edit(1) {
@@ -83,14 +103,17 @@ class BetterLocation : Feature("Better Location") {
addFixed32(1, getLat().toFloat()) // lat
addFixed32(2, getLong().toFloat()) // lng
}
if (config.alwaysUpdateLocation.get()) {
remove(7)
addVarInt(7, System.currentTimeMillis()) // timestamp
}
}
if (context.config.global.betterLocation.suspendLocationUpdates.get()) {
remove(1)
}
// SCVSDeviceData
edit(3) {
config.spoofBatteryLevel.getNullable()?.takeIf { it.isNotEmpty() }?.let {
@@ -102,12 +125,14 @@ class BetterLocation : Feature("Better Location") {
addVarInt(3, 1) // devicePluggedIn
}
}
if (config.spoofHeadphones.get()) {
remove(4)
addVarInt(4, 1) // headphoneOutput
remove(6)
addVarInt(6, 1) // isOtherAudioPlaying
}
edit(10) {
remove(1)
addVarInt(1, 4) // type = ALWAYS
@@ -117,11 +142,14 @@ class BetterLocation : Feature("Better Location") {
}
}
}
private fun onLocationEvent(protoReader: ProtoReader) {
protoReader.eachBuffer(3, 1) {
val clusterId = UUID(getFixed64(1, 1) ?: return@eachBuffer, getFixed64(1, 2) ?: return@eachBuffer).toString()
val latitude = getFixed32(4)?.let { Float.fromBits(it) }?.toDouble() ?: return@eachBuffer
val longitude = getFixed32(5)?.let { Float.fromBits(it) }?.toDouble() ?: return@eachBuffer
val locality = getString(10)
val localityPieces = mutableListOf<String>().also {
forEach { index, wire ->
@@ -129,6 +157,7 @@ class BetterLocation : Feature("Better Location") {
it.add((wire.value as ByteArray).toString(Charsets.UTF_8) )
}
}
eachBuffer(7) friend@{
val userId = if (contains(1)) UUID(getFixed64(1, 1) ?: return@friend, getFixed64(1, 2) ?: return@friend).toString() else clusterId
val friendLocation = FriendLocation(
@@ -140,14 +169,17 @@ class BetterLocation : Feature("Better Location") {
localityPieces = localityPieces,
batteryLevel = getFixed32(13)?.let { Float.fromBits(it) } ?: -1F,
)
locationHistory[userId] = friendLocation
}
}
}
private fun openManagementOverlay() {
context.bridgeClient.getLocationManager().provideFriendsLocation(
locationHistory.values.toList().mapNotNull { locationHistory ->
val friendInfo = context.database.getFriendInfo(locationHistory.userId) ?: return@mapNotNull null
me.rhunk.snapenhance.bridge.location.FriendLocation().also {
it.username = friendInfo.mutableUsername ?: return@mapNotNull null
it.displayName = friendInfo.displayName
@@ -163,9 +195,12 @@ class BetterLocation : Feature("Better Location") {
)
context.bridgeClient.openOverlay(OverlayType.BETTER_LOCATION)
}
override fun init() {
if (context.config.global.betterLocation.globalState != true) return
val canSpoofLocation = { context.config.global.betterLocation.spoofLocation.get() }
LocationManager::class.java.apply {
hook("isProviderEnabled", HookStage.BEFORE, { canSpoofLocation() }) { it.setResult(true) }
hook("isProviderEnabledForUser", HookStage.BEFORE, { canSpoofLocation() }) { it.setResult(true) }
@@ -174,7 +209,9 @@ class BetterLocation : Feature("Better Location") {
hook("getLatitude", HookStage.BEFORE, { canSpoofLocation() }) { it.setResult(getLat()) }
hook("getLongitude", HookStage.BEFORE, { canSpoofLocation() }) { it.setResult(getLong()) }
}
val mapViewId = context.resources.getId("mapview")
if (context.config.global.betterLocation.showBatteryLevel.get()) {
findClass("snap.snap_maps_sdk.nano.SnapMapsSdk\$PublicUserInfo").hook("setDisplayName", HookStage.BEFORE) { param ->
val instance = param.thisObject<Any>()
@@ -182,26 +219,55 @@ class BetterLocation : Feature("Better Location") {
val batteryLevel = locationHistory[userId]?.batteryLevel?.takeIf { it > -1F } ?: return@hook
param.setArg(0, param.arg<String>(0) + " (${(batteryLevel * 100).toInt()}%)")
}
findClass("com.snap.map_friend_focus_view.MapFocusViewFriendSectionDataModel").hookConstructor(HookStage.AFTER) { param ->
val instance = param.thisObject<Any>()
val userId = instance.getObjectField("_userId") as? String ?: return@hookConstructor
val batteryLevel = locationHistory[userId]?.batteryLevel?.takeIf { it > -1F } ?: return@hookConstructor
param.thisObject<Any>().dataBuilder {
val prevText = get<String?>("_lastSeen")?.let { " - $it" } ?: ""
set("_lastSeen", "(${(batteryLevel * 100).toInt()}%)$prevText")
}
}
}
context.event.subscribe(AddViewEvent::class) { event ->
if (!event.viewClassName.endsWith("MapScreenRoot")) return@subscribe
event.view.addOnAttachStateChangeListener(object: View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
// Overlay Compose UI is now created in the app module only!
// No Compose code or theming here in the core module.
val mapView = event.view.findViewById<View>(mapViewId) ?: throw IllegalStateException("Map view not found")
val view = (mapView.parent as ViewGroup).children().firstOrNull { it is RelativeLayout } as? RelativeLayout ?: throw IllegalStateException("Map view parent not found")
view.addView(createComposeView(view.context) {
val darkTheme = remember { context.androidContext.isDarkTheme() }
Box(
modifier = Modifier.padding(start = 8.dp)
) {
FilledIconButton(
modifier = Modifier.size(40.dp),
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = if (darkTheme) Color(0xFF1D1D1D) else Color.White,
contentColor = if (darkTheme) Color.White else Color(0xFF151A1A),
),
onClick = { openManagementOverlay() }
) {
Icon(Icons.Default.EditLocation, contentDescription = null)
}
}
}.apply {
layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
addRule(RelativeLayout.ALIGN_PARENT_LEFT)
setMargins(0, (60 * context.resources.displayMetrics.density).toInt(), 0, 0)
}
})
}
override fun onViewDetachedFromWindow(v: View) {}
})
}
context.event.subscribe(UnaryCallEvent::class) { event ->
if (event.uri == "/snapchat.valis.Valis/SendClientUpdate") {
event.buffer = ProtoEditor(event.buffer).apply {
@@ -213,6 +279,7 @@ class BetterLocation : Feature("Better Location") {
}.toByteArray()
}
}
context.mappings.useMapper(CallbackMapper::class) {
callbacks.getClass("ServerStreamingEventHandler")?.hook("onEvent", HookStage.BEFORE) { param ->
val buffer = param.argNullable<ByteBuffer>(1)?.let {
@@ -222,11 +289,13 @@ class BetterLocation : Feature("Better Location") {
onLocationEvent(ProtoReader(buffer))
}
}
findClass("com.snapchat.client.grpc.ClientStreamSendHandler\$CppProxy").hook("send", HookStage.BEFORE) { param ->
val array = param.arg<ByteBuffer>(0).let {
it.position(0)
ByteArray(it.capacity()).also { buffer -> it.get(buffer); it.position(0) }
}
param.setArg(0, ProtoEditor(array).apply {
edit {
editClientUpdate(this)
@@ -236,4 +305,4 @@ class BetterLocation : Feature("Better Location") {
})
}
}
}
}