changes
This commit is contained in:
parent
333b779c73
commit
8704b19277
@ -4,6 +4,14 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<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>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
@ -89,4 +89,6 @@ dependencies {
|
||||
implementation("androidx.compose.material:material-icons-extended:1.6.2")
|
||||
implementation("androidx.compose.material3:material3:1.2.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 kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
import android.util.Log
|
||||
|
||||
private const val THEME_MODE = "theme_mode"
|
||||
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"
|
||||
when (mode) {
|
||||
"static" -> Pair(
|
||||
preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008,
|
||||
preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954
|
||||
preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 0.0,
|
||||
preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 0.0
|
||||
)
|
||||
"gps" -> Pair(
|
||||
preferences[PreferencesKeys.GPS_LAT_KEY] ?: preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008,
|
||||
preferences[PreferencesKeys.GPS_LNG_KEY] ?: preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954
|
||||
preferences[PreferencesKeys.GPS_LAT_KEY] ?: 0.0,
|
||||
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) {
|
||||
Log.d("GPS_DEBUG", """
|
||||
Saving location:
|
||||
Latitude: $latitude
|
||||
Longitude: $longitude
|
||||
Mode: $mode
|
||||
""".trimIndent())
|
||||
|
||||
context.dataStore.edit { preferences ->
|
||||
val currentMode = mode ?: preferences[PreferencesKeys.LOCATION_MODE_KEY] ?: "static"
|
||||
when (currentMode) {
|
||||
"static" -> {
|
||||
preferences[PreferencesKeys.STATIC_LAT_KEY] = latitude
|
||||
preferences[PreferencesKeys.STATIC_LNG_KEY] = longitude
|
||||
Log.d("GPS_DEBUG", "Saved static location")
|
||||
}
|
||||
"gps" -> {
|
||||
preferences[PreferencesKeys.GPS_LAT_KEY] = latitude
|
||||
preferences[PreferencesKeys.GPS_LNG_KEY] = longitude
|
||||
Log.d("GPS_DEBUG", "Saved GPS location")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,15 +177,15 @@ class SettingsDataStore(private val context: Context) {
|
||||
"gps" -> {
|
||||
getCurrentLocation() ?: context.dataStore.data.map { preferences ->
|
||||
Pair(
|
||||
preferences[PreferencesKeys.GPS_LAT_KEY] ?: preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008,
|
||||
preferences[PreferencesKeys.GPS_LNG_KEY] ?: preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954
|
||||
preferences[PreferencesKeys.GPS_LAT_KEY] ?: 0.0,
|
||||
preferences[PreferencesKeys.GPS_LNG_KEY] ?: 0.0
|
||||
)
|
||||
}.first()
|
||||
}
|
||||
else -> context.dataStore.data.map { preferences ->
|
||||
Pair(
|
||||
preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 52.520008,
|
||||
preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 13.404954
|
||||
preferences[PreferencesKeys.STATIC_LAT_KEY] ?: 0.0,
|
||||
preferences[PreferencesKeys.STATIC_LNG_KEY] ?: 0.0
|
||||
)
|
||||
}.first()
|
||||
}
|
||||
@ -191,4 +201,10 @@ class SettingsDataStore(private val context: Context) {
|
||||
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 kotlinx.coroutines.flow.first
|
||||
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
|
||||
fun MainScreen() {
|
||||
@ -58,7 +62,7 @@ private fun MainContent() {
|
||||
val navController = rememberNavController()
|
||||
val context = LocalContext.current
|
||||
val settingsDataStore = remember { SettingsDataStore(context) }
|
||||
var isDarkTheme by remember { mutableStateOf(false) }
|
||||
var isDarkTheme by remember { mutableStateOf(true) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// Load saved theme preference
|
||||
@ -70,15 +74,19 @@ private fun MainContent() {
|
||||
|
||||
TankTheme(darkTheme = isDarkTheme) {
|
||||
Scaffold(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = { Text("Fuel Prices") },
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
navController.navigate("settings") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
navController.navigate("settings") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.animateContentSize()
|
||||
) {
|
||||
Icon(Icons.Default.Settings, contentDescription = "Settings")
|
||||
}
|
||||
}
|
||||
@ -88,10 +96,22 @@ private fun MainContent() {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "prices",
|
||||
modifier = Modifier.padding(padding)
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.animateContentSize()
|
||||
) {
|
||||
composable("prices") { PricesScreen() }
|
||||
composable("settings") {
|
||||
composable(
|
||||
"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(
|
||||
navController = navController,
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
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.LocationResult
|
||||
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
|
||||
fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
||||
val context = LocalContext.current
|
||||
@ -76,10 +84,8 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
||||
}
|
||||
|
||||
val locationRequest = remember {
|
||||
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000)
|
||||
.setWaitForAccurateLocation(true)
|
||||
.setMinUpdateIntervalMillis(500)
|
||||
.setMaxUpdateDelayMillis(1000)
|
||||
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
|
||||
.setMinUpdateDistanceMeters(10f)
|
||||
.build()
|
||||
}
|
||||
|
||||
@ -87,12 +93,22 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
||||
object : LocationCallback() {
|
||||
override fun onLocationResult(result: LocationResult) {
|
||||
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 {
|
||||
settingsDataStore.saveLocation(location.latitude, location.longitude)
|
||||
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
|
||||
LaunchedEffect(Unit) {
|
||||
settingsDataStore.locationMode.collect { mode ->
|
||||
Log.d("PricesScreen", "Location mode changed to: $mode")
|
||||
Log.d("GPS_DEBUG", "Location mode changed to: $mode")
|
||||
locationMode = mode
|
||||
// Reload stations when location mode changes
|
||||
when {
|
||||
mode == "gps" &&
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED -> {
|
||||
Log.d("PricesScreen", "GPS mode and permission granted")
|
||||
Log.d("GPS_DEBUG", "GPS mode and permission granted")
|
||||
try {
|
||||
fusedLocationClient.requestLocationUpdates(
|
||||
locationRequest,
|
||||
locationCallback,
|
||||
context.mainLooper
|
||||
).addOnSuccessListener {
|
||||
Log.d("PricesScreen", "Location updates requested successfully")
|
||||
}.addOnFailureListener { exception ->
|
||||
Log.e("PricesScreen", "Failed to request location updates", exception)
|
||||
}
|
||||
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
|
||||
.addOnSuccessListener { location ->
|
||||
if (location != null) {
|
||||
Log.d("GPS_DEBUG", """
|
||||
Initial GPS Location:
|
||||
Latitude: ${location.latitude}
|
||||
Longitude: ${location.longitude}
|
||||
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) {
|
||||
Log.e("PricesScreen", "Security exception when getting location", e)
|
||||
// Fallback to saved location
|
||||
scope.launch {
|
||||
val savedLocation = settingsDataStore.selectedLocation.first()
|
||||
viewModel.loadStations(savedLocation.first, savedLocation.second, selectedRadius)
|
||||
}
|
||||
Log.e("GPS_DEBUG", "Security exception when getting location", e)
|
||||
}
|
||||
}
|
||||
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 -> {
|
||||
Log.d("PricesScreen", "Using static mode or GPS permission denied")
|
||||
// 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)
|
||||
}
|
||||
Log.d("GPS_DEBUG", "Using static mode")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,108 +284,102 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
||||
) {
|
||||
when {
|
||||
error != null -> {
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.pullRefresh(pullRefreshState)
|
||||
) {
|
||||
Text(
|
||||
text = error ?: "An error occurred",
|
||||
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)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = error ?: "An error occurred",
|
||||
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 -> {
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.pullRefresh(pullRefreshState)
|
||||
) {
|
||||
Text(
|
||||
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)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
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 -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.pullRefresh(pullRefreshState),
|
||||
state = rememberLazyListState(),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
// Favorites section
|
||||
if (favoriteStations.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
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 ->
|
||||
items(
|
||||
items = stations,
|
||||
key = { station -> station.id }
|
||||
) { station ->
|
||||
StationCard(
|
||||
station = station,
|
||||
selectedFuelType = selectedFuelType,
|
||||
isFavorite = favoriteStations.contains(station.id),
|
||||
onFavoriteClick = { stationId ->
|
||||
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,
|
||||
selectedFuelType: String,
|
||||
isFavorite: Boolean,
|
||||
onFavoriteClick: (String) -> Unit
|
||||
onFavoriteClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
|
||||
@ -6,8 +6,11 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
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.ui.Alignment
|
||||
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.Priority
|
||||
import kotlinx.coroutines.launch
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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)
|
||||
@Composable
|
||||
@ -38,6 +46,8 @@ fun SettingsScreen(
|
||||
val context = LocalContext.current
|
||||
val settingsDataStore = remember { SettingsDataStore(context) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val viewModel: StationsViewModel = viewModel()
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
var selectedFuelType by remember { mutableStateOf("") }
|
||||
var selectedLocation by remember { mutableStateOf<Pair<Double, Double>?>(null) }
|
||||
@ -45,15 +55,15 @@ fun SettingsScreen(
|
||||
var apiKey by remember { mutableStateOf("") }
|
||||
var locationMode by remember { mutableStateOf("static") }
|
||||
|
||||
val fuelTypes = listOf(
|
||||
"DIESEL" to "Diesel",
|
||||
"E5" to "Super E5",
|
||||
"E10" to "Super E10"
|
||||
)
|
||||
val fuelTypes = remember {
|
||||
listOf(
|
||||
"DIESEL" to "Diesel",
|
||||
"E5" to "Super E5",
|
||||
"E10" to "Super E10"
|
||||
)
|
||||
}
|
||||
|
||||
val radiusOptions = listOf(5, 10, 15, 20)
|
||||
|
||||
// Add location services client
|
||||
val radiusOptions = remember { listOf(5, 10, 15, 20) }
|
||||
val fusedLocationClient = remember {
|
||||
LocationServices.getFusedLocationProviderClient(context)
|
||||
}
|
||||
@ -148,7 +158,7 @@ fun SettingsScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.verticalScroll(scrollState)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
@ -160,6 +170,55 @@ fun SettingsScreen(
|
||||
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
|
||||
SettingsSection(title = "Location") {
|
||||
Column(
|
||||
@ -243,59 +302,39 @@ fun SettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// Only show location card for static mode
|
||||
// Show map button for static mode
|
||||
if (locationMode == "static") {
|
||||
Button(
|
||||
onClick = {
|
||||
navController.navigate("map")
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text("Select Location on Map")
|
||||
}
|
||||
}
|
||||
|
||||
// Current Location Display
|
||||
if (selectedLocation != null) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(48.dp),
|
||||
.padding(vertical = 8.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (locationMode == "static")
|
||||
Icons.Default.LocationOn
|
||||
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
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "Latitude: ${String.format("%.6f", selectedLocation?.first)}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "Longitude: ${String.format("%.6f", selectedLocation?.second)}",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -359,7 +398,8 @@ fun SettingsScreen(
|
||||
},
|
||||
label = { Text("${radius}km") },
|
||||
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))
|
||||
|
||||
// Save Button
|
||||
@ -445,7 +465,7 @@ private fun SettingsSection(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clip(remember { RoundedCornerShape(12.dp) })
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
|
||||
@ -35,7 +35,7 @@ private val LightColorScheme = lightColorScheme(
|
||||
|
||||
@Composable
|
||||
fun TankTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
darkTheme: Boolean = true,
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
|
||||
Loading…
Reference in New Issue
Block a user