Initial release of GPS2Audio

This commit is contained in:
Marcel Mayer
2026-05-05 20:54:05 +02:00
commit b2aa1c167c
48 changed files with 11570 additions and 0 deletions
@@ -0,0 +1,172 @@
package de.waypointaudio
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.*
import androidx.core.content.ContextCompat
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import de.waypointaudio.ui.MapScreen
import de.waypointaudio.ui.PermissionRequiredScreen
import de.waypointaudio.ui.WaypointListScreen
import de.waypointaudio.ui.theme.WaypointAudioTheme
import de.waypointaudio.viewmodel.WaypointViewModel
/**
* Hauptaktivität der App.
*
* Navigation:
* "list" → WaypointListScreen (Startbildschirm)
* "map" → MapScreen (osmdroid Kartenansicht)
*
* Berechtigungslogik:
* - Nur ACCESS_FINE_LOCATION und ACCESS_COARSE_LOCATION sind zwingend erforderlich
* (für Standort-Button und Karte). Ohne diese wird der PermissionScreen gezeigt.
* - POST_NOTIFICATIONS und Audiozugriff werden separat angefragt, blockieren
* aber nicht die Hauptoberfläche.
*/
class MainActivity : ComponentActivity() {
private val viewModel: WaypointViewModel by viewModels()
/** Nur Standortberechtigungen steuern, ob die App-UI angezeigt wird. */
private val locationPermissionsGranted = mutableStateOf(false)
// Launcher für Standortberechtigungen (zwingend erforderlich)
private val locationPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { results ->
// Mindestens ACCESS_COARSE_LOCATION muss gewährt sein
val coarseGranted = results[Manifest.permission.ACCESS_COARSE_LOCATION] == true
val fineGranted = results[Manifest.permission.ACCESS_FINE_LOCATION] == true
locationPermissionsGranted.value = coarseGranted || fineGranted
// Nach Gewährung von Standort: optionale Berechtigungen separat anfragen
if (locationPermissionsGranted.value) {
requestOptionalPermissions()
}
}
// Launcher für optionale Berechtigungen (Benachrichtigungen, Audio)
private val optionalPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { /* optional kein Block der Hauptoberfläche */ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Standortberechtigungen prüfen und ggf. anfordern
checkAndRequestLocationPermissions()
setContent {
WaypointAudioTheme(darkTheme = isSystemInDarkTheme()) {
val locationGranted by locationPermissionsGranted
if (locationGranted) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "list"
) {
composable("list") {
WaypointListScreen(
viewModel = viewModel,
onNavigateToMap = { navController.navigate("map") }
)
}
composable("map") {
MapScreen(
viewModel = viewModel,
onNavigateBack = { navController.popBackStack() }
)
}
}
} else {
PermissionRequiredScreen(
onPermissionsGranted = { checkAndRequestLocationPermissions() }
)
}
}
}
}
override fun onResume() {
super.onResume()
// Beim Zurückkehren aus den Einstellungen erneut prüfen
locationPermissionsGranted.value = hasLocationPermission()
if (locationPermissionsGranted.value) {
requestOptionalPermissions()
}
}
// ---------------------------------------------------------------------------
// Berechtigungslogik
// ---------------------------------------------------------------------------
private fun checkAndRequestLocationPermissions() {
if (hasLocationPermission()) {
locationPermissionsGranted.value = true
requestOptionalPermissions()
} else {
locationPermissionLauncher.launch(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
)
}
}
/** Prüft ob mindestens grobe oder feine Standortberechtigung vorhanden ist. */
private fun hasLocationPermission(): Boolean {
val fine = ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val coarse = ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
return fine || coarse
}
/**
* Optionale Berechtigungen (Benachrichtigungen, Audiozugriff) anfragen,
* nachdem Standortberechtigung gewährt wurde.
* Diese blockieren NICHT die Haupt-UI.
*/
private fun requestOptionalPermissions() {
val toRequest = buildList {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this@MainActivity, Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
add(Manifest.permission.POST_NOTIFICATIONS)
}
if (ContextCompat.checkSelfPermission(
this@MainActivity, Manifest.permission.READ_MEDIA_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
add(Manifest.permission.READ_MEDIA_AUDIO)
}
} else {
if (ContextCompat.checkSelfPermission(
this@MainActivity, Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}
}
if (toRequest.isNotEmpty()) {
optionalPermissionLauncher.launch(toRequest.toTypedArray())
}
}
}