1
0

feat: add sending geocoords

This commit is contained in:
Sweetbread 2025-05-03 21:34:18 +03:00
parent db766c2acc
commit 8a54db266f
Signed by: Sweetbread
GPG Key ID: 17A5CB9A7DD85319
7 changed files with 194 additions and 3 deletions

View File

@ -67,6 +67,7 @@ dependencies {
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.json)
implementation(libs.logback.classic)
implementation(libs.splitties.base)

View File

@ -11,6 +11,9 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application
android:allowBackup="true"
@ -34,6 +37,12 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".LocationForegroundService"
android:foregroundServiceType="location"
android:enabled="true"
android:exported="false" />
</application>
</manifest>

View File

@ -9,7 +9,12 @@ import io.ktor.client.*
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import org.json.JSONObject
val client = HttpClient(OkHttp) {
install(Logging) {
@ -38,6 +43,22 @@ suspend fun health(baseurl: String): Boolean {
/**
* Send data to a server
* @return true if sent successfully
*/
fun sendGeo(baseurl: String, data: GeoData) {
suspend fun sendGeo(baseurl: String = SettingsPreferences.url, data: GeoData): Boolean {
try {
val json = JSONObject()
json.put("ln", data.ln)
json.put("lt", data.lt)
json.put("nick", data.nick)
client.post("$baseurl/map") {
contentType(ContentType.Application.Json)
setBody(json.toString(2))
}
return true
} catch (e: Exception) {
println("Error: ${e.message}")
return false
}
}

View File

@ -0,0 +1,76 @@
package ru.risdeveau.geotracker
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class LocationForegroundService : Service() {
private lateinit var locationTracker: LocationTracker
private val serviceScope = CoroutineScope(Dispatchers.Main + Job())
override fun onCreate() {
Log.d("Service", "onCreate")
super.onCreate()
locationTracker = LocationTracker(this) { location ->
serviceScope.launch {
sendGeo(
data = GeoData(
lt = location.latitude,
ln = location.longitude,
nick = SettingsPreferences.username
)
)
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("Service", "onStartCommand")
createNotificationChannel()
val notification = createNotification()
startForeground(1, notification)
locationTracker.startTracking(5000)
return START_STICKY
}
override fun onDestroy() {
locationTracker.stopTracking()
super.onDestroy()
}
private fun createNotificationChannel() {
Log.d("Service", "createNotificationChannel")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"location_channel",
"Location Tracking",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
private fun createNotification(): Notification {
return NotificationCompat.Builder(this, "location_channel")
.setContentTitle("Отслеживание местоположения")
.setContentText("Обновление каждые 5 секунд")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build()
}
override fun onBind(intent: Intent?): IBinder? = null
}

View File

@ -0,0 +1,62 @@
package ru.risdeveau.geotracker
import android.annotation.SuppressLint
import android.content.Context
import android.content.Context.LOCATION_SERVICE
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
class LocationTracker(
private val context: Context,
private val onLocationUpdate: (Location) -> Unit
) : LocationListener {
private val locationManager =
context.getSystemService(LOCATION_SERVICE) as LocationManager
@SuppressLint("MissingPermission")
fun startTracking(interval: Long = 5000, minDistance: Float = 0f) {
Log.d("Tracker", "perms: ${hasPermissions()}")
if (!hasPermissions()) return
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
interval,
minDistance,
this,
Looper.getMainLooper()
)
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)?.let {
onLocationUpdate(it)
}
}
fun stopTracking() {
locationManager.removeUpdates(this)
}
override fun onLocationChanged(location: Location) {
onLocationUpdate(location)
}
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
private fun hasPermissions(): Boolean {
return ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
}

View File

@ -7,8 +7,11 @@ package ru.risdeveau.geotracker
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
@ -46,8 +49,10 @@ import splitties.init.appCtx
import splitties.resources.appStr
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
GeoTrackerTheme {
@ -60,6 +65,12 @@ class MainActivity : ComponentActivity() {
when (screen) {
Screen.Main -> {
LaunchedEffect(Unit) {
Log.d("Thread", "Starting...")
startLocationService()
Log.d("Thread", "Started")
}
Text("Hello world")
}
@ -101,8 +112,8 @@ sealed class Screen {
@OptIn(ExperimentalSplittiesApi::class)
@Composable
fun Settings(modifier: Modifier = Modifier, onConfirm: () -> Unit) {
var username by remember { mutableStateOf("") }
var url by remember { mutableStateOf("") }
var username by remember { mutableStateOf(SettingsPreferences.username) }
var url by remember { mutableStateOf(SettingsPreferences.url) }
var urlIsValid by remember { mutableStateOf(false) }
var loading by remember { mutableStateOf(false) }
@ -196,4 +207,14 @@ fun hasLocationPermissions(context: Context): Boolean {
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
fun startLocationService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
appCtx.startForegroundService(Intent(appCtx, LocationForegroundService::class.java))
Log.d("startLocationService", "startForegroundService")
} else {
appCtx.startService(Intent(appCtx, LocationForegroundService::class.java))
Log.d("startLocationService", "startService")
}
}

View File

@ -31,6 +31,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-json = { module = "io.ktor:ktor-client-json", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logbackClassic" }