Commit 4bf53d84 by Aleksandr

Template for Print job details

parent 33b43604
......@@ -2,3 +2,4 @@ package com.isidroid.c23
class SpotHasNoPrintProfilesException(m: String? = null): Throwable(m)
class JobNotFoundException(m: String? = null): Throwable(m)
class NoLocationPermissionException(m: String? = null): Throwable(m)
\ No newline at end of file
......@@ -3,7 +3,15 @@ package com.isidroid.c23.constant
import androidx.annotation.StringDef
@Retention(AnnotationRetention.SOURCE)
@StringDef(Argument.URI, Argument.LATITUDE, Argument.LONGITUDE, Argument.SPOT_CODE, Argument.ID, Argument.INFO, Argument.MARKER)
@StringDef(
Argument.URI,
Argument.LATITUDE,
Argument.LONGITUDE,
Argument.SPOT_CODE,
Argument.ID,
Argument.INFO,
Argument.MARKER,
)
annotation class Argument {
companion object {
const val URI = "URI"
......@@ -13,5 +21,6 @@ annotation class Argument {
const val ID = "ID"
const val INFO = "INFO"
const val MARKER = "MARKER"
const val DISTANCE = "DISTANCE"
}
}
......@@ -15,6 +15,7 @@ fun PrintJob.createListItem(context: Context, richSpot: RichSpot?): PrintJobList
id = id,
spotCode = richSpot?.spot?.code,
spotName = richSpot?.spot?.name ?: "Deleted spot",
spotAddress = richSpot?.spot?.address ?: "Deleted spot",
cost = cost,
paperInfo = printSize.printSizeName,
isColor = profile?.grayscale != true,
......@@ -25,5 +26,7 @@ fun PrintJob.createListItem(context: Context, richSpot: RichSpot?): PrintJobList
statusName = context.getString(getPrintJobStatus(status)),
accessCode = accessCode.orEmpty(),
createdAt = createdAt,
profileName = profile?.name,
profileCost = profile?.cost
)
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ data class PrintJobListItem(
val id: String,
val spotName: String,
val spotCode: String?,
val spotAddress: String?,
val cost: Float,
val paperInfo: String,
val isColor: Boolean,
......@@ -16,5 +17,7 @@ data class PrintJobListItem(
val statusColor: Color,
val cover: String?,
val accessCode: String,
val createdAt: Date
val createdAt: Date,
val profileName: String?,
val profileCost: Float?
)
\ No newline at end of file
package com.isidroid.c23.domain.use_case
import android.Manifest
import android.content.Context
import androidx.compose.ui.text.intl.Locale
import com.isidroid.c23.JobNotFoundException
import com.isidroid.c23.NoLocationPermissionException
import com.isidroid.c23.constant.Argument
import com.isidroid.c23.data.mapper.createListItem
import com.isidroid.c23.ext.isDebug
import com.isidroid.core.FlowResult
import com.isidroid.job.repository.JobRepository
import com.isidroid.location.ext.isMileUnit
import com.isidroid.location.repository.LocationRepository
import com.isidroid.spot.repository.SpotRepository
import com.isidroid.ui.maps.ext.addKilometerToLatLng
import com.isidroid.ui.maps.model.MapMarker
import com.isidroid.utils.hasPermission
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.flow
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round
@Singleton
class DetailsUseCase @Inject constructor(
......@@ -26,11 +33,30 @@ class DetailsUseCase @Inject constructor(
fun loadDetails(id: String) = flow {
emit(FlowResult.Loading)
val hasLocationPermission = context.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
if (!hasLocationPermission) {
throw NoLocationPermissionException()
}
val job = jobRepository.readLocalList(ids = listOf(id)).firstOrNull() ?: throw JobNotFoundException()
val richSpot = spotRepository.findLocalRichSpots(ids = listOf(job.spotId))?.firstOrNull()
val result = job.createListItem(context = context, richSpot = richSpot)
val currentLocation = locationRepository.getCurrentLocation()
val distanceKm = locationRepository.findDistance(
currentLocation.first,
currentLocation.second,
richSpot?.spot?.lat,
richSpot?.spot?.lng
)?.let { (round(it * 100) / 100) }
val distance = buildString {
distanceKm?.let { append("$distanceKm ") }
if (Locale.current.isMileUnit)
append("miles")
else
append("km")
}
val location = if (isDebug() && richSpot?.spot != null)
addKilometerToLatLng(richSpot.spot.lat, richSpot.spot.lng, distance = 0.2)
......@@ -47,7 +73,8 @@ class DetailsUseCase @Inject constructor(
Argument.INFO to result,
Argument.LATITUDE to location.first,
Argument.LONGITUDE to location.second,
Argument.MARKER to mapMarker
Argument.MARKER to mapMarker,
Argument.DISTANCE to distance
)
)
)
......
......@@ -11,6 +11,7 @@ class JobDetailsContract {
data object OpenConfirmationMapRoute: Event
data object DismissBuildRouteConfirmation: Event
data object OpenNavigationApp: Event
data object LocationPermissionGranted: Event
}
sealed interface Effect : ViewSideEffect {
......@@ -25,6 +26,8 @@ class JobDetailsContract {
val printJob: PrintJobListItem? = null,
val lat: Double? = null,
val lng: Double? = null,
val routeConfirmationVisible: Boolean = false
val routeConfirmationVisible: Boolean = false,
val requestLocationPermission: Boolean = false,
val distance: String? = null
) : ViewState
}
\ No newline at end of file
package com.isidroid.c23.ui.screen.details
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import com.isidroid.c23.R
import com.isidroid.c23.ui._component.TopAppBarComponent
import com.isidroid.c23.ui.screen.details.component.DetailsV1
import com.isidroid.c23.ui.screen.details.component.DetailsV2
import com.isidroid.c23.ui.screen.details.component.PrintCodeComponent
import com.isidroid.c23.ui.screen.map.MapContract
import com.isidroid.c23.ui.screen.map._components.TPMapComponent
import com.isidroid.c23.ui.screen.map._components.RequestLocationPermissionComponent
import com.isidroid.core.vm.SIDE_EFFECTS_KEY
import com.isidroid.ui.maps.model.MapMarker
import kotlinx.coroutines.flow.Flow
......@@ -59,7 +21,21 @@ fun JobDetailsScreen(
onNavigationRequested: (navigationEffect: JobDetailsContract.Effect.Navigation) -> Unit,
modifier: Modifier = Modifier,
) {
DetailsV1(state, effectFlow, spotResultStateFlow, onEventSent, onNavigationRequested, modifier)
// DetailsV2(state, effectFlow, spotResultStateFlow, onEventSent, onNavigationRequested, modifier)
}
RequestLocationPermissionComponent(
requestLocationPermission = state.value.requestLocationPermission,
onGranted = { onEventSent(JobDetailsContract.Event.LocationPermissionGranted) }
)
LaunchedEffect(SIDE_EFFECTS_KEY) {
effectFlow?.collect { effect ->
when (effect) {
is JobDetailsContract.Effect.Navigation -> onNavigationRequested(effect)
}
}
}
BackHandler { onEventSent(JobDetailsContract.Event.ToBack) }
// DetailsV1(state, effectFlow, spotResultStateFlow, onEventSent, onNavigationRequested, modifier)
DetailsV2(state, spotResultStateFlow, onEventSent, modifier)
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.isidroid.c23.NoLocationPermissionException
import com.isidroid.c23.constant.Argument
import com.isidroid.c23.domain.dto.PrintJobListItem
import com.isidroid.c23.domain.use_case.DetailsUseCase
......@@ -17,9 +18,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.singleOrNull
import kotlinx.coroutines.launch
import javax.inject.Inject
......@@ -31,12 +32,17 @@ class JobDetailsViewModel @Inject constructor(
private val _spotResultStateFlow = MutableStateFlow<List<MapMarker>>(emptyList())
val spotResultStateFlow = _spotResultStateFlow.asStateFlow()
private lateinit var jobId: String
init {
viewModelScope.launch {
savedStateHandle.getStateFlow<String?>(Argument.ID, null)
.filterNotNull()
.onEach { loadDetails(it) }
.firstOrNull()
.onEach {
jobId = it
loadDetails()
}
.singleOrNull()
}
}
......@@ -49,14 +55,19 @@ class JobDetailsViewModel @Inject constructor(
JobDetailsContract.Event.OpenConfirmationMapRoute -> setState { copy(routeConfirmationVisible = true) }
JobDetailsContract.Event.DismissBuildRouteConfirmation -> setState { copy(routeConfirmationVisible = false) }
JobDetailsContract.Event.OpenNavigationApp -> openNavigationApp()
JobDetailsContract.Event.LocationPermissionGranted -> locationPermissionGranted()
}
}
// handle events
private suspend fun loadDetails(id: String) {
useCase.loadDetails(id)
private suspend fun loadDetails() {
useCase.loadDetails(jobId)
.flowOn(Dispatchers.IO)
.catchTimber { }
.catchTimber {
when (it) {
is NoLocationPermissionException -> setState { copy(requestLocationPermission = true) }
}
}
.collect { res ->
when (res) {
FlowResult.Loading -> setState { copy(isLoading = true) }
......@@ -64,21 +75,27 @@ class JobDetailsViewModel @Inject constructor(
item = res.result[Argument.INFO] as? PrintJobListItem,
lat = res.result[Argument.LATITUDE] as? Double,
lng = res.result[Argument.LONGITUDE] as? Double,
mapMarker = res.result[Argument.MARKER] as? MapMarker
mapMarker = res.result[Argument.MARKER] as? MapMarker,
distance = res.result[Argument.DISTANCE] as? String
)
}
}
}
private suspend fun onDetails(item: PrintJobListItem?, lat: Double?, lng: Double?, mapMarker: MapMarker?) {
private suspend fun onDetails(item: PrintJobListItem?, lat: Double?, lng: Double?, mapMarker: MapMarker?, distance: String?) {
val myLocation = MapMarker(id = "my_location", lat = lat ?: 0.0, lng = lng ?: 0.0, name = "My location", imageVector = Icons.Outlined.AccountCircle)
_spotResultStateFlow.emit(listOfNotNull(mapMarker, myLocation))
setState { copy(isLoading = false, printJob = item, lat = lat, lng = lng) }
setState { copy(isLoading = false, printJob = item, lat = lat, lng = lng, distance = distance) }
}
private fun openNavigationApp(){
private fun openNavigationApp() {
setState { copy(routeConfirmationVisible = false) }
setEffect { JobDetailsContract.Effect.Navigation.ToNavigationApp(viewState.value.lat, viewState.value.lng) }
}
private suspend fun locationPermissionGranted() {
setState { copy(requestLocationPermission = false) }
loadDetails()
}
}
\ No newline at end of file
package com.isidroid.c23.ui.screen.details.component
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.isidroid.c23.R
import com.isidroid.c23.ui.screen.details.JobDetailsContract
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun DisplayMapRouteConfirmation(
state: State<JobDetailsContract.State>,
onEventSent: (event: JobDetailsContract.Event) -> Unit
) {
if (!state.value.routeConfirmationVisible) return
val sheetState = rememberModalBottomSheetState()
ModalBottomSheet(
onDismissRequest = { onEventSent(JobDetailsContract.Event.DismissBuildRouteConfirmation) },
sheetState = sheetState,
windowInsets = WindowInsets.statusBars,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 120.dp),
) {
Text(
text = stringResource(id = R.string.app_navigation_explanation),
modifier = Modifier.padding(horizontal = 16.dp),
textAlign = TextAlign.Center
)
TextButton(
onClick = { onEventSent(JobDetailsContract.Event.OpenNavigationApp) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 18.dp)
) {
Text(
text = "Open in Google Maps",
)
}
}
}
\ No newline at end of file
......@@ -172,39 +172,3 @@ private fun InformationContent(
}
}
}
\ No newline at end of file
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DisplayMapRouteConfirmation(
state: State<JobDetailsContract.State>,
onEventSent: (event: JobDetailsContract.Event) -> Unit
) {
if (!state.value.routeConfirmationVisible) return
val sheetState = rememberModalBottomSheetState()
ModalBottomSheet(
onDismissRequest = { onEventSent(JobDetailsContract.Event.DismissBuildRouteConfirmation) },
sheetState = sheetState,
windowInsets = WindowInsets.statusBars,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 120.dp),
) {
Text(
text = stringResource(id = R.string.app_navigation_explanation),
modifier = Modifier.padding(horizontal = 16.dp),
textAlign = TextAlign.Center
)
TextButton(
onClick = { onEventSent(JobDetailsContract.Event.OpenNavigationApp) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 18.dp)
) {
Text(
text = "Open in Google Maps",
)
}
}
}
\ No newline at end of file
package com.isidroid.c23.ui.screen.details.component
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
......@@ -26,30 +25,27 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import com.isidroid.c23.R
import com.isidroid.c23.domain.dto.PrintJobListItem
import com.isidroid.c23.ui._component.TopAppBarComponent
import com.isidroid.c23.ui.screen.details.JobDetailsContract
import com.isidroid.c23.ui.screen.map.MapContract
import com.isidroid.c23.ui.screen.map._components.TPMapComponent
import com.isidroid.ui.maps.model.MapMarker
import com.isidroid.utils.asCost
import com.isidroid.utils.spaceInCenter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.text.DateFormat
import java.text.SimpleDateFormat
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailsV2(
state: State<JobDetailsContract.State>,
effectFlow: Flow<JobDetailsContract.Effect>?,
spotResultStateFlow: StateFlow<List<MapMarker>>,
onEventSent: (event: JobDetailsContract.Event) -> Unit,
onNavigationRequested: (navigationEffect: JobDetailsContract.Effect.Navigation) -> Unit,
modifier: Modifier = Modifier,
) {
val printJob = state.value.printJob ?: return
......@@ -69,10 +65,33 @@ fun DetailsV2(
.padding(top = paddingValues.calculateTopPadding())
.padding(horizontal = 16.dp)
) {
val textStrings = arrayOf(
PrintInfoComponent(printJob)
HorizontalDivider(Modifier.padding(vertical = 16.dp))
SpotInfoComponent(
lat = state.value.lat,
lng = state.value.lng,
distance = state.value.distance,
printJob = printJob,
spotResultStateFlow = spotResultStateFlow,
onEventSent = onEventSent,
paddingBottom = paddingValues.calculateBottomPadding()
)
}
DisplayMapRouteConfirmation(state, onEventSent)
}
}
@Composable
private fun PrintInfoComponent(printJob: PrintJobListItem) {
val textStrings = listOfNotNull(
"PIN code: ${printJob.accessCode.spaceInCenter()}",
"${printJob.cost.asCost()} | Unpaid",
DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(printJob.createdAt)
"${printJob.profileCost?.asCost()} per page",
DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT).format(printJob.createdAt),
"copies: ${printJob.copies}",
printJob.profileName,
)
for (text in textStrings) {
......@@ -90,18 +109,57 @@ fun DetailsV2(
.background(printJob.statusColor)
.padding(horizontal = 6.dp, vertical = 2.dp)
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(Color.Black)
@Composable
private fun ColumnScope.SpotInfoComponent(
lat: Double?,
lng: Double?,
distance: String?,
printJob: PrintJobListItem,
spotResultStateFlow: StateFlow<List<MapMarker>>,
onEventSent: (event: JobDetailsContract.Event) -> Unit,
paddingBottom: Dp
) {
val textStrings = listOfNotNull(
"Spot: ${printJob.spotName}",
"Address: ${printJob.spotAddress}",
distance?.let { "Distance: $distance" }
)
for (text in textStrings) {
Text(text = text)
Spacer(modifier = Modifier.height(8.dp))
}
Row(modifier = Modifier.fillMaxWidth()) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp)
.weight(1f),
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp, bottomStart = 0.dp, bottomEnd = 0.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 12.dp)
) {
TPMapComponent(
modifier = Modifier,
onEventSent = { event ->
when (event) {
is MapContract.Event.ClickOnMarker -> onEventSent(JobDetailsContract.Event.OpenConfirmationMapRoute)
else -> {}
}
},
mapMarkersStateFlow = spotResultStateFlow,
lat = lat,
lng = lng
)
}
Button(
onClick = { onEventSent(JobDetailsContract.Event.OpenConfirmationMapRoute) },
modifier = Modifier
.padding(bottom = paddingBottom, top = 16.dp)
.fillMaxWidth()
) {
Text(text = "Build a route to Print spot")
}
}
\ No newline at end of file
......@@ -53,7 +53,7 @@ fun MapScreen(
}
RequestLocationPermissionComponent(
state = state,
requestLocationPermission = state.value.requestLocationPermission,
onGranted = { onEventSent(MapContract.Event.PermissionGranted) }
)
......
......@@ -19,8 +19,8 @@ import com.isidroid.c23.ui.screen.map.MapContract
import rememberPermissionState
@Composable
internal fun RequestLocationPermissionComponent(state: State<MapContract.State>, onGranted: () -> Unit) {
if (!state.value.requestLocationPermission)
internal fun RequestLocationPermissionComponent(requestLocationPermission: Boolean, onGranted: () -> Unit) {
if (!requestLocationPermission)
return
val context = LocalContext.current
......
......@@ -12,11 +12,16 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.flow.singleOrNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
......@@ -34,6 +39,25 @@ import kotlin.coroutines.cancellation.CancellationException
class ExampleUnitTest {
@Test
fun stateFlow() {
val coroutineScope = CoroutineScope(Dispatchers.Default)
val job = coroutineScope.launch {
val flow = flow {
repeat(10) {
delay(1000)
emit(it)
}
}
val stateFlow = flow.stateIn(this, started = SharingStarted.Lazily, -1)
stateFlow.onEach { println("result: $it") }.singleOrNull()
}
runBlocking {
delay(5_000)
job.cancel()
}
}
}
\ No newline at end of file
package com.isidroid.location.repository
import android.annotation.SuppressLint
import android.content.Context
import androidx.compose.ui.text.intl.Locale
import com.google.android.gms.location.LocationServices
import com.google.android.gms.tasks.Tasks
import com.isidroid.location.repository.LocationRepository
import com.isidroid.location.ext.calculateDistanceBetweenPoints
import timber.log.Timber
class LocationRepositoryImpl(private val context: Context) : LocationRepository {
@SuppressLint("MissingPermission")
override fun getCurrentLocation(): Pair<Double, Double> {
val client = LocationServices.getFusedLocationProviderClient(context)
return with(Tasks.await(client.lastLocation)) {
Pair(latitude, longitude)
return with(Tasks.await(client.lastLocation)) { Pair(latitude, longitude) }
}
override fun findDistance(myLat: Double, myLng: Double, locationLat: Double?, locationLng: Double?): Float? {
if (locationLat == null || locationLng == null) return null
val result = calculateDistanceBetweenPoints(Locale.Companion.current, myLat, myLng, locationLat, locationLng)
return result.toFloat()
}
}
\ No newline at end of file
package com.isidroid.location.ext
import androidx.compose.ui.text.intl.Locale
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
internal fun calculateDistanceBetweenPoints(
locale: Locale,
myLat: Double,
myLng: Double,
locationLat: Double,
locationLng: Double
): Double {
val earthRadius = 6371.0 // Радиус Земли в километрах
val dLat = Math.toRadians(locationLat - myLat)
val dLng = Math.toRadians(locationLng - myLng)
val a = sin(dLat / 2) * sin(dLat / 2) +
cos(Math.toRadians(myLat)) * cos(Math.toRadians(locationLat)) *
sin(dLng / 2) * sin(dLng / 2)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
val distanceKm = earthRadius * c
val distance = if (locale.isMileUnit)
convertKmToMiles(distanceKm)
else
distanceKm
return distance
}
val Locale.isMileUnit get() = region in arrayOf("US")
internal fun convertKmToMiles(km: Double): Double {
return km * 0.621371
}
......@@ -2,4 +2,5 @@ package com.isidroid.location.repository
interface LocationRepository {
fun getCurrentLocation(): Pair<Double, Double>
fun findDistance(myLat: Double, myLng: Double, locationLat: Double?, locationLng: Double?): Float?
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment