mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 13:00:17 +02:00
refactor: location spoofer
This commit is contained in:
parent
1a7755e45c
commit
32a458a690
@ -52,10 +52,6 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:excludeFromRecents="true" />
|
android:excludeFromRecents="true" />
|
||||||
<activity
|
|
||||||
android:name=".ui.MapActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:excludeFromRecents="true" />
|
|
||||||
<activity android:name=".bridge.ForceStartActivity"
|
<activity android:name=".bridge.ForceStartActivity"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.ui
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.EditText
|
|
||||||
import me.rhunk.snapenhance.R
|
|
||||||
import org.osmdroid.config.Configuration
|
|
||||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
|
||||||
import org.osmdroid.util.GeoPoint
|
|
||||||
import org.osmdroid.views.MapView
|
|
||||||
import org.osmdroid.views.Projection
|
|
||||||
import org.osmdroid.views.overlay.Marker
|
|
||||||
import org.osmdroid.views.overlay.Overlay
|
|
||||||
|
|
||||||
|
|
||||||
class MapActivity : Activity() {
|
|
||||||
|
|
||||||
private lateinit var mapView: MapView
|
|
||||||
|
|
||||||
@SuppressLint("MissingInflatedId", "ResourceType")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
val contextBundle = intent.extras?.getBundle("location") ?: return
|
|
||||||
val locationLatitude = contextBundle.getDouble("latitude")
|
|
||||||
val locationLongitude = contextBundle.getDouble("longitude")
|
|
||||||
|
|
||||||
Configuration.getInstance().load(applicationContext, getSharedPreferences("osmdroid", Context.MODE_PRIVATE))
|
|
||||||
|
|
||||||
setContentView(R.layout.map)
|
|
||||||
|
|
||||||
mapView = findViewById(R.id.mapView)
|
|
||||||
mapView.setMultiTouchControls(true);
|
|
||||||
mapView.setTileSource(TileSourceFactory.MAPNIK)
|
|
||||||
|
|
||||||
val startPoint = GeoPoint(locationLatitude, locationLongitude)
|
|
||||||
mapView.controller.setZoom(10.0)
|
|
||||||
mapView.controller.setCenter(startPoint)
|
|
||||||
|
|
||||||
val marker = Marker(mapView)
|
|
||||||
marker.isDraggable = true
|
|
||||||
marker.position = startPoint
|
|
||||||
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
|
||||||
|
|
||||||
mapView.overlays.add(object: Overlay() {
|
|
||||||
override fun onSingleTapConfirmed(e: MotionEvent?, mapView: MapView?): Boolean {
|
|
||||||
val proj: Projection = mapView!!.projection
|
|
||||||
val loc = proj.fromPixels(e!!.x.toInt(), e.y.toInt()) as GeoPoint
|
|
||||||
marker.position = loc
|
|
||||||
mapView.invalidate()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mapView.overlays.add(marker)
|
|
||||||
|
|
||||||
val applyButton = findViewById<Button>(R.id.apply_location_button)
|
|
||||||
applyButton.setOnClickListener {
|
|
||||||
val bundle = Bundle()
|
|
||||||
bundle.putFloat("latitude", marker.position.latitude.toFloat())
|
|
||||||
bundle.putFloat("longitude", marker.position.longitude.toFloat())
|
|
||||||
setResult(RESULT_OK, intent.putExtra("location", bundle))
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
val setPreciseLocationButton = findViewById<Button>(R.id.set_precise_location_button)
|
|
||||||
|
|
||||||
setPreciseLocationButton.setOnClickListener {
|
|
||||||
val locationDialog = layoutInflater.inflate(R.layout.precise_location_dialog, null)
|
|
||||||
val dialogLatitude = locationDialog.findViewById<EditText>(R.id.dialog_latitude).also { it.setText(marker.position.latitude.toString()) }
|
|
||||||
val dialogLongitude = locationDialog.findViewById<EditText>(R.id.dialog_longitude).also { it.setText(marker.position.longitude.toString()) }
|
|
||||||
|
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setView(locationDialog)
|
|
||||||
.setTitle("Set a precise location")
|
|
||||||
.setPositiveButton("Set") { _, _ ->
|
|
||||||
val latitude = dialogLatitude.text.toString().toDoubleOrNull()
|
|
||||||
val longitude = dialogLongitude.text.toString().toDoubleOrNull()
|
|
||||||
if (latitude != null && longitude != null) {
|
|
||||||
val preciseLocation = GeoPoint(latitude, longitude)
|
|
||||||
mapView.controller.setCenter(preciseLocation)
|
|
||||||
marker.position = preciseLocation
|
|
||||||
mapView.invalidate()
|
|
||||||
}
|
|
||||||
}.setNegativeButton("Cancel") { _, _ -> }.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
mapView.onDetach()
|
|
||||||
}
|
|
||||||
}
|
|
@ -149,7 +149,10 @@ class FeaturesSection : Section() {
|
|||||||
|
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = { showDialog = false }
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false
|
||||||
|
),
|
||||||
|
onDismissRequest = { showDialog = false },
|
||||||
) {
|
) {
|
||||||
dialogComposable()
|
dialogComposable()
|
||||||
}
|
}
|
||||||
@ -182,6 +185,24 @@ class FeaturesSection : Section() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DataProcessors.Type.MAP_COORDINATES -> {
|
||||||
|
registerDialogOnClickCallback()
|
||||||
|
dialogComposable = {
|
||||||
|
alertDialogs.ChooseLocationDialog(property) {
|
||||||
|
showDialog = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.widthIn(0.dp, 120.dp),
|
||||||
|
text = (propertyValue.get() as Pair<*, *>).let {
|
||||||
|
"${it.first.toString().toFloatOrNull() ?: 0F}, ${it.second.toString().toFloatOrNull() ?: 0F}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
DataProcessors.Type.STRING_UNIQUE_SELECTION -> {
|
DataProcessors.Type.STRING_UNIQUE_SELECTION -> {
|
||||||
registerDialogOnClickCallback()
|
registerDialogOnClickCallback()
|
||||||
|
|
||||||
|
@ -1,41 +1,52 @@
|
|||||||
package me.rhunk.snapenhance.ui.util
|
package me.rhunk.snapenhance.ui.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.MotionEvent
|
||||||
import androidx.compose.foundation.ScrollState
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
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.viewinterop.AndroidView
|
||||||
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
|
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
|
||||||
import me.rhunk.snapenhance.common.config.DataProcessors
|
import me.rhunk.snapenhance.common.config.DataProcessors
|
||||||
import me.rhunk.snapenhance.common.config.PropertyPair
|
import me.rhunk.snapenhance.common.config.PropertyPair
|
||||||
|
import org.osmdroid.config.Configuration
|
||||||
|
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||||
|
import org.osmdroid.util.GeoPoint
|
||||||
|
import org.osmdroid.views.CustomZoomButtonsController
|
||||||
|
import org.osmdroid.views.MapView
|
||||||
|
import org.osmdroid.views.overlay.Marker
|
||||||
|
import org.osmdroid.views.overlay.Overlay
|
||||||
|
|
||||||
|
|
||||||
class AlertDialogs(
|
class AlertDialogs(
|
||||||
private val translation: LocaleWrapper,
|
private val translation: LocaleWrapper,
|
||||||
){
|
){
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultDialogCard(content: @Composable ColumnScope.() -> Unit) {
|
fun DefaultDialogCard(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||||
Card(
|
Card(
|
||||||
shape = MaterialTheme.shapes.medium,
|
shape = MaterialTheme.shapes.medium,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(10.dp, 5.dp, 10.dp, 10.dp),
|
.padding(10.dp, 5.dp, 10.dp, 10.dp)
|
||||||
|
.then(modifier),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -195,7 +206,9 @@ class AlertDialogs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(top = 10.dp).fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.padding(top = 10.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
) {
|
) {
|
||||||
Button(onClick = { dismiss() }) {
|
Button(onClick = { dismiss() }) {
|
||||||
@ -252,7 +265,9 @@ class AlertDialogs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(top = 10.dp).fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.padding(top = 10.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
) {
|
) {
|
||||||
Button(onClick = { onDismiss() }) {
|
Button(onClick = { onDismiss() }) {
|
||||||
@ -305,4 +320,145 @@ class AlertDialogs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChooseLocationDialog(property: PropertyPair<*>, dismiss: () -> Unit = {}) {
|
||||||
|
val coordinates = remember {
|
||||||
|
(property.value.get() as Pair<*, *>).let {
|
||||||
|
it.first.toString().toDouble() to it.second.toString().toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
Configuration.getInstance().load(context, context.getSharedPreferences("osmdroid", Context.MODE_PRIVATE))
|
||||||
|
}
|
||||||
|
|
||||||
|
var marker by remember { mutableStateOf<Marker?>(null) }
|
||||||
|
val mapView = remember {
|
||||||
|
MapView(context).apply {
|
||||||
|
setMultiTouchControls(true)
|
||||||
|
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
||||||
|
setTileSource(TileSourceFactory.MAPNIK)
|
||||||
|
|
||||||
|
val startPoint = GeoPoint(coordinates.first, coordinates.second)
|
||||||
|
controller.setZoom(10.0)
|
||||||
|
controller.setCenter(startPoint)
|
||||||
|
|
||||||
|
marker = Marker(this).apply {
|
||||||
|
isDraggable = true
|
||||||
|
position = startPoint
|
||||||
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||||
|
}
|
||||||
|
|
||||||
|
overlays.add(object: Overlay() {
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent, mapView: MapView): Boolean {
|
||||||
|
marker?.position = mapView.projection.fromPixels(e.x.toInt(), e.y.toInt()) as GeoPoint
|
||||||
|
mapView.invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
overlays.add(marker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
mapView.onDetach()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var customCoordinatesDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().fillMaxHeight(fraction = 0.9f),
|
||||||
|
) {
|
||||||
|
AndroidView(
|
||||||
|
factory = { mapView }
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(10.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
) {
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = {
|
||||||
|
val lat = marker?.position?.latitude ?: coordinates.first
|
||||||
|
val lon = marker?.position?.longitude ?: coordinates.second
|
||||||
|
property.value.setAny(lat to lon)
|
||||||
|
dismiss()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.padding(5.dp),
|
||||||
|
imageVector = Icons.Filled.Check,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FilledIconButton(
|
||||||
|
onClick = {
|
||||||
|
customCoordinatesDialog = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(60.dp)
|
||||||
|
.padding(5.dp),
|
||||||
|
imageVector = Icons.Filled.Edit,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customCoordinatesDialog) {
|
||||||
|
val lat = remember { mutableStateOf(coordinates.first.toString()) }
|
||||||
|
val lon = remember { mutableStateOf(coordinates.second.toString()) }
|
||||||
|
|
||||||
|
DefaultDialogCard(
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 10.dp),
|
||||||
|
value = lat.value,
|
||||||
|
onValueChange = { lat.value = it },
|
||||||
|
label = { Text(text = "Latitude") },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 10.dp),
|
||||||
|
value = lon.value,
|
||||||
|
onValueChange = { lon.value = it },
|
||||||
|
label = { Text(text = "Longitude") },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
customCoordinatesDialog = false
|
||||||
|
}) {
|
||||||
|
Text(text = translation["button.cancel"])
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
marker?.position = GeoPoint(lat.value.toDouble(), lon.value.toDouble())
|
||||||
|
mapView.controller.setCenter(marker?.position)
|
||||||
|
mapView.invalidate()
|
||||||
|
customCoordinatesDialog = false
|
||||||
|
}) {
|
||||||
|
Text(text = translation["button.ok"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<org.osmdroid.views.MapView
|
|
||||||
android:id="@+id/mapView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" >
|
|
||||||
|
|
||||||
</org.osmdroid.views.MapView>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/set_precise_location_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="left"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:background="@android:color/white"
|
|
||||||
android:text="Set Precise Location"
|
|
||||||
android:textSize="20sp"
|
|
||||||
tools:ignore="HardcodedText,RtlHardcoded" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/apply_location_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="right"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:layout_marginRight="20dp"
|
|
||||||
android:background="@android:color/white"
|
|
||||||
android:text="Apply"
|
|
||||||
android:textSize="20sp"
|
|
||||||
tools:ignore="HardcodedText,RtlHardcoded" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="20dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:ignore="HardcodedText">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/dialog_latitude"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:autofillHints=""
|
|
||||||
android:ems="10"
|
|
||||||
android:hint="Latitude"
|
|
||||||
android:inputType="number|numberDecimal|numberSigned" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/dialog_longitude"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:autofillHints=""
|
|
||||||
android:ems="10"
|
|
||||||
android:hint="Longitude"
|
|
||||||
android:inputType="number|numberDecimal|numberSigned" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -356,6 +356,16 @@
|
|||||||
"name": "Global",
|
"name": "Global",
|
||||||
"description": "Tweak Global Snapchat Settings",
|
"description": "Tweak Global Snapchat Settings",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"spoofLocation": {
|
||||||
|
"name": "Location",
|
||||||
|
"description": "Spoof your location",
|
||||||
|
"properties": {
|
||||||
|
"coordinates": {
|
||||||
|
"name": "Coordinates",
|
||||||
|
"description": "Set the coordinates"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"snapchat_plus": {
|
"snapchat_plus": {
|
||||||
"name": "Snapchat Plus",
|
"name": "Snapchat Plus",
|
||||||
"description": "Enables Snapchat Plus features\nSome Server-sided features may not work"
|
"description": "Enables Snapchat Plus features\nSome Server-sided features may not work"
|
||||||
@ -464,20 +474,6 @@
|
|||||||
"name": "Spoof",
|
"name": "Spoof",
|
||||||
"description": "Spoof various information about you",
|
"description": "Spoof various information about you",
|
||||||
"properties": {
|
"properties": {
|
||||||
"location": {
|
|
||||||
"name": "Location",
|
|
||||||
"description": "Spoof your location",
|
|
||||||
"properties": {
|
|
||||||
"location_latitude": {
|
|
||||||
"name": "Latitude",
|
|
||||||
"description": "The latitude of the location"
|
|
||||||
},
|
|
||||||
"location_longitude": {
|
|
||||||
"name": "Longitude",
|
|
||||||
"description": "The longitude of the location"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"device": {
|
"device": {
|
||||||
"name": "Device",
|
"name": "Device",
|
||||||
"description": "Spoof your device information",
|
"description": "Spoof your device information",
|
||||||
|
@ -8,8 +8,7 @@ enum class EnumAction(
|
|||||||
val isCritical: Boolean = false,
|
val isCritical: Boolean = false,
|
||||||
) {
|
) {
|
||||||
CLEAN_CACHE("clean_snapchat_cache", exitOnFinish = true),
|
CLEAN_CACHE("clean_snapchat_cache", exitOnFinish = true),
|
||||||
EXPORT_CHAT_MESSAGES("export_chat_messages"),
|
EXPORT_CHAT_MESSAGES("export_chat_messages");
|
||||||
OPEN_MAP("open_map");
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_PARAMETER = "se_action"
|
const val ACTION_PARAMETER = "se_action"
|
||||||
|
@ -60,6 +60,12 @@ open class ConfigContainer(
|
|||||||
container.parentContainerKey = it
|
container.parentContainerKey = it
|
||||||
}.get()
|
}.get()
|
||||||
|
|
||||||
|
protected fun mapCoordinates(
|
||||||
|
key: String,
|
||||||
|
defaultValue: Pair<Double, Double> = 0.0 to 0.0,
|
||||||
|
params: ConfigParamsBuilder = {}
|
||||||
|
) = registerProperty(key, DataProcessors.MAP_COORDINATES, PropertyValue(defaultValue), params)
|
||||||
|
|
||||||
fun toJson(): JsonObject {
|
fun toJson(): JsonObject {
|
||||||
val json = JsonObject()
|
val json = JsonObject()
|
||||||
properties.forEach { (propertyKey, propertyValue) ->
|
properties.forEach { (propertyKey, propertyValue) ->
|
||||||
|
@ -14,6 +14,7 @@ object DataProcessors {
|
|||||||
FLOAT,
|
FLOAT,
|
||||||
STRING_MULTIPLE_SELECTION,
|
STRING_MULTIPLE_SELECTION,
|
||||||
STRING_UNIQUE_SELECTION,
|
STRING_UNIQUE_SELECTION,
|
||||||
|
MAP_COORDINATES,
|
||||||
CONTAINER,
|
CONTAINER,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +76,20 @@ object DataProcessors {
|
|||||||
deserialize = { obj -> obj.takeIf { !it.isJsonNull }?.asString }
|
deserialize = { obj -> obj.takeIf { !it.isJsonNull }?.asString }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val MAP_COORDINATES = PropertyDataProcessor(
|
||||||
|
type = Type.MAP_COORDINATES,
|
||||||
|
serialize = {
|
||||||
|
JsonObject().apply {
|
||||||
|
addProperty("lat", it.first)
|
||||||
|
addProperty("lng", it.second)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deserialize = { obj ->
|
||||||
|
val jsonObject = obj.asJsonObject
|
||||||
|
jsonObject["lat"].asDouble to jsonObject["lng"].asDouble
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
fun <T : ConfigContainer> container(container: T) = PropertyDataProcessor(
|
fun <T : ConfigContainer> container(container: T) = PropertyDataProcessor(
|
||||||
type = Type.CONTAINER,
|
type = Type.CONTAINER,
|
||||||
serialize = {
|
serialize = {
|
||||||
|
@ -4,6 +4,10 @@ import me.rhunk.snapenhance.common.config.ConfigContainer
|
|||||||
import me.rhunk.snapenhance.common.config.FeatureNotice
|
import me.rhunk.snapenhance.common.config.FeatureNotice
|
||||||
|
|
||||||
class Global : ConfigContainer() {
|
class Global : ConfigContainer() {
|
||||||
|
inner class SpoofLocation : ConfigContainer(hasGlobalState = true) {
|
||||||
|
val coordinates = mapCoordinates("coordinates", 0.0 to 0.0) { requireRestart()} // lat, long
|
||||||
|
}
|
||||||
|
val spoofLocation = container("spoofLocation", SpoofLocation())
|
||||||
val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.BAN_RISK); requireRestart() }
|
val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.BAN_RISK); requireRestart() }
|
||||||
val disableMetrics = boolean("disable_metrics")
|
val disableMetrics = boolean("disable_metrics")
|
||||||
val blockAds = boolean("block_ads")
|
val blockAds = boolean("block_ads")
|
||||||
|
@ -4,12 +4,6 @@ import me.rhunk.snapenhance.common.config.ConfigContainer
|
|||||||
import me.rhunk.snapenhance.common.config.FeatureNotice
|
import me.rhunk.snapenhance.common.config.FeatureNotice
|
||||||
|
|
||||||
class Spoof : ConfigContainer() {
|
class Spoof : ConfigContainer() {
|
||||||
inner class Location : ConfigContainer(hasGlobalState = true) {
|
|
||||||
val latitude = float("location_latitude")
|
|
||||||
val longitude = float("location_longitude")
|
|
||||||
}
|
|
||||||
val location = container("location", Location())
|
|
||||||
|
|
||||||
inner class Device : ConfigContainer(hasGlobalState = true) {
|
inner class Device : ConfigContainer(hasGlobalState = true) {
|
||||||
val fingerprint = string("fingerprint")
|
val fingerprint = string("fingerprint")
|
||||||
val androidId = string("android_id")
|
val androidId = string("android_id")
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.core.action.impl
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import me.rhunk.snapenhance.common.BuildConfig
|
|
||||||
import me.rhunk.snapenhance.core.action.AbstractAction
|
|
||||||
|
|
||||||
class OpenMap: AbstractAction() {
|
|
||||||
override fun run() {
|
|
||||||
context.runOnUiThread {
|
|
||||||
val mapActivityIntent = Intent()
|
|
||||||
mapActivityIntent.setClassName(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + ".ui.MapActivity")
|
|
||||||
mapActivityIntent.putExtra("location", Bundle().apply {
|
|
||||||
putDouble("latitude", context.config.experimental.spoof.location.latitude.get().toDouble())
|
|
||||||
putDouble("longitude", context.config.experimental.spoof.location.longitude.get().toDouble())
|
|
||||||
})
|
|
||||||
|
|
||||||
context.mainActivity!!.startActivityForResult(mapActivityIntent, 0x1337)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +1,28 @@
|
|||||||
package me.rhunk.snapenhance.core.features.impl.global
|
package me.rhunk.snapenhance.core.features.impl.global
|
||||||
|
|
||||||
import android.content.Intent
|
import android.location.Location
|
||||||
|
import android.location.LocationManager
|
||||||
import me.rhunk.snapenhance.core.features.Feature
|
import me.rhunk.snapenhance.core.features.Feature
|
||||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.Hooker
|
|
||||||
import me.rhunk.snapenhance.core.util.hook.hook
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
|
|
||||||
class LocationSpoofer: Feature("LocationSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class LocationSpoofer: Feature("LocationSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
override fun onActivityCreate() {
|
||||||
Hooker.hook(context.mainActivity!!.javaClass, "onActivityResult", HookStage.BEFORE) { param ->
|
if (context.config.global.spoofLocation.globalState != true) return
|
||||||
val intent = param.argNullable<Intent>(2) ?: return@hook
|
|
||||||
val bundle = intent.getBundleExtra("location") ?: return@hook
|
|
||||||
param.setResult(null)
|
|
||||||
|
|
||||||
with(context.config.experimental.spoof.location) {
|
val coordinates by context.config.global.spoofLocation.coordinates
|
||||||
latitude.set(bundle.getFloat("latitude"))
|
|
||||||
longitude.set(bundle.getFloat("longitude"))
|
|
||||||
|
|
||||||
context.longToast("Location set to $latitude, $longitude")
|
Location::class.java.apply {
|
||||||
}
|
hook("getLatitude", HookStage.BEFORE) { it.setResult(coordinates.first) }
|
||||||
|
hook("getLongitude", HookStage.BEFORE) { it.setResult(coordinates.second) }
|
||||||
|
hook("getAccuracy", HookStage.BEFORE) { it.setResult(0.0F) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.config.experimental.spoof.location.globalState != true) return
|
LocationManager::class.java.apply {
|
||||||
|
//Might be redundant because it calls isProviderEnabledForUser which we also hook, meaning if isProviderEnabledForUser returns true this will also return true
|
||||||
val latitude by context.config.experimental.spoof.location.latitude
|
hook("isProviderEnabled", HookStage.BEFORE) { it.setResult(true) }
|
||||||
val longitude by context.config.experimental.spoof.location.longitude
|
hook("isProviderEnabledForUser", HookStage.BEFORE) { it.setResult(true) }
|
||||||
|
}
|
||||||
val locationClass = android.location.Location::class.java
|
|
||||||
val locationManagerClass = android.location.LocationManager::class.java
|
|
||||||
|
|
||||||
locationClass.hook("getLatitude", HookStage.BEFORE) { it.setResult(latitude.toDouble()) }
|
|
||||||
locationClass.hook("getLongitude", HookStage.BEFORE) { it.setResult(longitude.toDouble()) }
|
|
||||||
locationClass.hook("getAccuracy", HookStage.BEFORE) { it.setResult(0.0F) }
|
|
||||||
|
|
||||||
//Might be redundant because it calls isProviderEnabledForUser which we also hook, meaning if isProviderEnabledForUser returns true this will also return true
|
|
||||||
locationManagerClass.hook("isProviderEnabled", HookStage.BEFORE) { it.setResult(true) }
|
|
||||||
locationManagerClass.hook("isProviderEnabledForUser", HookStage.BEFORE) { it.setResult(true) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,6 @@ import me.rhunk.snapenhance.common.action.EnumAction
|
|||||||
import me.rhunk.snapenhance.core.ModContext
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
import me.rhunk.snapenhance.core.action.impl.CleanCache
|
import me.rhunk.snapenhance.core.action.impl.CleanCache
|
||||||
import me.rhunk.snapenhance.core.action.impl.ExportChatMessages
|
import me.rhunk.snapenhance.core.action.impl.ExportChatMessages
|
||||||
import me.rhunk.snapenhance.core.action.impl.OpenMap
|
|
||||||
import me.rhunk.snapenhance.core.manager.Manager
|
import me.rhunk.snapenhance.core.manager.Manager
|
||||||
|
|
||||||
class ActionManager(
|
class ActionManager(
|
||||||
@ -16,7 +15,6 @@ class ActionManager(
|
|||||||
mapOf(
|
mapOf(
|
||||||
EnumAction.CLEAN_CACHE to CleanCache::class,
|
EnumAction.CLEAN_CACHE to CleanCache::class,
|
||||||
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class,
|
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class,
|
||||||
EnumAction.OPEN_MAP to OpenMap::class,
|
|
||||||
).map {
|
).map {
|
||||||
it.key to it.value.java.getConstructor().newInstance().apply {
|
it.key to it.value.java.getConstructor().newInstance().apply {
|
||||||
this.context = modContext
|
this.context = modContext
|
||||||
|
Loading…
x
Reference in New Issue
Block a user