|
1 | 1 | package com.artemzarubin.weatherml.data.location |
2 | 2 |
|
3 | 3 | import android.Manifest |
4 | | -import android.app.Application // Needs Application context |
| 4 | +import android.app.Application |
5 | 5 | import android.content.Context |
6 | 6 | import android.content.pm.PackageManager |
7 | 7 | import android.location.Location |
8 | | -import android.location.LocationManager // To check if GPS is enabled |
| 8 | +import android.location.LocationManager |
9 | 9 | import android.util.Log |
10 | 10 | import androidx.core.content.ContextCompat |
11 | 11 | import com.artemzarubin.weatherml.domain.location.LocationTracker |
| 12 | +import com.artemzarubin.weatherml.util.Resource |
| 13 | +import com.google.android.gms.location.CurrentLocationRequest |
12 | 14 | import com.google.android.gms.location.FusedLocationProviderClient |
13 | 15 | import com.google.android.gms.location.Priority |
14 | 16 | import com.google.android.gms.tasks.CancellationTokenSource |
15 | | -import kotlinx.coroutines.suspendCancellableCoroutine |
| 17 | +import dagger.hilt.android.qualifiers.ApplicationContext |
| 18 | +import kotlinx.coroutines.channels.awaitClose |
| 19 | +import kotlinx.coroutines.flow.Flow |
| 20 | +import kotlinx.coroutines.flow.callbackFlow |
16 | 21 | import javax.inject.Inject |
17 | | -import kotlin.coroutines.resume |
18 | | - |
19 | | -// import android.util.Log // For debugging |
| 22 | +import javax.inject.Singleton |
20 | 23 |
|
| 24 | +@Singleton |
21 | 25 | class LocationTrackerImpl @Inject constructor( |
22 | | - private val application: Application, // Hilt can provide Application context |
23 | | - private val fusedLocationClient: FusedLocationProviderClient // Hilt will provide this |
| 26 | + private val locationClient: FusedLocationProviderClient, |
| 27 | + @ApplicationContext private val application: Application |
24 | 28 | ) : LocationTracker { |
25 | 29 |
|
26 | | - override suspend fun getCurrentLocation(): Location? { |
27 | | - // Check if location permissions are granted |
28 | | - val hasAccessFineLocationPermission = ContextCompat.checkSelfPermission( |
29 | | - application, |
30 | | - Manifest.permission.ACCESS_FINE_LOCATION |
31 | | - ) == PackageManager.PERMISSION_GRANTED |
| 30 | + override fun getCurrentLocation(): Flow<Resource<Location?>> { |
| 31 | + return callbackFlow { |
| 32 | + Log.d("LocationTrackerImpl", "getCurrentLocation Flow started") |
32 | 33 |
|
33 | | - val hasAccessCoarseLocationPermission = ContextCompat.checkSelfPermission( |
34 | | - application, |
35 | | - Manifest.permission.ACCESS_COARSE_LOCATION |
36 | | - ) == PackageManager.PERMISSION_GRANTED |
| 34 | + if (ContextCompat.checkSelfPermission( |
| 35 | + application, |
| 36 | + Manifest.permission.ACCESS_FINE_LOCATION |
| 37 | + ) != PackageManager.PERMISSION_GRANTED && |
| 38 | + ContextCompat.checkSelfPermission( |
| 39 | + application, |
| 40 | + Manifest.permission.ACCESS_COARSE_LOCATION |
| 41 | + ) != PackageManager.PERMISSION_GRANTED |
| 42 | + ) { |
| 43 | + Log.w("LocationTrackerImpl", "Location permission not granted.") |
| 44 | + trySend(Resource.Error("Location permission not granted.")) |
| 45 | + channel.close() |
| 46 | + return@callbackFlow |
| 47 | + } |
37 | 48 |
|
38 | | - // Check if GPS is enabled |
39 | | - val locationManager = |
40 | | - application.getSystemService(Context.LOCATION_SERVICE) as LocationManager |
41 | | - val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || |
| 49 | + val locationManager = |
| 50 | + application.getSystemService(Context.LOCATION_SERVICE) as LocationManager |
| 51 | + val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) |
| 52 | + val isNetworkEnabled = |
42 | 53 | locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) |
43 | 54 |
|
44 | | - if (!hasAccessFineLocationPermission && !hasAccessCoarseLocationPermission) { |
45 | | - Log.d("LocationTracker", "Location permission not granted.") |
46 | | - return null // Permissions not granted |
47 | | - } |
| 55 | + if (!isGpsEnabled && !isNetworkEnabled) { |
| 56 | + Log.w("LocationTrackerImpl", "GPS and Network providers are disabled.") |
| 57 | + trySend(Resource.Error("GPS is disabled. Please enable location services.")) |
| 58 | + channel.close() |
| 59 | + return@callbackFlow |
| 60 | + } |
48 | 61 |
|
49 | | - if (!isGpsEnabled) { |
50 | | - Log.d("LocationTracker", "GPS or Network location is not enabled.") |
51 | | - // TODO: Optionally, prompt user to enable location services |
52 | | - return null // GPS not enabled |
53 | | - } |
| 62 | + trySend(Resource.Loading(message = "Fetching current location...")) |
| 63 | + Log.d( |
| 64 | + "LocationTrackerImpl", |
| 65 | + "Requesting current location update using FusedLocationProviderClient.getCurrentLocation()." |
| 66 | + ) |
54 | 67 |
|
55 | | - // Permissions are granted and GPS is enabled, try to get location |
56 | | - // Using suspendCancellableCoroutine to bridge GMS Task API with coroutines |
57 | | - return suspendCancellableCoroutine { continuation -> |
58 | 68 | val cancellationTokenSource = CancellationTokenSource() |
59 | | - fusedLocationClient.getCurrentLocation( |
60 | | - Priority.PRIORITY_HIGH_ACCURACY, // Or PRIORITY_BALANCED_POWER_ACCURACY for coarse |
61 | | - cancellationTokenSource.token |
62 | | - ).addOnSuccessListener { location: Location? -> |
63 | | - // Got last known location. In some rare situations this can be null. |
64 | | - // Log.d("LocationTracker", "Location success: $location") |
65 | | - continuation.resume(location) |
66 | | - }.addOnFailureListener { exception -> |
67 | | - Log.e("LocationTracker", "Failed to get location: ${exception.message}") |
68 | | - continuation.resume(null) // Resume with null on failure |
69 | | - }.addOnCanceledListener { |
70 | | - Log.d("LocationTracker", "Location request canceled.") |
71 | | - continuation.cancel() // Cancel the coroutine if the task is canceled |
72 | | - } |
| 69 | + // Создаем запрос на текущее местоположение |
| 70 | + val currentLocationRequest = CurrentLocationRequest.Builder() |
| 71 | + .setPriority(Priority.PRIORITY_HIGH_ACCURACY) // Вы можете выбрать другой приоритет |
| 72 | + // .setDurationMillis(10000) // Опционально: максимальное время ожидания |
| 73 | + .build() |
| 74 | + |
| 75 | + locationClient.getCurrentLocation(currentLocationRequest, cancellationTokenSource.token) |
| 76 | + .addOnSuccessListener { location: Location? -> |
| 77 | + if (location != null) { |
| 78 | + Log.i("LocationTrackerImpl", "Successfully got CURRENT location: $location") |
| 79 | + trySend(Resource.Success(location)) |
| 80 | + } else { |
| 81 | + // Эта ситуация маловероятна при успешном вызове, но возможна, |
| 82 | + // если геолокация отключается в момент запроса. |
| 83 | + Log.w( |
| 84 | + "LocationTrackerImpl", |
| 85 | + "FusedLocationProviderClient.getCurrentLocation() returned null despite success listener." |
| 86 | + ) |
| 87 | + trySend(Resource.Error("Failed to get current location (null result). Try enabling high accuracy GPS.")) |
| 88 | + } |
| 89 | + channel.close() |
| 90 | + } |
| 91 | + .addOnFailureListener { exception -> |
| 92 | + Log.e( |
| 93 | + "LocationTrackerImpl", |
| 94 | + "Failed to get current location using FusedLocationProviderClient.getCurrentLocation()", |
| 95 | + exception |
| 96 | + ) |
| 97 | + trySend(Resource.Error("Failed to get current location: ${exception.message}")) |
| 98 | + channel.close() |
| 99 | + } |
| 100 | + .addOnCanceledListener { // Этот слушатель обычно не вызывается, если вы сами не отменяете CancellationToken |
| 101 | + Log.w( |
| 102 | + "LocationTrackerImpl", |
| 103 | + "Current location request was cancelled by FusedLocationProviderClient." |
| 104 | + ) |
| 105 | + trySend(Resource.Error("Location request cancelled.")) |
| 106 | + channel.close() |
| 107 | + } |
73 | 108 |
|
74 | | - // When the coroutine is cancelled, cancel the GMS Task |
75 | | - continuation.invokeOnCancellation { |
76 | | - cancellationTokenSource.cancel() |
| 109 | + awaitClose { |
| 110 | + Log.d("LocationTrackerImpl", "getCurrentLocation Flow closing. Cancelling token.") |
| 111 | + cancellationTokenSource.cancel() // Важно отменить токен при закрытии Flow |
77 | 112 | } |
78 | 113 | } |
79 | 114 | } |
|
0 commit comments