diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/core/model/domain/location/Address.kt b/app/src/main/java/com/pranshulgg/weather_master_app/core/model/domain/location/Address.kt new file mode 100644 index 000000000..0e489d846 --- /dev/null +++ b/app/src/main/java/com/pranshulgg/weather_master_app/core/model/domain/location/Address.kt @@ -0,0 +1,6 @@ +package com.pranshulgg.weather_master_app.core.model.domain.location + +data class Address( + val city: String?, + val country: String +) \ No newline at end of file diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/data/local/dao/location/LocationsDao.kt b/app/src/main/java/com/pranshulgg/weather_master_app/data/local/dao/location/LocationsDao.kt index c7986a7ac..2e1aab330 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/data/local/dao/location/LocationsDao.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/data/local/dao/location/LocationsDao.kt @@ -36,8 +36,8 @@ interface LocationsDao { @Query("SELECT * FROM weather_locations WHERE isDefault = 1 LIMIT 1") fun getDefaultLocation(): Flow - @Query("UPDATE weather_locations SET lat = :lat, lon = :lon WHERE isDeviceLocation = 1") - suspend fun updateDeviceLocationPosition(lat: Double, lon: Double) + @Query("UPDATE weather_locations SET lat = :lat, lon = :lon, name = :name, country = :country WHERE isDeviceLocation = 1") + suspend fun updateDeviceLocation(lat: Double, lon: Double, name: String, country: String) @Transaction @Query("SELECT * FROM weather_locations WHERE id = :locationId LIMIT 1") @@ -62,4 +62,7 @@ interface LocationsDao { @Query("SELECT * FROM weather_locations ORDER BY isDefault DESC") suspend fun getLocationsOnce(): List + + @Query("SELECT * FROM weather_locations WHERE isDeviceLocation = 1 LIMIT 1") + suspend fun getDeviceLocation(): WeatherLocationEntity } \ No newline at end of file diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/data/provider/devicelocation/DeviceLocationProvider.kt b/app/src/main/java/com/pranshulgg/weather_master_app/data/provider/devicelocation/DeviceLocationProvider.kt index cc9a4fb4c..caeffa308 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/data/provider/devicelocation/DeviceLocationProvider.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/data/provider/devicelocation/DeviceLocationProvider.kt @@ -32,13 +32,9 @@ class GetDeviceLocation { private var timeoutHandler: Handler? = null private var timeoutRunnable: Runnable? = null - private val timeoutSecs = 10_000L // 10 seconds - + private val timeoutMillis = 20_000L private fun getProvider(lm: LocationManager): String { return when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && - lm.allProviders.contains(LocationManager.FUSED_PROVIDER) -> LocationManager.FUSED_PROVIDER - lm.allProviders.contains(LocationManager.NETWORK_PROVIDER) -> LocationManager.NETWORK_PROVIDER lm.allProviders.contains(LocationManager.GPS_PROVIDER) -> LocationManager.GPS_PROVIDER else -> LocationManager.PASSIVE_PROVIDER @@ -95,6 +91,9 @@ class GetDeviceLocation { ) { val lastKnown = lm.getLastKnownLocation(provider) + ?: lm.getLastKnownLocation(LocationManager.GPS_PROVIDER) + ?: lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER) + ?: lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER) if (lastKnown != null) { timeoutHandler?.removeCallbacks(timeoutRunnable!!) @@ -127,9 +126,10 @@ class GetDeviceLocation { lm.requestLocationUpdates( provider, - 5000L, - 5f, - locationListener!! + 0L, + 0f, + locationListener!!, + Looper.getMainLooper() ) timeoutHandler = Handler(Looper.getMainLooper()) @@ -139,7 +139,7 @@ class GetDeviceLocation { onTimeout() } - timeoutHandler?.postDelayed(timeoutRunnable!!, timeoutSecs) + timeoutHandler?.postDelayed(timeoutRunnable!!, timeoutMillis) } diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/data/repository/LocationsRepository.kt b/app/src/main/java/com/pranshulgg/weather_master_app/data/repository/LocationsRepository.kt index 47b043156..2a9ca2be4 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/data/repository/LocationsRepository.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/data/repository/LocationsRepository.kt @@ -1,11 +1,13 @@ package com.pranshulgg.weather_master_app.data.repository import android.content.Context +import android.util.Log import androidx.room.Transaction import com.pranshulgg.weather_master_app.core.model.domain.AppException import com.pranshulgg.weather_master_app.core.model.domain.location.Location import com.pranshulgg.weather_master_app.core.model.domain.weather.Weather import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource +import com.pranshulgg.weather_master_app.core.network.sources.address.nominatim.json.NominatimRepository import com.pranshulgg.weather_master_app.data.local.dao.airquality.AirQualityDao import com.pranshulgg.weather_master_app.data.local.dao.location.LocationsDao import com.pranshulgg.weather_master_app.data.local.mapper.locations.toDomain @@ -17,16 +19,20 @@ import com.pranshulgg.weather_master_app.feature.intro.toDomain import dagger.hilt.android.qualifiers.ApplicationContext import jakarta.inject.Inject import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext import kotlin.coroutines.resumeWithException class LocationsRepository @Inject constructor( private val dao: LocationsDao, private val airQualityDao: AirQualityDao, - @param:ApplicationContext private val context: Context + @param:ApplicationContext private val context: Context, + private val nominatimRepository: NominatimRepository ) { + private val LOCATION_UPDATE_THRESHOLD_METERS = 1000f // 1000m class Callbacks { fun onSuccess(cont: CancellableContinuation, result: DeviceLocation) { @@ -93,10 +99,12 @@ class LocationsRepository @Inject constructor( return dao.getWeatherForLocation(locationId).toDomain() } + val getDeviceLocation = GetDeviceLocation() + suspend fun updateDeviceLocationPosition() { val location = suspendCancellableCoroutine { cont -> - GetDeviceLocation().getDeviceLocation( + getDeviceLocation.getDeviceLocation( context, onTimeout = { callback.onTimeout(cont) @@ -105,20 +113,43 @@ class LocationsRepository @Inject constructor( } } - if (location.latitude == null || location.longitude == null) { + + val newLat = location.latitude ?: return + val newLon = location.longitude ?: return + + val currentLocation = dao.getDeviceLocation() + + + val results = FloatArray(1) + + android.location.Location.distanceBetween( + currentLocation.lat, + currentLocation.lon, + newLat, + newLon, + results + ) + + val distanceInMeters = results[0] + // Only update the location if needed + if (distanceInMeters < LOCATION_UPDATE_THRESHOLD_METERS) { return } + val address = nominatimRepository.getAddress(location.latitude, location.longitude) - val formattedLatitude = kotlin.math.round(location.latitude * 100000) / 100000 - val formattedLongitude = kotlin.math.round(location.longitude * 100000) / 100000 - - dao.updateDeviceLocationPosition(formattedLatitude, formattedLongitude) + dao.updateDeviceLocation( + newLat, + newLon, + address?.city ?: "$newLat, $newLon", + address?.country ?: "" + ) } suspend fun saveDeviceLocation() { + val location = suspendCancellableCoroutine { cont -> - GetDeviceLocation().getDeviceLocation( + getDeviceLocation.getDeviceLocation( context, onTimeout = { callback.onTimeout(cont) @@ -129,7 +160,22 @@ class LocationsRepository @Inject constructor( if (location.latitude == null || location.longitude == null) { throw AppException.CurrentLocationUnavailable() } - saveLocation(location.toDomain(context)) + + + val address = nominatimRepository.getAddress(location.latitude, location.longitude) + + if (address != null && address.city != null) { + saveLocation( + location.toDomain(context).copy( + name = address.city, + country = address.country + ) + ) + } else { + saveLocation( + location.toDomain(context) + ) + } } fun getWeatherForAllLocations(): Flow> { diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreen.kt b/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreen.kt index d15ea073c..62151f98a 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreen.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavController import com.pranshulgg.weather_master_app.R +import com.pranshulgg.weather_master_app.core.model.domain.location.Address import com.pranshulgg.weather_master_app.core.model.sources.WeatherSource import com.pranshulgg.weather_master_app.core.model.domain.location.Location import com.pranshulgg.weather_master_app.core.ui.components.Gap @@ -267,7 +268,7 @@ suspend fun DeviceLocation.toDomain(context: Context): Location { return Location( id = UuidGenerator.generateId(), - name = "$formattedLatitude, $formattedLongitude", // TODO: Reverse geocoding support + name = "$formattedLatitude, $formattedLongitude", latitude = formattedLatitude, longitude = formattedLongitude, country = "", diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreenViewModel.kt b/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreenViewModel.kt index 30162a899..e5070fba8 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreenViewModel.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/feature/intro/IntroScreenViewModel.kt @@ -1,9 +1,11 @@ package com.pranshulgg.weather_master_app.feature.intro import android.content.Context +import android.widget.Toast import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pranshulgg.weather_master_app.core.model.domain.location.Location +import com.pranshulgg.weather_master_app.core.network.sources.address.nominatim.json.NominatimRepository import com.pranshulgg.weather_master_app.data.provider.devicelocation.DeviceLocation import com.pranshulgg.weather_master_app.data.repository.LocationsRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -15,13 +17,28 @@ import javax.inject.Inject class IntroScreenViewModel @Inject constructor( val locationsRepo: LocationsRepository, - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + private val nominatimRepository: NominatimRepository ) : ViewModel() { fun saveDeviceLocation(location: DeviceLocation) { viewModelScope.launch { - locationsRepo.saveLocation(location.toDomain(context)) + + val address = nominatimRepository.getAddress(location.latitude, location.longitude) + + if (address != null && address.city != null) { + locationsRepo.saveLocation( + location.toDomain(context).copy( + name = address.city, + country = address.country + ) + ) + } else { + locationsRepo.saveLocation(location.toDomain(context)) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreen.kt b/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreen.kt index 82c5907be..12b540727 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreen.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreen.kt @@ -61,8 +61,7 @@ fun LocationsScreen( navController: NavController, locations: List, activeLocation: Location?, - onLocationSelect: (Location) -> Unit, - isTabletLike: Boolean = false + onLocationSelect: (Location) -> Unit ) { val viewModel: LocationsScreenViewModel = hiltViewModel() @@ -102,10 +101,9 @@ fun LocationsScreen( Scaffold( - modifier = if (isTabletLike) Modifier.width(360.dp) else Modifier, containerColor = MaterialTheme.colorScheme.surfaceContainer, topBar = { - TopBar(onBack, isTabletLike) + TopBar(onBack) }, floatingActionButton = { FloatingButton(navController) @@ -156,7 +154,7 @@ fun LocationsScreen( } @Composable -private fun TopBar(onBack: () -> Unit, isTabletLike: Boolean) { +private fun TopBar(onBack: () -> Unit) { TopAppBar( colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surfaceContainer), title = { @@ -167,21 +165,19 @@ private fun TopBar(onBack: () -> Unit, isTabletLike: Boolean) { ) }, navigationIcon = { - if (!isTabletLike) { - Tooltip( - "Navigate up", - preferredPosition = TooltipAnchorPosition.Below, - spacing = 10.dp + Tooltip( + "Navigate up", + preferredPosition = TooltipAnchorPosition.Below, + spacing = 10.dp + ) { + IconButton( + onClick = { onBack() }, shapes = IconButtonDefaults.shapes() ) { - IconButton( - onClick = { onBack() }, shapes = IconButtonDefaults.shapes() - ) { - Symbol( - R.drawable.arrow_back_24px, - desc = "arrow back icon", - color = MaterialTheme.colorScheme.onSurface - ) - } + Symbol( + R.drawable.arrow_back_24px, + desc = "arrow back icon", + color = MaterialTheme.colorScheme.onSurface + ) } } } diff --git a/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreenViewModel.kt b/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreenViewModel.kt index ecc64bef7..54f2969ca 100644 --- a/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreenViewModel.kt +++ b/app/src/main/java/com/pranshulgg/weather_master_app/feature/locations/LocationsScreenViewModel.kt @@ -1,9 +1,11 @@ package com.pranshulgg.weather_master_app.feature.locations +import android.util.Log import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import coil.util.Logger import com.pranshulgg.weather_master_app.core.model.domain.AppException import com.pranshulgg.weather_master_app.core.model.domain.location.Location import com.pranshulgg.weather_master_app.core.model.domain.toMessageRes @@ -63,6 +65,7 @@ class LocationsScreenViewModel @Inject constructor( _uiState.value = _uiState.value.copy(isDeviceLocationLoading = true) viewModelScope.launch { try { + Log.d("COOL HERE", "CALLED HERE HERE") locationsRepo.saveDeviceLocation() } catch (e: Exception) { if (e is CancellationException) throw e