Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae0d1b2d3d | ||
|
|
c5b3c5dae9 | ||
|
|
3fa3b4eac1 | ||
|
|
197bcc6f5e | ||
|
|
f4f5cec2be | ||
|
|
5a630f8401 |
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId = "pl.firmatpp.kierowca"
|
applicationId = "pl.firmatpp.kierowca"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 107
|
versionCode = 110
|
||||||
versionName = "1.0.54"
|
versionName = "1.0.57"
|
||||||
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
|
setProperty("archivesBaseName", "pl.firmatpp.kierowca")
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ data class RealtimeConfigDto(
|
|||||||
val reverbEnabled: Boolean = false,
|
val reverbEnabled: Boolean = false,
|
||||||
val reverbAppKey: String? = null,
|
val reverbAppKey: String? = null,
|
||||||
val reverbWsBaseUrl: String? = null,
|
val reverbWsBaseUrl: String? = null,
|
||||||
|
val reverbOrigin: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class RoutesBucketDto(
|
data class RoutesBucketDto(
|
||||||
|
|||||||
@@ -43,17 +43,17 @@ class DriverRepositoryLiveSyncGateway(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface LiveWebSocketFactory {
|
interface LiveWebSocketFactory {
|
||||||
fun newWebSocket(url: String, listener: WebSocketListener): WebSocket
|
fun newWebSocket(url: String, origin: String?, listener: WebSocketListener): WebSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
class OkHttpLiveWebSocketFactory(
|
class OkHttpLiveWebSocketFactory(
|
||||||
private val client: OkHttpClient,
|
private val client: OkHttpClient,
|
||||||
) : LiveWebSocketFactory {
|
) : LiveWebSocketFactory {
|
||||||
override fun newWebSocket(url: String, listener: WebSocketListener): WebSocket =
|
override fun newWebSocket(url: String, origin: String?, listener: WebSocketListener): WebSocket =
|
||||||
client.newWebSocket(
|
client.newWebSocket(
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header("Origin", websocketOrigin(url))
|
.header("Origin", origin?.takeIf { it.isNotBlank() } ?: websocketOrigin(url))
|
||||||
.build(),
|
.build(),
|
||||||
listener,
|
listener,
|
||||||
)
|
)
|
||||||
@@ -248,7 +248,7 @@ class DriverLiveSyncClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun connectNow(resetAttempt: Boolean = false) {
|
private fun connectNow(resetAttempt: Boolean = false) {
|
||||||
val wsUrl = synchronized(lock) {
|
val (wsUrl, origin) = synchronized(lock) {
|
||||||
val config = realtimeConfig ?: return
|
val config = realtimeConfig ?: return
|
||||||
val id = driverId ?: return
|
val id = driverId ?: return
|
||||||
if (!desiredActive || !foregroundActive || !networkAvailable || !isConfigUsable(config)) return
|
if (!desiredActive || !foregroundActive || !networkAvailable || !isConfigUsable(config)) return
|
||||||
@@ -262,11 +262,15 @@ class DriverLiveSyncClient(
|
|||||||
|
|
||||||
val appKey = config.reverbAppKey.orEmpty()
|
val appKey = config.reverbAppKey.orEmpty()
|
||||||
val wsBaseUrl = config.reverbWsBaseUrl.orEmpty()
|
val wsBaseUrl = config.reverbWsBaseUrl.orEmpty()
|
||||||
wsBaseUrl.trimEnd('/') + "/" + appKey + "?protocol=7&client=android&version=1.0&flash=false"
|
Pair(
|
||||||
|
wsBaseUrl.trimEnd('/') + "/" + appKey + "?protocol=7&client=android&version=1.0&flash=false",
|
||||||
|
config.reverbOrigin,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val socket = webSocketFactory.newWebSocket(
|
val socket = webSocketFactory.newWebSocket(
|
||||||
wsUrl,
|
wsUrl,
|
||||||
|
origin,
|
||||||
object : WebSocketListener() {
|
object : WebSocketListener() {
|
||||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||||
handleMessage(webSocket, text)
|
handleMessage(webSocket, text)
|
||||||
|
|||||||
@@ -228,6 +228,22 @@ fun DriverApp(
|
|||||||
viewModel.openLeaveRequest(initialLeaveRequestId)
|
viewModel.openLeaveRequest(initialLeaveRequestId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(state.screen, state.notifyNewRoutes, state.notificationPermissionDenied, appInForeground) {
|
||||||
|
if (!appInForeground || state.screen != DriverScreen.Profile || !state.notifyNewRoutes) return@LaunchedEffect
|
||||||
|
|
||||||
|
if (canPostNotifications(context)) {
|
||||||
|
if (state.notificationPermissionDenied) {
|
||||||
|
viewModel.clearNotificationPermissionWarning()
|
||||||
|
}
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !hasPostNotificationsRuntimePermission(context)) {
|
||||||
|
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
} else {
|
||||||
|
viewModel.markNotificationPermissionBlocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize().background(TppTheme.colors.surface)) {
|
Box(Modifier.fillMaxSize().background(TppTheme.colors.surface)) {
|
||||||
when (state.screen) {
|
when (state.screen) {
|
||||||
@@ -2238,13 +2254,18 @@ private fun ProfileScreen(
|
|||||||
Text("Zmiana powiadomień wymaga połączenia z internetem.", color = TppTheme.colors.muted, style = MaterialTheme.typography.bodySmall)
|
Text("Zmiana powiadomień wymaga połączenia z internetem.", color = TppTheme.colors.muted, style = MaterialTheme.typography.bodySmall)
|
||||||
}
|
}
|
||||||
if (state.notificationPermissionDenied) {
|
if (state.notificationPermissionDenied) {
|
||||||
|
Text(
|
||||||
|
"Android blokuje powiadomienia dla tej aplikacji. Token FCM może być zarejestrowany, ale powiadomienie nie pojawi się na telefonie, dopóki nie włączysz zgody systemowej.",
|
||||||
|
color = TppTheme.colors.error,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = onOpenNotificationSettings,
|
onClick = onOpenNotificationSettings,
|
||||||
modifier = Modifier.fillMaxWidth().height(48.dp),
|
modifier = Modifier.fillMaxWidth().height(48.dp),
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.navy),
|
colors = ButtonDefaults.buttonColors(containerColor = TppTheme.colors.navy),
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
) {
|
) {
|
||||||
Text("Przejdź do ustawień", fontWeight = FontWeight.Bold)
|
Text("Otwórz ustawienia powiadomień", fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.55f))
|
HorizontalDivider(color = TppTheme.colors.outline.copy(alpha = 0.55f))
|
||||||
@@ -2725,16 +2746,14 @@ private fun RouteStageScreen(
|
|||||||
label = { Text(weightLabel) },
|
label = { Text(weightLabel) },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
Text(
|
Text(
|
||||||
"t",
|
"t / kg",
|
||||||
color = TppTheme.colors.muted,
|
color = TppTheme.colors.muted,
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
supportingText = {
|
supportingText = {
|
||||||
if (weightBlocker != null) {
|
Text(weightBlocker ?: routeWeightInputHelpText(state.routeStageWeightText))
|
||||||
Text(weightBlocker)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
isError = weightBlocker != null,
|
isError = weightBlocker != null,
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package pl.firmatpp.kierowca.ui
|
package pl.firmatpp.kierowca.ui
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
import java.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.abs
|
||||||
import pl.firmatpp.kierowca.data.model.DriverRouteDto
|
import pl.firmatpp.kierowca.data.model.DriverRouteDto
|
||||||
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
|
import pl.firmatpp.kierowca.data.model.DispatchSheetReminderDto
|
||||||
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
|
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
|
||||||
@@ -15,6 +19,8 @@ import pl.firmatpp.kierowca.data.upload.RouteActionStatus
|
|||||||
import pl.firmatpp.kierowca.data.upload.RouteActionType
|
import pl.firmatpp.kierowca.data.upload.RouteActionType
|
||||||
|
|
||||||
private val shortDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM")
|
private val shortDateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd.MM")
|
||||||
|
private val routeWeightKilogramInputThreshold = BigDecimal("1000")
|
||||||
|
private const val routeWeightMaxTons = 999.999
|
||||||
|
|
||||||
enum class RouteFlowStepState {
|
enum class RouteFlowStepState {
|
||||||
Todo,
|
Todo,
|
||||||
@@ -150,6 +156,31 @@ fun routeStageRequirementIsVisible(requirement: String): Boolean =
|
|||||||
fun routeStageRequirementIsRequired(requirement: String): Boolean =
|
fun routeStageRequirementIsRequired(requirement: String): Boolean =
|
||||||
normalizeRouteStageRequirement(requirement) == RouteStageRequirement.Required
|
normalizeRouteStageRequirement(requirement) == RouteStageRequirement.Required
|
||||||
|
|
||||||
|
fun parseRouteWeightTons(weightText: String): Double? {
|
||||||
|
val enteredWeight = weightText.trim().replace(',', '.').toBigDecimalOrNull() ?: return null
|
||||||
|
val tons = if (enteredWeight.abs() >= routeWeightKilogramInputThreshold) {
|
||||||
|
enteredWeight.movePointLeft(3)
|
||||||
|
} else {
|
||||||
|
enteredWeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return tons.setScale(3, RoundingMode.HALF_UP).toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun routeWeightInputHelpText(weightText: String): String {
|
||||||
|
val enteredWeight = weightText.trim().replace(',', '.').toDoubleOrNull()
|
||||||
|
val tons = parseRouteWeightTons(weightText)
|
||||||
|
val enteredAsKilograms = enteredWeight != null &&
|
||||||
|
abs(enteredWeight) >= routeWeightKilogramInputThreshold.toDouble() &&
|
||||||
|
tons != null
|
||||||
|
|
||||||
|
return if (enteredAsKilograms) {
|
||||||
|
"Wpisano kilogramy — zapiszemy ${String.format(Locale("pl", "PL"), "%.3f", tons)} t."
|
||||||
|
} else {
|
||||||
|
"Wpisz tony (np. 23,4). Wartości od 1000 rozpoznajemy jako kilogramy (np. 23400)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun canSubmitRouteStageForm(
|
fun canSubmitRouteStageForm(
|
||||||
weightText: String,
|
weightText: String,
|
||||||
serverPhotoCount: Int,
|
serverPhotoCount: Int,
|
||||||
@@ -188,13 +219,14 @@ fun loadingWeightSubmitBlocker(
|
|||||||
photoRequirement: String = RouteStageRequirement.Required,
|
photoRequirement: String = RouteStageRequirement.Required,
|
||||||
): String? {
|
): String? {
|
||||||
val trimmed = weightText.trim()
|
val trimmed = weightText.trim()
|
||||||
val normalizedWeight = trimmed.replace(',', '.').toDoubleOrNull()
|
val normalizedWeight = parseRouteWeightTons(trimmed)
|
||||||
val weightVisible = routeStageRequirementIsVisible(weightRequirement)
|
val weightVisible = routeStageRequirementIsVisible(weightRequirement)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
weightVisible && routeStageRequirementIsRequired(weightRequirement) && trimmed.isBlank() -> "Podaj tonaż."
|
weightVisible && routeStageRequirementIsRequired(weightRequirement) && trimmed.isBlank() -> "Podaj tonaż."
|
||||||
weightVisible && trimmed.isNotBlank() && normalizedWeight == null -> "Podaj poprawny tonaż."
|
weightVisible && trimmed.isNotBlank() && normalizedWeight == null -> "Podaj poprawny tonaż."
|
||||||
weightVisible && normalizedWeight != null && normalizedWeight <= 0.0 -> "Tonaż musi być większy od zera."
|
weightVisible && normalizedWeight != null && normalizedWeight <= 0.0 -> "Tonaż musi być większy od zera."
|
||||||
|
weightVisible && normalizedWeight != null && normalizedWeight > routeWeightMaxTons -> "Tonaż nie może przekraczać 999,999 t."
|
||||||
loadingPhotosSubmitBlocker(serverPhotoCount, localUploadCount, photoRequirement) != null -> "Dodaj co najmniej jedno zdjęcie załadunku."
|
loadingPhotosSubmitBlocker(serverPhotoCount, localUploadCount, photoRequirement) != null -> "Dodaj co najmniej jedno zdjęcie załadunku."
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -208,13 +240,14 @@ fun routeStageSubmitBlocker(
|
|||||||
photoRequirement: String = RouteStageRequirement.Required,
|
photoRequirement: String = RouteStageRequirement.Required,
|
||||||
): String? {
|
): String? {
|
||||||
val trimmed = weightText.trim()
|
val trimmed = weightText.trim()
|
||||||
val normalizedWeight = trimmed.replace(',', '.').toDoubleOrNull()
|
val normalizedWeight = parseRouteWeightTons(trimmed)
|
||||||
val weightVisible = routeStageRequirementIsVisible(weightRequirement)
|
val weightVisible = routeStageRequirementIsVisible(weightRequirement)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
weightVisible && routeStageRequirementIsRequired(weightRequirement) && trimmed.isBlank() -> "Podaj tonaż."
|
weightVisible && routeStageRequirementIsRequired(weightRequirement) && trimmed.isBlank() -> "Podaj tonaż."
|
||||||
weightVisible && trimmed.isNotBlank() && normalizedWeight == null -> "Podaj poprawny tonaż."
|
weightVisible && trimmed.isNotBlank() && normalizedWeight == null -> "Podaj poprawny tonaż."
|
||||||
weightVisible && normalizedWeight != null && normalizedWeight <= 0.0 -> "Tonaż musi być większy od zera."
|
weightVisible && normalizedWeight != null && normalizedWeight <= 0.0 -> "Tonaż musi być większy od zera."
|
||||||
|
weightVisible && normalizedWeight != null && normalizedWeight > routeWeightMaxTons -> "Tonaż nie może przekraczać 999,999 t."
|
||||||
routeStageRequirementIsRequired(photoRequirement) && visiblePhotoAttachmentCount(serverPhotoCount, localUploadCount) <= 0 -> "Dodaj co najmniej jedno zdjęcie etapu."
|
routeStageRequirementIsRequired(photoRequirement) && visiblePhotoAttachmentCount(serverPhotoCount, localUploadCount) <= 0 -> "Dodaj co najmniej jedno zdjęcie etapu."
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,6 +639,24 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun markNotificationPermissionBlocked() {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
notificationPermissionDenied = true,
|
||||||
|
error = "Powiadomienia są włączone w aplikacji, ale Android blokuje ich wyświetlanie. Włącz powiadomienia w ustawieniach aplikacji.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearNotificationPermissionWarning() {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
notificationPermissionDenied = false,
|
||||||
|
error = if (it.error?.contains("powiadom", ignoreCase = true) == true) null else it.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setThemeMode(themeMode: AppThemeMode) {
|
fun setThemeMode(themeMode: AppThemeMode) {
|
||||||
_state.update { it.copy(themeMode = themeMode) }
|
_state.update { it.copy(themeMode = themeMode) }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@@ -1085,7 +1103,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
val photoRequirement = if (stage == "loading") snapshot.loadingPhotoRequirement else snapshot.unloadingPhotoRequirement
|
val photoRequirement = if (stage == "loading") snapshot.loadingPhotoRequirement else snapshot.unloadingPhotoRequirement
|
||||||
val weightRequirement = if (stage == "loading") snapshot.loadingWeightRequirement else snapshot.unloadingWeightRequirement
|
val weightRequirement = if (stage == "loading") snapshot.loadingWeightRequirement else snapshot.unloadingWeightRequirement
|
||||||
val weight = if (routeStageRequirementIsVisible(weightRequirement)) {
|
val weight = if (routeStageRequirementIsVisible(weightRequirement)) {
|
||||||
snapshot.routeStageWeightText.trim().replace(',', '.').toDoubleOrNull()
|
parseRouteWeightTons(snapshot.routeStageWeightText)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class DriverLiveSyncClientTest {
|
|||||||
reverbEnabled = true,
|
reverbEnabled = true,
|
||||||
reverbAppKey = "app-key",
|
reverbAppKey = "app-key",
|
||||||
reverbWsBaseUrl = "wss://example.test/app",
|
reverbWsBaseUrl = "wss://example.test/app",
|
||||||
|
reverbOrigin = "https://bootstrap-origin.test",
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -32,6 +33,16 @@ class DriverLiveSyncClientTest {
|
|||||||
assertEquals("http://localhost:8080", websocketOrigin("ws://localhost:8080/app/app-key"))
|
assertEquals("http://localhost:8080", websocketOrigin("ws://localhost:8080/app/app-key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun passesBootstrapOriginToWebSocketFactory() = runTest {
|
||||||
|
val factory = FakeWebSocketFactory()
|
||||||
|
val client = liveClient(factory = factory, scope = backgroundScope)
|
||||||
|
|
||||||
|
client.start("driver-1", config)
|
||||||
|
|
||||||
|
assertEquals("https://bootstrap-origin.test", factory.origins.single())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun reconnectsWithBackoffAfterSocketFailure() = runTest {
|
fun reconnectsWithBackoffAfterSocketFailure() = runTest {
|
||||||
val factory = FakeWebSocketFactory()
|
val factory = FakeWebSocketFactory()
|
||||||
@@ -211,10 +222,12 @@ class DriverLiveSyncClientTest {
|
|||||||
|
|
||||||
private class FakeWebSocketFactory : LiveWebSocketFactory {
|
private class FakeWebSocketFactory : LiveWebSocketFactory {
|
||||||
val sockets = mutableListOf<FakeWebSocket>()
|
val sockets = mutableListOf<FakeWebSocket>()
|
||||||
|
val origins = mutableListOf<String?>()
|
||||||
|
|
||||||
override fun newWebSocket(url: String, listener: WebSocketListener): WebSocket {
|
override fun newWebSocket(url: String, origin: String?, listener: WebSocketListener): WebSocket {
|
||||||
val socket = FakeWebSocket(listener)
|
val socket = FakeWebSocket(listener)
|
||||||
sockets += socket
|
sockets += socket
|
||||||
|
origins += origin
|
||||||
return socket
|
return socket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,11 +306,20 @@ class DriverUiRulesTest {
|
|||||||
fun routeStageFormRequiresPositiveWeightAndAtLeastOnePhotoOrQueuedUpload() {
|
fun routeStageFormRequiresPositiveWeightAndAtLeastOnePhotoOrQueuedUpload() {
|
||||||
assertTrue(canSubmitRouteStageForm(weightText = "12,5", serverPhotoCount = 1, localUploadCount = 0))
|
assertTrue(canSubmitRouteStageForm(weightText = "12,5", serverPhotoCount = 1, localUploadCount = 0))
|
||||||
assertTrue(canSubmitRouteStageForm(weightText = "12.5", serverPhotoCount = 0, localUploadCount = 1))
|
assertTrue(canSubmitRouteStageForm(weightText = "12.5", serverPhotoCount = 0, localUploadCount = 1))
|
||||||
|
assertTrue(canSubmitRouteStageForm(weightText = "23400", serverPhotoCount = 1, localUploadCount = 0))
|
||||||
assertFalse(canSubmitRouteStageForm(weightText = "", serverPhotoCount = 1, localUploadCount = 0))
|
assertFalse(canSubmitRouteStageForm(weightText = "", serverPhotoCount = 1, localUploadCount = 0))
|
||||||
assertFalse(canSubmitRouteStageForm(weightText = "0", serverPhotoCount = 1, localUploadCount = 0))
|
assertFalse(canSubmitRouteStageForm(weightText = "0", serverPhotoCount = 1, localUploadCount = 0))
|
||||||
assertFalse(canSubmitRouteStageForm(weightText = "12.5", serverPhotoCount = 0, localUploadCount = 0))
|
assertFalse(canSubmitRouteStageForm(weightText = "12.5", serverPhotoCount = 0, localUploadCount = 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parsesRouteWeightsEnteredInTonsOrKilograms() {
|
||||||
|
assertEquals(23.4, parseRouteWeightTons("23,4") ?: 0.0, 0.0)
|
||||||
|
assertEquals(23.4, parseRouteWeightTons("23400") ?: 0.0, 0.0)
|
||||||
|
assertEquals(23.401, parseRouteWeightTons("23400,5") ?: 0.0, 0.0)
|
||||||
|
assertEquals("Wpisano kilogramy — zapiszemy 23,400 t.", routeWeightInputHelpText("23400"))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun filtersRoutePhotosByStageWithOtherFallback() {
|
fun filtersRoutePhotosByStageWithOtherFallback() {
|
||||||
val photos = listOf(
|
val photos = listOf(
|
||||||
|
|||||||
Reference in New Issue
Block a user