1
0

Compare commits

..

5 Commits

Author SHA1 Message Date
4d86cd0630
bump to 1.1 2025-05-22 23:34:02 +03:00
b55ee94d8a
feat: add stop tracking button 2025-05-22 23:31:26 +03:00
7bee8910ec
feat: add setting sending interval 2025-05-22 22:09:13 +03:00
d6f352be29
chore: change default URL 2025-05-22 00:12:25 +03:00
52816c155b
fix: change API 2025-05-21 23:55:40 +03:00
5 changed files with 62 additions and 17 deletions

View File

@ -17,8 +17,8 @@ android {
applicationId = "ru.risdeveau.geotracker" applicationId = "ru.risdeveau.geotracker"
minSdk = 24 minSdk = 24
targetSdk = 35 targetSdk = 35
versionCode = 1 versionCode = 2
versionName = "1.0" versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -48,11 +48,11 @@ suspend fun health(baseurl: String): Boolean {
suspend fun sendGeo(baseurl: String = SettingsPreferences.url, data: GeoData): Boolean { suspend fun sendGeo(baseurl: String = SettingsPreferences.url, data: GeoData): Boolean {
try { try {
val json = JSONObject() val json = JSONObject()
json.put("ln", data.ln) json.put("lon", data.ln)
json.put("lt", data.lt) json.put("lat", data.lt)
json.put("nick", data.nick) json.put("user_name", data.nick)
client.post("$baseurl/map") { client.post("$baseurl/api/app") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(json.toString(2)) setBody(json.toString(2))
} }

View File

@ -8,6 +8,7 @@ package ru.risdeveau.geotracker
import splitties.preferences.Preferences import splitties.preferences.Preferences
object SettingsPreferences : Preferences("settings") { object SettingsPreferences : Preferences("settings") {
var username by stringPref("username", "anonimous") var username by stringPref("username", "")
var url by stringPref("url", "https://example.com") var url by stringPref("url", "https://geo.tmp.codrs.ru")
val interval by IntPref("interval", 15)
} }

View File

@ -5,6 +5,7 @@ import android.Manifest.permission.POST_NOTIFICATIONS
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
@ -41,12 +42,14 @@ class LocationForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("Service", "onStartCommand") Log.d("Service", "onStartCommand")
if (!(hasPermission(ACCESS_FINE_LOCATION) if (
!(hasPermission(ACCESS_FINE_LOCATION)
&& ( && (
(Build.VERSION.SDK_INT < 33) (Build.VERSION.SDK_INT < 33)
|| hasPermission(POST_NOTIFICATIONS) || hasPermission(POST_NOTIFICATIONS)
) ))
)) { || intent?.action == ACTION_STOP_SERVICE
) {
stopSelf() stopSelf()
return START_NOT_STICKY return START_NOT_STICKY
} }
@ -55,11 +58,12 @@ class LocationForegroundService : Service() {
val notification = createNotification() val notification = createNotification()
startForeground(1, notification) startForeground(1, notification)
locationTracker.startTracking(5000) locationTracker.startTracking(SettingsPreferences.interval * 1000L)
return START_STICKY return START_STICKY
} }
override fun onDestroy() { override fun onDestroy() {
Log.d("Service", "Destroyed")
locationTracker.stopTracking() locationTracker.stopTracking()
super.onDestroy() super.onDestroy()
} }
@ -69,8 +73,8 @@ class LocationForegroundService : Service() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
"location_channel", "location_channel",
"Location Tracking", "Отправка Местоположения",
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_HIGH
) )
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
@ -78,12 +82,36 @@ class LocationForegroundService : Service() {
} }
private fun createNotification(): Notification { private fun createNotification(): Notification {
val stopIntent = Intent(this, LocationForegroundService::class.java).apply {
action = ACTION_STOP_SERVICE
}
val stopPendingIntent = PendingIntent.getService(
this,
0,
stopIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
else
PendingIntent.FLAG_UPDATE_CURRENT
)
return NotificationCompat.Builder(this, "location_channel") return NotificationCompat.Builder(this, "location_channel")
.setContentTitle("Отслеживание местоположения") .setContentTitle("Отслеживание местоположения")
.setContentText("Обновление каждые 5 секунд") .setContentText("Обновление каждые ${SettingsPreferences.interval} секунд")
.setSmallIcon(R.drawable.ic_launcher_foreground) .setSmallIcon(R.drawable.share_location)
.addAction(
R.drawable.cancel,
"Остановить",
stopPendingIntent
)
.setOngoing(true)
.build() .build()
} }
override fun onBind(intent: Intent?): IBinder? = null override fun onBind(intent: Intent?): IBinder? = null
}
companion object {
const val ACTION_STOP_SERVICE = "ru.risdeveau.geotracker.STOP_SERVICE"
}
}

View File

@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Done import androidx.compose.material.icons.outlined.Done
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -33,6 +34,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -40,6 +42,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -89,6 +92,7 @@ class MainActivity : ComponentActivity() {
launch { launch {
screen = if ( screen = if (
health(SettingsPreferences.url) health(SettingsPreferences.url)
&& SettingsPreferences.username.isNotBlank()
&& hasPermission(ACCESS_FINE_LOCATION) && hasPermission(ACCESS_FINE_LOCATION)
&& ( && (
(Build.VERSION.SDK_INT < 33) (Build.VERSION.SDK_INT < 33)
@ -121,6 +125,7 @@ sealed class Screen {
fun Settings(modifier: Modifier = Modifier, onConfirm: () -> Unit) { fun Settings(modifier: Modifier = Modifier, onConfirm: () -> Unit) {
var username by remember { mutableStateOf(SettingsPreferences.username) } var username by remember { mutableStateOf(SettingsPreferences.username) }
var url by remember { mutableStateOf(SettingsPreferences.url) } var url by remember { mutableStateOf(SettingsPreferences.url) }
var interval by remember { mutableIntStateOf(SettingsPreferences.interval) }
var urlIsValid by remember { mutableStateOf(false) } var urlIsValid by remember { mutableStateOf(false) }
var loading by remember { mutableStateOf(false) } var loading by remember { mutableStateOf(false) }
var fineLoc by remember { mutableStateOf(hasPermission(ACCESS_FINE_LOCATION)) } var fineLoc by remember { mutableStateOf(hasPermission(ACCESS_FINE_LOCATION)) }
@ -162,6 +167,17 @@ fun Settings(modifier: Modifier = Modifier, onConfirm: () -> Unit) {
} }
) )
OutlinedTextField(
value = interval.toString(),
onValueChange = {
val newVal = it.toIntOrNull()
if (newVal != null)
interval = newVal.coerceIn(1..300)
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
label = { Text("Интервал отправки") }
)
GetPermission(ACCESS_FINE_LOCATION) { fineLoc = true; } GetPermission(ACCESS_FINE_LOCATION) { fineLoc = true; }
if (Build.VERSION.SDK_INT >= 33) if (Build.VERSION.SDK_INT >= 33)