Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fda4b462dc | ||
|
|
6a1a15ade9 | ||
|
|
ea44cf5d2e | ||
|
|
984feb170a | ||
|
|
5691c87e91 |
@@ -22,7 +22,7 @@ val keystoreProperties = Properties().apply {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "pl.firmatpp.kierowca"
|
namespace = "pl.firmatpp.kierowca"
|
||||||
compileSdk = 35
|
compileSdk = 36
|
||||||
|
|
||||||
fun configValue(name: String, default: String): String =
|
fun configValue(name: String, default: String): String =
|
||||||
providers.gradleProperty(name)
|
providers.gradleProperty(name)
|
||||||
@@ -33,9 +33,9 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "pl.firmatpp.kierowca"
|
applicationId = "pl.firmatpp.kierowca"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 36
|
||||||
versionCode = 120
|
versionCode = 123
|
||||||
versionName = "1.0.67"
|
versionName = "1.0.70"
|
||||||
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
|
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.speech.RecognitionService" />
|
<action android:name="android.speech.RecognitionService" />
|
||||||
|
|||||||
@@ -22,17 +22,6 @@ class DeviceInfoProvider(
|
|||||||
androidVersion = Build.VERSION.RELEASE,
|
androidVersion = Build.VERSION.RELEASE,
|
||||||
sdkInt = Build.VERSION.SDK_INT,
|
sdkInt = Build.VERSION.SDK_INT,
|
||||||
appVersion = BuildConfig.VERSION_NAME,
|
appVersion = BuildConfig.VERSION_NAME,
|
||||||
serialNumber = readSerialNumber(),
|
serialNumber = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun readSerialNumber(): String? =
|
|
||||||
runCatching {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
Build.getSerial()
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
Build.SERIAL
|
|
||||||
}
|
|
||||||
}.getOrNull()
|
|
||||||
?.takeUnless { it.isBlank() || it.equals(Build.UNKNOWN, ignoreCase = true) }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,18 +229,28 @@ class DiagnosticSnapshotProvider(private val context: Context) {
|
|||||||
val validated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
val validated = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
||||||
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||||
val links = activeNetwork?.let(manager::getLinkProperties)
|
val links = activeNetwork?.let(manager::getLinkProperties)
|
||||||
|
val roaming = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
yesNo(!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING))
|
||||||
|
} else {
|
||||||
|
"niedostępne"
|
||||||
|
}
|
||||||
|
val mtu = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
links?.mtu?.toString() ?: "brak"
|
||||||
|
} else {
|
||||||
|
"niedostępne"
|
||||||
|
}
|
||||||
return listOf(
|
return listOf(
|
||||||
DiagnosticEntry("Transport", transports.joinToString()),
|
DiagnosticEntry("Transport", transports.joinToString()),
|
||||||
DiagnosticEntry("Internet zweryfikowany", yesNo(validated), if (validated) DiagnosticSeverity.Good else DiagnosticSeverity.Error),
|
DiagnosticEntry("Internet zweryfikowany", yesNo(validated), if (validated) DiagnosticSeverity.Good else DiagnosticSeverity.Error),
|
||||||
DiagnosticEntry("NET_CAPABILITY_INTERNET", yesNo(capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET))),
|
DiagnosticEntry("NET_CAPABILITY_INTERNET", yesNo(capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET))),
|
||||||
DiagnosticEntry("NET_CAPABILITY_VALIDATED", yesNo(capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))),
|
DiagnosticEntry("NET_CAPABILITY_VALIDATED", yesNo(capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED))),
|
||||||
DiagnosticEntry("Sieć mierzona", yesNo(!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED))),
|
DiagnosticEntry("Sieć mierzona", yesNo(!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED))),
|
||||||
DiagnosticEntry("Roaming", yesNo(!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING))),
|
DiagnosticEntry("Roaming", roaming),
|
||||||
DiagnosticEntry("Pasmo downstream / upstream", "${capabilities.linkDownstreamBandwidthKbps} / ${capabilities.linkUpstreamBandwidthKbps} kb/s"),
|
DiagnosticEntry("Pasmo downstream / upstream", "${capabilities.linkDownstreamBandwidthKbps} / ${capabilities.linkUpstreamBandwidthKbps} kb/s"),
|
||||||
DiagnosticEntry("Interfejs", links?.interfaceName ?: "brak"),
|
DiagnosticEntry("Interfejs", links?.interfaceName ?: "brak"),
|
||||||
DiagnosticEntry("Adresy IP", links?.linkAddresses?.joinToString { it.toString() }?.ifBlank { "brak" } ?: "brak"),
|
DiagnosticEntry("Adresy IP", links?.linkAddresses?.joinToString { it.toString() }?.ifBlank { "brak" } ?: "brak"),
|
||||||
DiagnosticEntry("DNS", links?.dnsServers?.joinToString { it.hostAddress ?: it.toString() }?.ifBlank { "brak" } ?: "brak"),
|
DiagnosticEntry("DNS", links?.dnsServers?.joinToString { it.hostAddress ?: it.toString() }?.ifBlank { "brak" } ?: "brak"),
|
||||||
DiagnosticEntry("MTU", links?.mtu?.toString() ?: "brak"),
|
DiagnosticEntry("MTU", mtu),
|
||||||
DiagnosticEntry("Proxy", links?.httpProxy?.toString() ?: "brak"),
|
DiagnosticEntry("Proxy", links?.httpProxy?.toString() ?: "brak"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -57,7 +57,11 @@ class LeaveRequestDecisionNotificationWorker(
|
|||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
NotificationManagerCompat.from(applicationContext).notify(leaveRequestId.hashCode(), notification)
|
try {
|
||||||
|
NotificationManagerCompat.from(applicationContext).notify(leaveRequestId.hashCode(), notification)
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ class NewRouteNotificationWorker(
|
|||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
NotificationManagerCompat.from(applicationContext).notify(route.id.hashCode(), notification)
|
try {
|
||||||
|
NotificationManagerCompat.from(applicationContext).notify(route.id.hashCode(), notification)
|
||||||
|
} catch (_: SecurityException) {
|
||||||
|
return@runCatching Result.success()
|
||||||
|
}
|
||||||
Result.success()
|
Result.success()
|
||||||
}.getOrElse { throwable ->
|
}.getOrElse { throwable ->
|
||||||
throwable.rethrowIfCancellation()
|
throwable.rethrowIfCancellation()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pl.firmatpp.kierowca.tracking
|
package pl.firmatpp.kierowca.tracking
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
@@ -160,6 +161,7 @@ class ActiveRouteTrackingService : Service(), LocationListener {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
private fun requestLocationUpdates() {
|
private fun requestLocationUpdates() {
|
||||||
if (!hasLocationPermission() || routeId.isNullOrBlank() || driverId.isNullOrBlank()) {
|
if (!hasLocationPermission() || routeId.isNullOrBlank() || driverId.isNullOrBlank()) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ import androidx.compose.material3.ButtonDefaults
|
|||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DatePicker
|
||||||
|
import androidx.compose.material3.DatePickerDialog
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -93,11 +95,13 @@ import androidx.compose.material3.ModalBottomSheet
|
|||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SelectableDates
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.rememberDatePickerState
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -145,6 +149,7 @@ import java.time.DayOfWeek
|
|||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
|
import java.time.ZoneOffset
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
@@ -1159,6 +1164,7 @@ private fun RoutesScreen(
|
|||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var cameraUri by remember { mutableStateOf<Uri?>(null) }
|
var cameraUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
var showPreciseLocationPermissionDialog by remember { mutableStateOf(false) }
|
var showPreciseLocationPermissionDialog by remember { mutableStateOf(false) }
|
||||||
|
var showRouteDatePicker by remember { mutableStateOf(false) }
|
||||||
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { ok ->
|
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { ok ->
|
||||||
val capturedUri = cameraUri
|
val capturedUri = cameraUri
|
||||||
if (
|
if (
|
||||||
@@ -1239,7 +1245,10 @@ private fun RoutesScreen(
|
|||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = TppTheme.colors.ink,
|
color = TppTheme.colors.ink,
|
||||||
)
|
)
|
||||||
DateChip(state.selectedDate)
|
DateChip(
|
||||||
|
date = state.selectedDate,
|
||||||
|
onClick = { showRouteDatePicker = true },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Row(
|
Row(
|
||||||
@@ -1253,7 +1262,10 @@ private fun RoutesScreen(
|
|||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = TppTheme.colors.ink,
|
color = TppTheme.colors.ink,
|
||||||
)
|
)
|
||||||
DateChip(state.selectedDate)
|
DateChip(
|
||||||
|
date = state.selectedDate,
|
||||||
|
onClick = { showRouteDatePicker = true },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1333,6 +1345,19 @@ private fun RoutesScreen(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showRouteDatePicker) {
|
||||||
|
RouteDatePickerDialog(
|
||||||
|
selectedDate = state.selectedDate,
|
||||||
|
minDate = state.minRouteDate,
|
||||||
|
maxDate = state.maxRouteDate,
|
||||||
|
onDismiss = { showRouteDatePicker = false },
|
||||||
|
onDateSelected = { date ->
|
||||||
|
showRouteDatePicker = false
|
||||||
|
onDate(date)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -2429,19 +2454,120 @@ private fun StitchHeader(height: Dp = 124.dp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DateChip(date: String) {
|
private fun DateChip(date: String, onClick: () -> Unit) {
|
||||||
val formatter = remember { DateTimeFormatter.ofPattern("d MMM", Locale("pl", "PL")) }
|
val formatter = remember { DateTimeFormatter.ofPattern("d MMM", Locale("pl", "PL")) }
|
||||||
val label = remember(date) { LocalDate.parse(date).format(formatter).replace(".", "").replaceFirstChar { it.uppercase() } }
|
val label = remember(date) { LocalDate.parse(date).format(formatter).replace(".", "").replaceFirstChar { it.uppercase() } }
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.background(TppTheme.colors.panel, RoundedCornerShape(12.dp)).padding(horizontal = 16.dp, vertical = 12.dp),
|
modifier = Modifier
|
||||||
|
.background(TppTheme.colors.panel, RoundedCornerShape(12.dp))
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Outlined.CalendarToday, contentDescription = null, tint = TppTheme.colors.forest)
|
Icon(
|
||||||
|
Icons.Outlined.CalendarToday,
|
||||||
|
contentDescription = "Otwórz kalendarz",
|
||||||
|
tint = TppTheme.colors.forest,
|
||||||
|
)
|
||||||
Text(label, fontFamily = FontFamily.Monospace, color = TppTheme.colors.muted, fontWeight = FontWeight.SemiBold)
|
Text(label, fontFamily = FontFamily.Monospace, color = TppTheme.colors.muted, fontWeight = FontWeight.SemiBold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun RouteDatePickerDialog(
|
||||||
|
selectedDate: String,
|
||||||
|
minDate: String,
|
||||||
|
maxDate: String,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onDateSelected: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val bounds = remember(selectedDate, minDate, maxDate) {
|
||||||
|
routeDatePickerBounds(
|
||||||
|
selectedDate = selectedDate,
|
||||||
|
minDate = minDate,
|
||||||
|
maxDate = maxDate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val selectableDates = remember(bounds) {
|
||||||
|
object : SelectableDates {
|
||||||
|
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
||||||
|
val date = Instant.ofEpochMilli(utcTimeMillis).atZone(ZoneOffset.UTC).toLocalDate()
|
||||||
|
return date in bounds.minDate..bounds.maxDate
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSelectableYear(year: Int): Boolean =
|
||||||
|
year in bounds.minDate.year..bounds.maxDate.year
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val datePickerState = rememberDatePickerState(
|
||||||
|
initialSelectedDateMillis = bounds.selectedDate
|
||||||
|
.atStartOfDay(ZoneOffset.UTC)
|
||||||
|
.toInstant()
|
||||||
|
.toEpochMilli(),
|
||||||
|
yearRange = bounds.minDate.year..bounds.maxDate.year,
|
||||||
|
selectableDates = selectableDates,
|
||||||
|
)
|
||||||
|
|
||||||
|
DatePickerDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
datePickerState.selectedDateMillis
|
||||||
|
?.let { Instant.ofEpochMilli(it).atZone(ZoneOffset.UTC).toLocalDate() }
|
||||||
|
?.takeIf { it in bounds.minDate..bounds.maxDate }
|
||||||
|
?.let { onDateSelected(it.toString()) }
|
||||||
|
},
|
||||||
|
enabled = datePickerState.selectedDateMillis != null,
|
||||||
|
) {
|
||||||
|
Text("Wybierz")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("Anuluj")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
DatePicker(
|
||||||
|
state = datePickerState,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
"Wybierz dzień kursów",
|
||||||
|
modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 16.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
showModeToggle = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class RouteDatePickerBounds(
|
||||||
|
val selectedDate: LocalDate,
|
||||||
|
val minDate: LocalDate,
|
||||||
|
val maxDate: LocalDate,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun routeDatePickerBounds(
|
||||||
|
selectedDate: String,
|
||||||
|
minDate: String,
|
||||||
|
maxDate: String,
|
||||||
|
): RouteDatePickerBounds {
|
||||||
|
val selected = runCatching { LocalDate.parse(selectedDate) }.getOrDefault(LocalDate.now())
|
||||||
|
val parsedMin = runCatching { LocalDate.parse(minDate) }.getOrDefault(selected)
|
||||||
|
val parsedMax = runCatching { LocalDate.parse(maxDate) }.getOrDefault(selected)
|
||||||
|
val rangeMin = minOf(parsedMin, parsedMax)
|
||||||
|
val rangeMax = maxOf(parsedMin, parsedMax)
|
||||||
|
|
||||||
|
return RouteDatePickerBounds(
|
||||||
|
selectedDate = selected.coerceIn(rangeMin, rangeMax),
|
||||||
|
minDate = rangeMin,
|
||||||
|
maxDate = rangeMax,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RouteDateSelector(selectedDate: String, minDate: String, maxDate: String, onDate: (String) -> Unit) {
|
private fun RouteDateSelector(selectedDate: String, minDate: String, maxDate: String, onDate: (String) -> Unit) {
|
||||||
val dates = remember(minDate, maxDate) {
|
val dates = remember(minDate, maxDate) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pl.firmatpp.kierowca.ui
|
package pl.firmatpp.kierowca.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
@@ -39,6 +40,7 @@ internal suspend fun currentPhotoLocation(context: Context): Location? {
|
|||||||
return bestLastKnownLocation(manager)
|
return bestLastKnownLocation(manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
private suspend fun LocationManager.awaitSingleLocation(
|
private suspend fun LocationManager.awaitSingleLocation(
|
||||||
context: Context,
|
context: Context,
|
||||||
provider: String,
|
provider: String,
|
||||||
@@ -75,6 +77,7 @@ private fun hasAnyLocationPermission(context: Context): Boolean =
|
|||||||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
||||||
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
private fun bestLastKnownLocation(manager: LocationManager): Location? {
|
private fun bestLastKnownLocation(manager: LocationManager): Location? {
|
||||||
val providers = listOf(
|
val providers = listOf(
|
||||||
LocationManager.GPS_PROVIDER,
|
LocationManager.GPS_PROVIDER,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package pl.firmatpp.kierowca.ui
|
||||||
|
|
||||||
|
import java.time.LocalDate
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RouteDatePickerBoundsTest {
|
||||||
|
@Test
|
||||||
|
fun usesTheSameMinAndMaxDatesAsTheRouteSelector() {
|
||||||
|
val bounds = routeDatePickerBounds(
|
||||||
|
selectedDate = "2026-07-24",
|
||||||
|
minDate = "2026-07-17",
|
||||||
|
maxDate = "2026-07-26",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(LocalDate.parse("2026-07-24"), bounds.selectedDate)
|
||||||
|
assertEquals(LocalDate.parse("2026-07-17"), bounds.minDate)
|
||||||
|
assertEquals(LocalDate.parse("2026-07-26"), bounds.maxDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clampsSelectedDateToAllowedRange() {
|
||||||
|
val bounds = routeDatePickerBounds(
|
||||||
|
selectedDate = "2026-07-30",
|
||||||
|
minDate = "2026-07-17",
|
||||||
|
maxDate = "2026-07-26",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(LocalDate.parse("2026-07-26"), bounds.selectedDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun normalizesAccidentallyReversedServerBounds() {
|
||||||
|
val bounds = routeDatePickerBounds(
|
||||||
|
selectedDate = "2026-07-24",
|
||||||
|
minDate = "2026-07-26",
|
||||||
|
maxDate = "2026-07-17",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(LocalDate.parse("2026-07-17"), bounds.minDate)
|
||||||
|
assertEquals(LocalDate.parse("2026-07-26"), bounds.maxDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.7.3"
|
agp = "8.10.1"
|
||||||
kotlin = "2.1.10"
|
kotlin = "2.1.10"
|
||||||
composeBom = "2026.03.00"
|
composeBom = "2026.03.00"
|
||||||
activityCompose = "1.9.3"
|
activityCompose = "1.9.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user