changes
This commit is contained in:
Generated
+8
@@ -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>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
onClick = {
|
||||||
navController.navigate("settings") {
|
navController.navigate("settings") {
|
||||||
launchSingleTop = true
|
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",
|
||||||
|
enterTransition = { fadeIn(animationSpec = tween(300)) },
|
||||||
|
exitTransition = { fadeOut(animationSpec = tween(300)) }
|
||||||
|
) {
|
||||||
|
PricesScreen()
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
"settings",
|
||||||
|
enterTransition = { fadeIn(animationSpec = tween(300)) },
|
||||||
|
exitTransition = { fadeOut(animationSpec = tween(300)) }
|
||||||
) {
|
) {
|
||||||
composable("prices") { PricesScreen() }
|
|
||||||
composable("settings") {
|
|
||||||
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,6 +284,11 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
|||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
error != null -> {
|
error != null -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(pullRefreshState)
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -278,8 +308,20 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
|||||||
modifier = Modifier.padding(top = 8.dp)
|
modifier = Modifier.padding(top = 8.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
refreshing = isLoading,
|
||||||
|
state = pullRefreshState,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stations.isEmpty() && !isLoading -> {
|
stations.isEmpty() && !isLoading -> {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(pullRefreshState)
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -298,69 +340,46 @@ fun PricesScreen(viewModel: StationsViewModel = viewModel()) {
|
|||||||
modifier = Modifier.padding(top = 8.dp)
|
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 {
|
||||||
|
listOf(
|
||||||
"DIESEL" to "Diesel",
|
"DIESEL" to "Diesel",
|
||||||
"E5" to "Super E5",
|
"E5" to "Super E5",
|
||||||
"E10" to "Super E10"
|
"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(
|
|
||||||
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(
|
Text(
|
||||||
"Map",
|
text = "Latitude: ${String.format("%.6f", selectedLocation?.first)}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Longitude: ${String.format("%.6f", selectedLocation?.second)}",
|
||||||
style = MaterialTheme.typography.bodyMedium
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user