This commit is contained in:
inhale-dir
2024-12-15 12:17:37 +01:00
parent 333b779c73
commit 8704b19277
7 changed files with 299 additions and 207 deletions
+8
View File
@@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-12-14T13:53:37.083294865Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=37271FDJH00240" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>
+2
View File
@@ -89,4 +89,6 @@ dependencies {
implementation("androidx.compose.material:material-icons-extended:1.6.2") implementation("androidx.compose.material:material-icons-extended:1.6.2")
implementation("androidx.compose.material3:material3:1.2.0") implementation("androidx.compose.material3:material3:1.2.0")
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.compose.foundation:foundation:1.6.2")
implementation("androidx.compose.animation:animation:1.6.2")
} }
@@ -13,6 +13,7 @@ import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority import com.google.android.gms.location.Priority
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume import kotlin.coroutines.resume
import android.util.Log
private const val THEME_MODE = "theme_mode" private const val THEME_MODE = "theme_mode"
private val Context.dataStore by preferencesDataStore(name = "settings") private val Context.dataStore by preferencesDataStore(name = "settings")
@@ -39,14 +40,14 @@ class SettingsDataStore(private val context: Context) {
val mode = preferences[PreferencesKeys.LOCATION_MODE_KEY] ?: "static" val mode = preferences[PreferencesKeys.LOCATION_MODE_KEY] ?: "static"
when (mode) { when (mode) {
"static" -> Pair( "static" -> Pair(
preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008, preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 0.0,
preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954 preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 0.0
) )
"gps" -> Pair( "gps" -> Pair(
preferences[PreferencesKeys.GPS_LAT_KEY] ?: preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008, preferences[PreferencesKeys.GPS_LAT_KEY] ?: 0.0,
preferences[PreferencesKeys.GPS_LNG_KEY] ?: preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954 preferences[PreferencesKeys.GPS_LNG_KEY] ?: 0.0
) )
else -> Pair(52.520008, 13.404954) else -> Pair(0.0, 0.0)
} }
} }
@@ -72,16 +73,25 @@ class SettingsDataStore(private val context: Context) {
} }
suspend fun saveLocation(latitude: Double, longitude: Double, mode: String? = null) { suspend fun saveLocation(latitude: Double, longitude: Double, mode: String? = null) {
Log.d("GPS_DEBUG", """
Saving location:
Latitude: $latitude
Longitude: $longitude
Mode: $mode
""".trimIndent())
context.dataStore.edit { preferences -> context.dataStore.edit { preferences ->
val currentMode = mode ?: preferences[PreferencesKeys.LOCATION_MODE_KEY] ?: "static" val currentMode = mode ?: preferences[PreferencesKeys.LOCATION_MODE_KEY] ?: "static"
when (currentMode) { when (currentMode) {
"static" -> { "static" -> {
preferences[PreferencesKeys.STATIC_LAT_KEY] = latitude preferences[PreferencesKeys.STATIC_LAT_KEY] = latitude
preferences[PreferencesKeys.STATIC_LNG_KEY] = longitude preferences[PreferencesKeys.STATIC_LNG_KEY] = longitude
Log.d("GPS_DEBUG", "Saved static location")
} }
"gps" -> { "gps" -> {
preferences[PreferencesKeys.GPS_LAT_KEY] = latitude preferences[PreferencesKeys.GPS_LAT_KEY] = latitude
preferences[PreferencesKeys.GPS_LNG_KEY] = longitude preferences[PreferencesKeys.GPS_LNG_KEY] = longitude
Log.d("GPS_DEBUG", "Saved GPS location")
} }
} }
} }
@@ -167,15 +177,15 @@ class SettingsDataStore(private val context: Context) {
"gps" -> { "gps" -> {
getCurrentLocation() ?: context.dataStore.data.map { preferences -> getCurrentLocation() ?: context.dataStore.data.map { preferences ->
Pair( Pair(
preferences[PreferencesKeys.GPS_LAT_KEY] ?: preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008, preferences[PreferencesKeys.GPS_LAT_KEY] ?: 0.0,
preferences[PreferencesKeys.GPS_LNG_KEY] ?: preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954 preferences[PreferencesKeys.GPS_LNG_KEY] ?: 0.0
) )
}.first() }.first()
} }
else -> context.dataStore.data.map { preferences -> else -> context.dataStore.data.map { preferences ->
Pair( Pair(
preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008, preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 0.0,
preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954 preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 0.0
) )
}.first() }.first()
} }
@@ -191,4 +201,10 @@ class SettingsDataStore(private val context: Context) {
preferences[PreferencesKeys.THEME_MODE_KEY] = isDark preferences[PreferencesKeys.THEME_MODE_KEY] = isDark
} }
} }
suspend fun saveFavoriteStations(stations: Set<String>) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.FAVORITE_STATIONS] = stations.joinToString(",")
}
}
} }
@@ -20,6 +20,10 @@ import com.example.tank.di.NetworkModule
import com.example.tank.ui.theme.TankTheme import com.example.tank.ui.theme.TankTheme
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.core.tween
@Composable @Composable
fun MainScreen() { fun MainScreen() {
@@ -58,7 +62,7 @@ private fun MainContent() {
val navController = rememberNavController() val navController = rememberNavController()
val context = LocalContext.current val context = LocalContext.current
val settingsDataStore = remember { SettingsDataStore(context) } val settingsDataStore = remember { SettingsDataStore(context) }
var isDarkTheme by remember { mutableStateOf(false) } var isDarkTheme by remember { mutableStateOf(true) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
// Load saved theme preference // Load saved theme preference
@@ -70,15 +74,19 @@ private fun MainContent() {
TankTheme(darkTheme = isDarkTheme) { TankTheme(darkTheme = isDarkTheme) {
Scaffold( Scaffold(
modifier = Modifier.animateContentSize(),
topBar = { topBar = {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = { Text("Fuel Prices") }, title = { Text("Fuel Prices") },
actions = { actions = {
IconButton(onClick = { IconButton(
navController.navigate("settings") { onClick = {
launchSingleTop = true navController.navigate("settings") {
} launchSingleTop = true
}) { }
},
modifier = Modifier.animateContentSize()
) {
Icon(Icons.Default.Settings, contentDescription = "Settings") Icon(Icons.Default.Settings, contentDescription = "Settings")
} }
} }
@@ -88,10 +96,22 @@ private fun MainContent() {
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = "prices", startDestination = "prices",
modifier = Modifier.padding(padding) modifier = Modifier
.padding(padding)
.animateContentSize()
) { ) {
composable("prices") { PricesScreen() } composable(
composable("settings") { "prices",
enterTransition = { fadeIn(animationSpec = tween(300)) },
exitTransition = { fadeOut(animationSpec = tween(300)) }
) {
PricesScreen()
}
composable(
"settings",
enterTransition = { fadeIn(animationSpec = tween(300)) },
exitTransition = { fadeOut(animationSpec = tween(300)) }
) {
SettingsScreen( SettingsScreen(
navController = navController, navController = navController,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
@@ -102,7 +122,13 @@ private fun MainContent() {
} }
) )
} }
composable("map") { MapScreen(navController = navController) } composable(
"map",
enterTransition = { fadeIn(animationSpec = tween(300)) },
exitTransition = { fadeOut(animationSpec = tween(300)) }
) {
MapScreen(navController = navController)
}
} }
} }
} }
@@ -3,6 +3,7 @@ package com.example.tank.ui.screens
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -43,8 +44,15 @@ import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationCallback import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationResult
import android.os.Looper import android.os.Looper
import com.google.android.gms.tasks.CancellationTokenSource
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.animation.animateContentSize
@OptIn(ExperimentalMaterialApi::class) @OptIn(
ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class
)
@Composable @Composable
fun PricesScreen(viewModel: StationsViewModel = viewModel()) { fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
val context = LocalContext.current val context = LocalContext.current
@@ -76,10 +84,8 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
} }
val locationRequest = remember { val locationRequest = remember {
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000) LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
.setWaitForAccurateLocation(true) .setMinUpdateDistanceMeters(10f)
.setMinUpdateIntervalMillis(500)
.setMaxUpdateDelayMillis(1000)
.build() .build()
} }
@@ -87,12 +93,22 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
object : LocationCallback() { object : LocationCallback() {
override fun onLocationResult(result: LocationResult) { override fun onLocationResult(result: LocationResult) {
result.lastLocation?.let { location -> result.lastLocation?.let { location ->
Log.d("PricesScreen", "Location update received: ${location.latitude}, ${location.longitude}") Log.d("GPS_DEBUG", """
Location Update Received:
Latitude: ${location.latitude}
Longitude: ${location.longitude}
Accuracy: ${location.accuracy}m
Speed: ${location.speed}m/s
Time: ${location.time}
Provider: ${location.provider}
Bearing: ${location.bearing}
Altitude: ${location.altitude}
""".trimIndent())
scope.launch { scope.launch {
settingsDataStore.saveLocation(location.latitude, location.longitude) settingsDataStore.saveLocation(location.latitude, location.longitude)
viewModel.loadStations(location.latitude, location.longitude, selectedRadius) viewModel.loadStations(location.latitude, location.longitude, selectedRadius)
} }
} } ?: Log.e("GPS_DEBUG", "Location result was null")
} }
} }
} }
@@ -107,41 +123,50 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
// Add location mode collection // Add location mode collection
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
settingsDataStore.locationMode.collect { mode -> settingsDataStore.locationMode.collect { mode ->
Log.d("PricesScreen", "Location mode changed to: $mode") Log.d("GPS_DEBUG", "Location mode changed to: $mode")
locationMode = mode locationMode = mode
// Reload stations when location mode changes
when { when {
mode == "gps" && mode == "gps" &&
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED -> { PackageManager.PERMISSION_GRANTED -> {
Log.d("PricesScreen", "GPS mode and permission granted") Log.d("GPS_DEBUG", "GPS mode and permission granted")
try { try {
fusedLocationClient.requestLocationUpdates( fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
locationRequest, .addOnSuccessListener { location ->
locationCallback, if (location != null) {
context.mainLooper Log.d("GPS_DEBUG", """
).addOnSuccessListener { Initial GPS Location:
Log.d("PricesScreen", "Location updates requested successfully") Latitude: ${location.latitude}
}.addOnFailureListener { exception -> Longitude: ${location.longitude}
Log.e("PricesScreen", "Failed to request location updates", exception) Accuracy: ${location.accuracy}m
} Provider: ${location.provider}
Is Mock Location: ${location.isFromMockProvider}
Elapsed Time: ${location.elapsedRealtimeNanos}
""".trimIndent())
scope.launch {
settingsDataStore.saveLocation(location.latitude, location.longitude)
viewModel.loadStations(location.latitude, location.longitude, selectedRadius)
}
} else {
Log.e("GPS_DEBUG", "Initial location request returned null")
}
}
.addOnFailureListener { e ->
Log.e("GPS_DEBUG", "Failed to get location: ${e.message}", e)
}
} catch (e: SecurityException) { } catch (e: SecurityException) {
Log.e("PricesScreen", "Security exception when getting location", e) Log.e("GPS_DEBUG", "Security exception when getting location", e)
// Fallback to saved location
scope.launch {
val savedLocation = settingsDataStore.selectedLocation.first()
viewModel.loadStations(savedLocation.first, savedLocation.second, selectedRadius)
}
} }
} }
mode == "gps" -> {
Log.e("GPS_DEBUG", """
GPS mode but no permission:
FINE_LOCATION permission: ${ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)}
COARSE_LOCATION permission: ${ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)}
""".trimIndent())
}
else -> { else -> {
Log.d("PricesScreen", "Using static mode or GPS permission denied") Log.d("GPS_DEBUG", "Using static mode")
// Use saved location for static mode or when GPS is not available
scope.launch {
val savedLocation = settingsDataStore.selectedLocation.first()
Log.d("PricesScreen", "Using saved location: ${savedLocation.first}, ${savedLocation.second}")
viewModel.loadStations(savedLocation.first, savedLocation.second, selectedRadius)
}
} }
} }
} }
@@ -259,108 +284,102 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
) { ) {
when { when {
error != null -> { error != null -> {
Column( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .pullRefresh(pullRefreshState)
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Column(
text = error ?: "An error occurred", modifier = Modifier
style = MaterialTheme.typography.bodyLarge, .fillMaxSize()
color = MaterialTheme.colorScheme.error .padding(16.dp),
) verticalArrangement = Arrangement.Center,
Text( horizontalAlignment = Alignment.CenterHorizontally
text = "Pull down to refresh", ) {
style = MaterialTheme.typography.bodyMedium, Text(
color = MaterialTheme.colorScheme.onSurfaceVariant, text = error ?: "An error occurred",
modifier = Modifier.padding(top = 8.dp) style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.error
)
Text(
text = "Pull down to refresh",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
}
PullRefreshIndicator(
refreshing = isLoading,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
) )
} }
} }
stations.isEmpty() && !isLoading -> { stations.isEmpty() && !isLoading -> {
Column( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .pullRefresh(pullRefreshState)
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Column(
text = "No stations found", modifier = Modifier
style = MaterialTheme.typography.bodyLarge .fillMaxSize()
) .padding(16.dp),
Text( verticalArrangement = Arrangement.Center,
text = "Pull down to refresh", horizontalAlignment = Alignment.CenterHorizontally
style = MaterialTheme.typography.bodyMedium, ) {
color = MaterialTheme.colorScheme.onSurfaceVariant, Text(
modifier = Modifier.padding(top = 8.dp) text = "No stations found",
style = MaterialTheme.typography.bodyLarge
)
Text(
text = "Pull down to refresh",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 8.dp)
)
}
PullRefreshIndicator(
refreshing = isLoading,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
) )
} }
} }
else -> { else -> {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.pullRefresh(pullRefreshState),
state = rememberLazyListState(),
contentPadding = PaddingValues(16.dp), contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
// Favorites section items(
if (favoriteStations.isNotEmpty()) { items = stations,
item { key = { station -> station.id }
Text( ) { station ->
text = "Favorites",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 8.dp)
)
}
items(stations.filter { station ->
favoriteStations.contains(station.id)
}) { station ->
FavoriteStationCard(
station = station,
selectedFuelType = selectedFuelType,
onFavoriteClick = { stationId ->
scope.launch {
settingsDataStore.toggleFavorite(stationId)
}
}
)
}
item {
Divider(
modifier = Modifier.padding(vertical = 16.dp),
color = MaterialTheme.colorScheme.outlineVariant
)
Text(
text = "All Stations",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 8.dp)
)
}
}
// Regular stations
items(stations.filter { station ->
!favoriteStations.contains(station.id) &&
when (selectedFuelType.lowercase()) {
"e5" -> station.e5 != null
"e10" -> station.e10 != null
"diesel" -> station.diesel != null
else -> true
}
}) { station ->
StationCard( StationCard(
station = station, station = station,
selectedFuelType = selectedFuelType, selectedFuelType = selectedFuelType,
isFavorite = favoriteStations.contains(station.id), isFavorite = favoriteStations.contains(station.id),
onFavoriteClick = { stationId -> onFavoriteClick = { stationId ->
scope.launch { scope.launch {
settingsDataStore.toggleFavorite(stationId) if (favoriteStations.contains(stationId)) {
settingsDataStore.saveFavoriteStations(
favoriteStations.filter { it != stationId }.toSet()
)
} else {
settingsDataStore.saveFavoriteStations(
favoriteStations + stationId
)
}
} }
} },
modifier = Modifier
.animateContentSize()
) )
} }
} }
@@ -381,7 +400,8 @@ private fun StationCard(
station: Station, station: Station,
selectedFuelType: String, selectedFuelType: String,
isFavorite: Boolean, isFavorite: Boolean,
onFavoriteClick: (String) -> Unit onFavoriteClick: (String) -> Unit,
modifier: Modifier = Modifier
) { ) {
Card( Card(
modifier = Modifier modifier = Modifier
@@ -6,8 +6,11 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LocationOn import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.MyLocation import androidx.compose.material.icons.filled.MyLocation
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -20,13 +23,18 @@ import com.example.tank.di.NetworkModule
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority import com.google.android.gms.location.Priority
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.material.icons.Icons
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.tank.ui.viewmodel.StationsViewModel
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -38,6 +46,8 @@ fun SettingsScreen(
val context = LocalContext.current val context = LocalContext.current
val settingsDataStore = remember { SettingsDataStore(context) } val settingsDataStore = remember { SettingsDataStore(context) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val viewModel: StationsViewModel = viewModel()
val scrollState = rememberScrollState()
var selectedFuelType by remember { mutableStateOf("") } var selectedFuelType by remember { mutableStateOf("") }
var selectedLocation by remember { mutableStateOf<Pair<Double, Double>?>(null) } var selectedLocation by remember { mutableStateOf<Pair<Double, Double>?>(null) }
@@ -45,15 +55,15 @@ fun SettingsScreen(
var apiKey by remember { mutableStateOf("") } var apiKey by remember { mutableStateOf("") }
var locationMode by remember { mutableStateOf("static") } var locationMode by remember { mutableStateOf("static") }
val fuelTypes = listOf( val fuelTypes = remember {
"DIESEL" to "Diesel", listOf(
"E5" to "Super E5", "DIESEL" to "Diesel",
"E10" to "Super E10" "E5" to "Super E5",
) "E10" to "Super E10"
)
}
val radiusOptions = listOf(5, 10, 15, 20) val radiusOptions = remember { listOf(5, 10, 15, 20) }
// Add location services client
val fusedLocationClient = remember { val fusedLocationClient = remember {
LocationServices.getFusedLocationProviderClient(context) LocationServices.getFusedLocationProviderClient(context)
} }
@@ -148,7 +158,7 @@ fun SettingsScreen(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState()) .verticalScroll(scrollState)
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
@@ -160,6 +170,55 @@ fun SettingsScreen(
modifier = Modifier.padding(bottom = 8.dp) modifier = Modifier.padding(bottom = 8.dp)
) )
// Appearance Section (Moved up)
SettingsSection(title = "Appearance") {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Theme",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
FilterChip(
selected = !isDarkTheme,
onClick = { onThemeChanged(false) },
label = { Text("Light") },
leadingIcon = {
Icon(
Icons.Default.LightMode,
contentDescription = "Light Theme"
)
},
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
)
FilterChip(
selected = isDarkTheme,
onClick = { onThemeChanged(true) },
label = { Text("Dark") },
leadingIcon = {
Icon(
Icons.Default.DarkMode,
contentDescription = "Dark Theme"
)
},
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
)
}
}
}
// Location Section // Location Section
SettingsSection(title = "Location") { SettingsSection(title = "Location") {
Column( Column(
@@ -243,59 +302,39 @@ fun SettingsScreen(
) )
} }
// Only show location card for static mode // Show map button for static mode
if (locationMode == "static") { if (locationMode == "static") {
Button(
onClick = {
navController.navigate("map")
},
modifier = Modifier.fillMaxWidth()
) {
Text("Select Location on Map")
}
}
// Current Location Display
if (selectedLocation != null) {
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(48.dp), .padding(vertical = 8.dp),
colors = CardDefaults.cardColors( colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant, containerColor = MaterialTheme.colorScheme.surfaceVariant
) )
) { ) {
Row( Column(
modifier = Modifier modifier = Modifier.padding(16.dp)
.fillMaxSize()
.padding(horizontal = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
Row( Text(
verticalAlignment = Alignment.CenterVertically, text = "Latitude: ${String.format("%.6f", selectedLocation?.first)}",
horizontalArrangement = Arrangement.spacedBy(8.dp) style = MaterialTheme.typography.bodyMedium
) { )
Icon( Text(
imageVector = if (locationMode == "static") text = "Longitude: ${String.format("%.6f", selectedLocation?.second)}",
Icons.Default.LocationOn style = MaterialTheme.typography.bodyMedium
else Icons.Default.MyLocation, )
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp)
)
Text(
text = if (selectedLocation != null) {
String.format(
"%.4f, %.4f",
selectedLocation!!.first,
selectedLocation!!.second
)
} else {
"No location selected"
},
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
TextButton(
onClick = { navController.navigate("map") },
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Text(
"Map",
style = MaterialTheme.typography.bodyMedium
)
}
} }
} }
} }
@@ -359,7 +398,8 @@ fun SettingsScreen(
}, },
label = { Text("${radius}km") }, label = { Text("${radius}km") },
colors = FilterChipDefaults.filterChipColors( colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
containerColor = MaterialTheme.colorScheme.surfaceVariant
) )
) )
} }
@@ -384,26 +424,6 @@ fun SettingsScreen(
) )
} }
// Add Theme Section
SettingsSection(title = "Appearance") {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Dark Theme",
style = MaterialTheme.typography.bodyLarge
)
Switch(
checked = isDarkTheme,
onCheckedChange = { onThemeChanged(it) }
)
}
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// Save Button // Save Button
@@ -445,7 +465,7 @@ private fun SettingsSection(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(12.dp)) .clip(remember { RoundedCornerShape(12.dp) })
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surface)
.padding(16.dp), .padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
@@ -35,7 +35,7 @@ private val LightColorScheme = lightColorScheme(
@Composable @Composable
fun TankTheme( fun TankTheme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = true,
// Dynamic color is available on Android 12+ // Dynamic color is available on Android 12+
dynamicColor: Boolean = true, dynamicColor: Boolean = true,
content: @Composable () -> Unit content: @Composable () -> Unit