Compare commits
7 Commits
4d86cd0630
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
386fb20bff
|
|||
|
2f4cfa33ad
|
|||
|
5f7d4c0163
|
|||
|
4e517b87cd
|
|||
|
831b6bf491
|
|||
|
515f950bde
|
|||
|
761bd921ab
|
+20
-3
@@ -3,6 +3,8 @@
|
|||||||
* Copyright (c) 2025. All rights reserved.
|
* Copyright (c) 2025. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
@@ -15,12 +17,22 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "ru.risdeveau.geotracker"
|
applicationId = "ru.risdeveau.geotracker"
|
||||||
minSdk = 24
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 3
|
||||||
versionName = "1.0"
|
versionName = "1.2"
|
||||||
|
setProperty("archivesBaseName", "$applicationId-v$versionCode($versionName)")
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
val secretProperties = Properties().apply {
|
||||||
|
val secretFile = rootProject.file("secrets.properties")
|
||||||
|
if (secretFile.exists())
|
||||||
|
secretFile.inputStream().use { load(it) }
|
||||||
|
else
|
||||||
|
println("Warning: secrets.properties not found!")
|
||||||
|
}
|
||||||
|
manifestPlaceholders["sentry_url"] = secretProperties.getProperty("SENTRY_URL")!!
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -30,6 +42,10 @@ android {
|
|||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
|
manifestPlaceholders["sentry_env"] = "production"
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
manifestPlaceholders["sentry_env"] = "develop"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -71,4 +87,5 @@ dependencies {
|
|||||||
implementation(libs.logback.classic)
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
implementation(libs.splitties.base)
|
implementation(libs.splitties.base)
|
||||||
|
implementation(libs.sentry)
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,35 @@
|
|||||||
android:theme="@style/Theme.GeoTracker"
|
android:theme="@style/Theme.GeoTracker"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.dsn"
|
||||||
|
android:value="${sentry_url}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.environment"
|
||||||
|
android:value="${sentry_env}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.send-default-pii"
|
||||||
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.traces.sample-rate"
|
||||||
|
android:value="1.0" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.traces.user-interaction.enable"
|
||||||
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.attach-screenshot"
|
||||||
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.attach-view-hierarchy"
|
||||||
|
android:value="true" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.session-replay.on-error-sample-rate"
|
||||||
|
android:value="1.0" />
|
||||||
|
<meta-data
|
||||||
|
android:name="io.sentry.session-replay.session-sample-rate"
|
||||||
|
android:value="0.1" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -12,3 +12,9 @@ object SettingsPreferences : Preferences("settings") {
|
|||||||
var url by stringPref("url", "https://geo.tmp.codrs.ru")
|
var url by stringPref("url", "https://geo.tmp.codrs.ru")
|
||||||
val interval by IntPref("interval", 15)
|
val interval by IntPref("interval", 15)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object StatisticsPreferences : Preferences("statistics") {
|
||||||
|
var totalSent by IntPref("total_sent", 0)
|
||||||
|
var sessionSent by IntPref("session_sent", 0)
|
||||||
|
var lastSent by LongPref("last_sent", 0)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -15,6 +16,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
class LocationForegroundService : Service() {
|
class LocationForegroundService : Service() {
|
||||||
@@ -35,18 +37,26 @@ class LocationForegroundService : Service() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatisticsPreferences.apply {
|
||||||
|
totalSent++
|
||||||
|
sessionSent++
|
||||||
|
lastSent = Instant.now().epochSecond
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -60,30 +70,53 @@ class LocationForegroundService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
Log.d("Service", "Destroyed")
|
||||||
locationTracker.stopTracking()
|
locationTracker.stopTracking()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
Log.d("Service", "createNotificationChannel")
|
Log.d("Service", "createNotificationChannel")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val channel = NotificationChannel(
|
||||||
val channel = NotificationChannel(
|
"location_channel",
|
||||||
"location_channel",
|
"Отправка Местоположения",
|
||||||
"Location Tracking",
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
NotificationManager.IMPORTANCE_LOW
|
)
|
||||||
)
|
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
manager.createNotificationChannel(channel)
|
||||||
manager.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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("Обновление каждые ${SettingsPreferences.interval} секунд")
|
.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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +50,7 @@ import ru.risdeveau.geotracker.ui.theme.GeoTrackerTheme
|
|||||||
import splitties.experimental.ExperimentalSplittiesApi
|
import splitties.experimental.ExperimentalSplittiesApi
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import splitties.resources.appStr
|
import splitties.resources.appStr
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
@@ -68,13 +69,29 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
when (screen) {
|
when (screen) {
|
||||||
Screen.Main -> {
|
Screen.Main -> {
|
||||||
|
var totalSent by remember { mutableIntStateOf(0) }
|
||||||
|
var sessionSent by remember { mutableIntStateOf(0) }
|
||||||
|
var lastSent by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
Log.d("Thread", "Starting...")
|
Log.d("Thread", "Starting...")
|
||||||
startLocationService()
|
startLocationService()
|
||||||
|
StatisticsPreferences.sessionSent = 0
|
||||||
Log.d("Thread", "Started")
|
Log.d("Thread", "Started")
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
totalSent = StatisticsPreferences.totalSent
|
||||||
|
sessionSent = StatisticsPreferences.sessionSent
|
||||||
|
lastSent = (Instant.now().epochSecond - StatisticsPreferences.lastSent).toInt()
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("Hello world")
|
Column(Modifier.align(Alignment.Center)) {
|
||||||
|
Text("Всего отправлено: $totalSent")
|
||||||
|
Text("Отправлено за эту сессию: $sessionSent")
|
||||||
|
Text("В последний раз было отпавлено $lastSent секунд назад")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Screen.Settings -> {
|
Screen.Settings -> {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M336,680L480,536L624,680L680,624L536,480L680,336L624,280L480,424L336,280L280,336L424,480L280,624L336,680ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M519,878L519,798Q561,792 600.5,775Q640,758 675,732L733,790Q686,827 632,849.5Q578,872 519,878ZM789,732L733,676Q759,643 775,603.5Q791,564 797,520L879,520Q871,582 848.5,635.5Q826,689 789,732ZM797,440Q791,395 775,355.5Q759,316 733,284L789,228Q827,272 850.5,326Q874,380 879,440L797,440ZM439,878Q286,860 183.5,747Q81,634 81,480Q81,325 183.5,212Q286,99 439,82L439,162Q319,179 240,269Q161,359 161,480Q161,601 240,690.5Q319,780 439,798L439,878ZM677,228Q641,201 601,184Q561,167 519,162L519,82Q578,87 632,109.5Q686,132 733,170L677,228ZM480,680Q422,631 371,575Q320,519 320,444Q320,376 366.5,328Q413,280 480,280Q547,280 593.5,328Q640,376 640,444Q640,519 589,575Q538,631 480,680ZM480,480Q498,480 510.5,467.5Q523,455 523,437Q523,420 510.5,407Q498,394 480,394Q462,394 449.5,407Q437,420 437,437Q437,455 449.5,467.5Q462,480 480,480Z"/>
|
||||||
|
</vector>
|
||||||
@@ -11,6 +11,7 @@ lifecycleRuntimeKtx = "2.8.7"
|
|||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
logbackClassic = "1.2.11"
|
logbackClassic = "1.2.11"
|
||||||
|
sentry = "8.12.0"
|
||||||
splittiesFunPackAndroidBase = "3.0.0"
|
splittiesFunPackAndroidBase = "3.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -35,6 +36,7 @@ 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-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", 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" }
|
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logbackClassic" }
|
||||||
|
sentry = { module = "io.sentry:sentry-android", version.ref = "sentry" }
|
||||||
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
|
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base", version.ref = "splittiesFunPackAndroidBase" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|||||||
Reference in New Issue
Block a user