Compare commits
3 Commits
3d2d68d145
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5019c83b80 | |||
| 8de7b4d886 | |||
| a455be8db7 |
Generated
+8
@@ -15,6 +15,14 @@
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="gpstracker">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-03-06T08:51:20.686825100Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=28161JEEJR069K" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
@@ -20,6 +20,10 @@ import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ActivitiesActivity : AppCompatActivity() {
|
||||
|
||||
@@ -35,14 +39,16 @@ class ActivitiesActivity : AppCompatActivity() {
|
||||
uri?.let { importGpxFile(it) }
|
||||
}
|
||||
|
||||
private val backupFolderLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree()
|
||||
// Backup: Wir erstellen eine ZIP-Datei
|
||||
private val createBackupLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/zip")
|
||||
) { uri ->
|
||||
uri?.let { performDriveBackup(it) }
|
||||
}
|
||||
|
||||
private val restoreFolderLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree()
|
||||
// Restore: Wir wählen eine ZIP-Datei aus
|
||||
private val restoreBackupLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
uri?.let { performDriveRestore(it) }
|
||||
}
|
||||
@@ -77,11 +83,12 @@ class ActivitiesActivity : AppCompatActivity() {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_drive_backup -> {
|
||||
backupFolderLauncher.launch(googleDriveUri())
|
||||
val date = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(java.util.Date())
|
||||
createBackupLauncher.launch("gpstracker_backup_$date.zip")
|
||||
true
|
||||
}
|
||||
R.id.action_drive_restore -> {
|
||||
restoreFolderLauncher.launch(googleDriveUri())
|
||||
restoreBackupLauncher.launch(arrayOf("application/zip", "application/x-zip-compressed"))
|
||||
true
|
||||
}
|
||||
R.id.action_import_gpx -> {
|
||||
@@ -92,10 +99,7 @@ class ActivitiesActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun googleDriveUri(): Uri =
|
||||
Uri.parse("content://com.google.android.apps.docs.storage/")
|
||||
|
||||
private fun performDriveBackup(folderUri: Uri) {
|
||||
private fun performDriveBackup(fileUri: Uri) {
|
||||
val tracks = trackStorage.getAllTracks()
|
||||
if (tracks.isEmpty()) {
|
||||
Toast.makeText(this, "Keine Tracks zum Sichern", Toast.LENGTH_SHORT).show()
|
||||
@@ -104,39 +108,28 @@ class ActivitiesActivity : AppCompatActivity() {
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val folder = DocumentFile.fromTreeUri(this@ActivitiesActivity, folderUri)
|
||||
if (folder == null || !folder.canWrite()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@ActivitiesActivity, "Ordner nicht beschreibbar", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
contentResolver.openOutputStream(fileUri)?.use { os ->
|
||||
ZipOutputStream(BufferedOutputStream(os)).use { zos ->
|
||||
tracks.forEach { track ->
|
||||
val safeName = trackStorage.sanitizeFileName(track.name)
|
||||
|
||||
// JSON Datei in ZIP
|
||||
val jsonEntry = ZipEntry("$safeName.json")
|
||||
zos.putNextEntry(jsonEntry)
|
||||
zos.write(gson.toJson(track).toByteArray(Charsets.UTF_8))
|
||||
zos.closeEntry()
|
||||
|
||||
var count = 0
|
||||
tracks.forEach { track ->
|
||||
val safeName = track.name.replace(Regex("[^a-zA-Z0-9._-]"), "_")
|
||||
|
||||
val jsonName = "$safeName.json"
|
||||
folder.findFile(jsonName)?.delete()
|
||||
folder.createFile("application/json", jsonName)?.let { doc ->
|
||||
contentResolver.openOutputStream(doc.uri)?.use { os ->
|
||||
os.write(gson.toJson(track).toByteArray(Charsets.UTF_8))
|
||||
// GPX Datei in ZIP
|
||||
val gpxEntry = ZipEntry("$safeName.gpx")
|
||||
zos.putNextEntry(gpxEntry)
|
||||
zos.write(gpxExporter.generateGpxContent(track).toByteArray(Charsets.UTF_8))
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
|
||||
val gpxName = "$safeName.gpx"
|
||||
folder.findFile(gpxName)?.delete()
|
||||
folder.createFile("application/gpx+xml", gpxName)?.let { doc ->
|
||||
contentResolver.openOutputStream(doc.uri)?.use { os ->
|
||||
os.write(gpxExporter.generateGpxContent(track).toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@ActivitiesActivity, "$count Track(s) gesichert (JSON + GPX)", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(this@ActivitiesActivity, "${tracks.size} Tracks in Backup gesichert", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
@@ -146,42 +139,35 @@ class ActivitiesActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun performDriveRestore(folderUri: Uri) {
|
||||
private fun performDriveRestore(fileUri: Uri) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val folder = DocumentFile.fromTreeUri(this@ActivitiesActivity, folderUri)
|
||||
if (folder == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@ActivitiesActivity, "Ordner nicht zugänglich", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
val files = folder.listFiles()
|
||||
val jsonFiles = files.filter { it.name?.endsWith(".json") == true }
|
||||
val gpxFiles = files.filter { it.name?.endsWith(".gpx") == true }
|
||||
val jsonNames = jsonFiles.map { it.name!!.removeSuffix(".json") }.toSet()
|
||||
|
||||
var restored = 0
|
||||
var skipped = 0
|
||||
|
||||
jsonFiles.forEach { file ->
|
||||
contentResolver.openInputStream(file.uri)?.use { inputStream ->
|
||||
val content = inputStream.readBytes().toString(Charsets.UTF_8)
|
||||
val track = runCatching { gson.fromJson(content, Track::class.java) }.getOrNull()
|
||||
if (track != null) {
|
||||
if (trackStorage.getTrack(track.name) == null) { trackStorage.saveTrack(track); restored++ } else skipped++
|
||||
contentResolver.openInputStream(fileUri)?.use { inputStream ->
|
||||
ZipInputStream(inputStream).use { zis ->
|
||||
var entry = zis.nextEntry
|
||||
while (entry != null) {
|
||||
if (!entry.isDirectory && entry.name.endsWith(".json")) {
|
||||
val content = zis.readBytes().toString(Charsets.UTF_8)
|
||||
val track = runCatching { gson.fromJson(content, Track::class.java) }.getOrNull()
|
||||
|
||||
if (track != null) {
|
||||
if (trackStorage.getTrack(track.name) == null) {
|
||||
trackStorage.saveTrack(track)
|
||||
restored++
|
||||
} else {
|
||||
skipped++
|
||||
}
|
||||
}
|
||||
}
|
||||
zis.closeEntry()
|
||||
entry = zis.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gpxFiles.filter { it.name?.removeSuffix(".gpx") !in jsonNames }.forEach { file ->
|
||||
val track = gpxImporter.importFromUri(file.uri)
|
||||
if (track != null) {
|
||||
if (trackStorage.getTrack(track.name) == null) { trackStorage.saveTrack(track); restored++ } else skipped++
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val msg = buildString {
|
||||
append("$restored Track(s) wiederhergestellt")
|
||||
@@ -206,7 +192,6 @@ class ActivitiesActivity : AppCompatActivity() {
|
||||
|
||||
private fun loadAndDisplayTracks() {
|
||||
val tracks = trackStorage.getAllTracks()
|
||||
|
||||
binding.tracksContainer.removeAllViews()
|
||||
|
||||
if (tracks.isEmpty()) {
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package de.tstratmann.gpstracker
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -18,6 +22,7 @@ class TrackDetailActivity : AppCompatActivity(), OnMapReadyCallback {
|
||||
private lateinit var binding: ActivityTrackDetailBinding
|
||||
private lateinit var googleMap: GoogleMap
|
||||
private lateinit var track: Track
|
||||
private val waypointMarkers = mutableListOf<Marker>()
|
||||
|
||||
companion object {
|
||||
const val EXTRA_TRACK_NAME = "track_name"
|
||||
@@ -86,6 +91,93 @@ class TrackDetailActivity : AppCompatActivity(), OnMapReadyCallback {
|
||||
drawTrackWithSpeedGradient()
|
||||
addStartEndMarkers()
|
||||
fitMapToTrack()
|
||||
|
||||
googleMap.setOnCameraIdleListener {
|
||||
displayWaypoints(googleMap.cameraPosition.zoom)
|
||||
}
|
||||
|
||||
googleMap.setOnMarkerClickListener { marker ->
|
||||
val point = marker.tag as? TrackPoint ?: return@setOnMarkerClickListener false
|
||||
marker.title = formatWaypointSpeed(point.speed)
|
||||
marker.snippet = "Kurs: ${point.bearing.toInt()}°"
|
||||
marker.showInfoWindow()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWaypointStride(zoom: Float): Int = when {
|
||||
zoom < 12f -> Int.MAX_VALUE
|
||||
zoom < 13f -> 100
|
||||
zoom < 14f -> 50
|
||||
zoom < 15f -> 20
|
||||
zoom < 16f -> 10
|
||||
zoom < 17f -> 5
|
||||
zoom < 18f -> 3
|
||||
else -> 1
|
||||
}
|
||||
|
||||
private fun createArrowBitmap(bearing: Float): BitmapDescriptor {
|
||||
val size = 48
|
||||
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
val cx = size / 2f
|
||||
val cy = size / 2f
|
||||
|
||||
val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.argb(220, 30, 100, 200)
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 3f
|
||||
}
|
||||
|
||||
val path = Path().apply {
|
||||
moveTo(cx, cy - size * 0.38f)
|
||||
lineTo(cx + size * 0.22f, cy + size * 0.30f)
|
||||
lineTo(cx, cy + size * 0.12f)
|
||||
lineTo(cx - size * 0.22f, cy + size * 0.30f)
|
||||
close()
|
||||
}
|
||||
|
||||
canvas.rotate(bearing, cx, cy)
|
||||
canvas.drawPath(path, fillPaint)
|
||||
canvas.drawPath(path, strokePaint)
|
||||
|
||||
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
}
|
||||
|
||||
private fun displayWaypoints(zoom: Float) {
|
||||
waypointMarkers.forEach { it.remove() }
|
||||
waypointMarkers.clear()
|
||||
|
||||
val stride = getWaypointStride(zoom)
|
||||
if (stride == Int.MAX_VALUE) return
|
||||
|
||||
track.points.forEachIndexed { index, point ->
|
||||
if (index % stride == 0) {
|
||||
val marker = googleMap.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(point.latitude, point.longitude))
|
||||
.icon(createArrowBitmap(point.bearing))
|
||||
.anchor(0.5f, 0.5f)
|
||||
.flat(true)
|
||||
) ?: return@forEachIndexed
|
||||
marker.tag = point
|
||||
waypointMarkers.add(marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatWaypointSpeed(speedMs: Float): String {
|
||||
val useKnots = track.activityType?.useKnots == true
|
||||
return if (useKnots) {
|
||||
val knots = speedMs * 1.94384f
|
||||
"%.1f kn".format(knots)
|
||||
} else {
|
||||
"${(speedMs * 3.6f).toInt()} km/h"
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawTrackWithSpeedGradient() {
|
||||
|
||||
@@ -19,21 +19,30 @@ class GPXManager(private val context: Context) {
|
||||
return file
|
||||
}
|
||||
|
||||
fun finalizeSession() {
|
||||
currentFile?.appendText(getGpxFooter())
|
||||
currentFile = null
|
||||
}
|
||||
|
||||
fun appendLocation(location: Location) {
|
||||
currentFile?.let { file ->
|
||||
val time = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.format(Date(location.time))
|
||||
|
||||
val trkpt = """
|
||||
<trkpt lat="${location.latitude}" lon="${location.longitude}">
|
||||
<ele>${location.altitude}</ele>
|
||||
<time>$time</time>
|
||||
</trkpt>
|
||||
""".trimIndent()
|
||||
val sb = StringBuilder()
|
||||
sb.append("<trkpt lat=\"${location.latitude}\" lon=\"${location.longitude}\">")
|
||||
sb.append("<ele>${location.altitude}</ele>")
|
||||
sb.append("<time>$time</time>")
|
||||
if (location.speed > 0 || location.bearing > 0) {
|
||||
sb.append("<extensions>")
|
||||
if (location.speed > 0) sb.append("<speed>${location.speed}</speed>")
|
||||
if (location.bearing > 0) sb.append("<course>${location.bearing}</course>")
|
||||
sb.append("</extensions>")
|
||||
}
|
||||
sb.append("</trkpt>")
|
||||
|
||||
val content = file.readText().removeSuffix(getGpxFooter())
|
||||
file.writeText(content + trkpt + getGpxFooter())
|
||||
file.appendText(sb.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +54,12 @@ class GPXManager(private val context: Context) {
|
||||
return context.filesDir.listFiles { _, name -> name.endsWith(".gpx") }?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
fun deleteAllFiles(): Int {
|
||||
val files = getAllFiles()
|
||||
files.forEach { it.delete() }
|
||||
return files.size
|
||||
}
|
||||
|
||||
private fun getGpxHeader() = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="WearGPSTracker" xmlns="http://www.topografix.com/GPX/1/1">
|
||||
|
||||
@@ -25,11 +25,14 @@ class TrackingService : Service() {
|
||||
private var startTime = 0L
|
||||
private var lastLocation: Location? = null
|
||||
private var job: Job? = null
|
||||
private var speedSumKmh = 0.0
|
||||
private var speedCount = 0
|
||||
|
||||
data class TrackingData(
|
||||
val durationSeconds: Long = 0,
|
||||
val distanceKm: Double = 0.0,
|
||||
val speedKmh: Double = 0.0
|
||||
val speedKmh: Double = 0.0,
|
||||
val avgSpeedKmh: Double = 0.0
|
||||
)
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
@@ -65,9 +68,16 @@ class TrackingService : Service() {
|
||||
lastLocation = location
|
||||
val speedKmh = location.speed * 3.6
|
||||
|
||||
if (speedKmh > 0) {
|
||||
speedSumKmh += speedKmh
|
||||
speedCount++
|
||||
}
|
||||
val avgSpeedKmh = if (speedCount > 0) speedSumKmh / speedCount else 0.0
|
||||
|
||||
_trackingState.value = currentData.copy(
|
||||
distanceKm = newDistance,
|
||||
speedKmh = speedKmh
|
||||
speedKmh = speedKmh,
|
||||
avgSpeedKmh = avgSpeedKmh
|
||||
)
|
||||
}
|
||||
|
||||
@@ -86,10 +96,13 @@ class TrackingService : Service() {
|
||||
gpxManager.startNewSession()
|
||||
startTime = System.currentTimeMillis()
|
||||
lastLocation = null
|
||||
speedSumKmh = 0.0
|
||||
speedCount = 0
|
||||
_trackingState.value = TrackingData()
|
||||
|
||||
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000)
|
||||
.setMinUpdateIntervalMillis(1000)
|
||||
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
|
||||
.setMinUpdateIntervalMillis(5000)
|
||||
.setMinUpdateDistanceMeters(10f)
|
||||
.build()
|
||||
|
||||
try {
|
||||
@@ -110,6 +123,7 @@ class TrackingService : Service() {
|
||||
fun stopTracking() {
|
||||
fusedLocationClient.removeLocationUpdates(locationCallback)
|
||||
job?.cancel()
|
||||
gpxManager.finalizeSession()
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -154,16 +156,18 @@ fun TrackingScreen(navController: NavHostController) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
val hours = trackingData.durationSeconds / 3600
|
||||
val minutes = (trackingData.durationSeconds % 3600) / 60
|
||||
val seconds = trackingData.durationSeconds % 60
|
||||
val timeString = String.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
|
||||
Text(text = timeString, style = MaterialTheme.typography.display1)
|
||||
Text(text = timeString, style = MaterialTheme.typography.title1)
|
||||
Text(text = String.format("%.2f km", trackingData.distanceKm), style = MaterialTheme.typography.body1)
|
||||
Text(text = String.format("%.1f km/h", trackingData.speedKmh), style = MaterialTheme.typography.body1)
|
||||
Text(text = String.format("Ø %.1f km/h", trackingData.avgSpeedKmh), style = MaterialTheme.typography.body2)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
@@ -200,11 +204,47 @@ fun ActivitiesScreen(navController: NavHostController) {
|
||||
val gpxManager = remember { GPXManager(context) }
|
||||
val syncManager = remember { WearSyncManager(context) }
|
||||
var count by remember { mutableIntStateOf(gpxManager.getActivitiesCount()) }
|
||||
var showDeleteConfirm by remember { mutableStateOf(false) }
|
||||
|
||||
if (showDeleteConfirm) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(text = "Alle löschen?", style = MaterialTheme.typography.title3)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
val deleted = gpxManager.deleteAllFiles()
|
||||
count = 0
|
||||
showDeleteConfirm = false
|
||||
Toast.makeText(context, "$deleted Aktivitäten gelöscht", Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red),
|
||||
modifier = Modifier.fillMaxWidth(0.8f)
|
||||
) {
|
||||
Text("Ja, löschen", color = Color.White)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { showDeleteConfirm = false },
|
||||
modifier = Modifier.fillMaxWidth(0.8f),
|
||||
colors = ButtonDefaults.secondaryButtonColors()
|
||||
) {
|
||||
Text("Abbrechen")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.verticalScroll(scrollState).padding(vertical = 16.dp)
|
||||
) {
|
||||
Text(text = "$count Aktivitäten", style = MaterialTheme.typography.title3)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
@@ -230,6 +270,14 @@ fun ActivitiesScreen(navController: NavHostController) {
|
||||
Text("GPX Sync")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { showDeleteConfirm = true },
|
||||
modifier = Modifier.fillMaxWidth(0.8f),
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
|
||||
) {
|
||||
Text("Alle löschen", color = Color.White)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { navController.popBackStack() },
|
||||
modifier = Modifier.fillMaxWidth(0.8f),
|
||||
|
||||
Reference in New Issue
Block a user