Compare commits

...
7 Commits
Author SHA1 Message Date
admin bf6e76dcbf Odwzoruj szczegoly trasy ze Stitch 2026-06-30 22:00:59 +02:00
admin 18baec5020 Ukryj galerie wedlug ustawien 2026-06-30 21:52:29 +02:00
admin 2531ff2b9f Dodaj odswiezanie i wybor daty kursow 2026-06-30 21:47:10 +02:00
admin 983a77c9fb Popraw header i ekran profilu 2026-06-30 21:20:51 +02:00
admin 780463cc3a Popraw dolna nawigacje i safe area 2026-06-30 21:12:26 +02:00
admin 3e37a61024 Odwzoruj listę kursów ze Stitch 2026-06-30 21:05:26 +02:00
admin d4f867bf14 Popraw OTP, kafelki i aparat 2026-06-30 20:52:09 +02:00
10 changed files with 837 additions and 106 deletions
+5 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "pl.firmatpp.kierowca"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0.0"
versionCode = 8
versionName = "1.0.7"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildConfigField("String", "API_BASE_URL", "\"https://api-intranet.firmatpp.pl/api/\"")
@@ -64,6 +64,7 @@ dependencies {
implementation(libs.camera.view)
implementation(libs.coil.compose)
implementation(libs.compose.material.icons.extended)
implementation("androidx.compose.material:material")
implementation(libs.compose.material3)
implementation(libs.compose.ui)
implementation(libs.compose.ui.graphics)
@@ -71,6 +72,8 @@ dependencies {
implementation(libs.coroutines.android)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
implementation(libs.play.services.auth)
implementation(libs.play.services.auth.api.phone)
implementation(libs.retrofit)
implementation(libs.retrofit.gson)
@@ -30,8 +30,8 @@ class DriverRepository(
return response.driver ?: error("Brak danych kierowcy.")
}
suspend fun bootstrap(): BootstrapResponse =
api.bootstrap(authHeader(requireToken()))
suspend fun bootstrap(date: String? = null): BootstrapResponse =
api.bootstrap(authHeader(requireToken()), date)
suspend fun route(routeId: String): RouteResponse =
api.route(authHeader(requireToken()), routeId)
@@ -16,6 +16,7 @@ import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
interface MobileDriverApi {
@POST("mobile/driver/auth/request-otp")
@@ -28,7 +29,10 @@ interface MobileDriverApi {
suspend fun logout(@Header("Authorization") authorization: String): Map<String, Boolean>
@GET("mobile/driver/bootstrap")
suspend fun bootstrap(@Header("Authorization") authorization: String): BootstrapResponse
suspend fun bootstrap(
@Header("Authorization") authorization: String,
@Query("date") date: String?,
): BootstrapResponse
@GET("mobile/driver/routes/{routeId}")
suspend fun route(
@@ -30,6 +30,17 @@ data class DriverDto(
data class BootstrapResponse(
val session: DriverSessionDto,
val routes: RoutesBucketDto,
val driverAppSettings: DriverAppSettingsDto?,
)
data class DriverAppSettingsDto(
val minDate: String,
val maxDate: String,
val today: String,
val selectedDate: String,
val maxPastDays: Int,
val autoRefreshSeconds: Int,
val allowGalleryUploads: Boolean?,
)
data class DriverSessionDto(
@@ -0,0 +1,8 @@
package pl.firmatpp.kierowca.domain
object OtpCodeExtractor {
private val codePattern = Regex("""(?<!\d)\d{4,10}(?!\d)""")
fun extract(message: String): String? =
codePattern.find(message)?.value
}
File diff suppressed because it is too large Load Diff
@@ -8,20 +8,28 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.LocalDate
import pl.firmatpp.kierowca.data.DriverRepository
import pl.firmatpp.kierowca.data.model.DriverDto
import pl.firmatpp.kierowca.data.model.DriverRouteDto
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
enum class DriverScreen { Phone, Otp, Routes, Detail, Photo }
enum class DriverScreen { Phone, Otp, Routes, Profile, Detail, Photo }
data class DriverUiState(
val screen: DriverScreen = DriverScreen.Phone,
val loading: Boolean = false,
val refreshing: Boolean = false,
val phone: String = "",
val otpCode: String = "",
val maskedPhone: String = "",
val driver: DriverDto? = null,
val routes: List<DriverRouteDto> = emptyList(),
val selectedDate: String = LocalDate.now().toString(),
val minRouteDate: String = LocalDate.now().minusDays(7).toString(),
val maxRouteDate: String = LocalDate.now().plusDays(1).toString(),
val autoRefreshSeconds: Int = 60,
val allowGalleryUploads: Boolean = true,
val selectedRoute: DriverRouteDto? = null,
val selectedPhoto: RoutePhotoDto? = null,
val imageAuthHeader: String? = null,
@@ -45,6 +53,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
it.copy(
screen = DriverScreen.Otp,
phone = phone,
otpCode = "",
maskedPhone = response.phoneMasked.orEmpty(),
error = null,
)
@@ -57,16 +66,44 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
refreshRoutes()
}
fun refreshRoutes() = runLoading {
val response = repository.bootstrap()
_state.update {
it.copy(
screen = DriverScreen.Routes,
driver = response.session.driver,
routes = response.routes.today,
imageAuthHeader = repository.imageAuthHeader(),
error = null,
)
fun updateOtpCode(code: String) {
_state.update { it.copy(otpCode = code.filter(Char::isDigit).take(10)) }
}
fun refreshRoutes() = loadRoutes(date = _state.value.selectedDate, showLoading = true, navigateToRoutes = true)
fun refreshRoutesSilently() = loadRoutes(date = _state.value.selectedDate, showLoading = false, navigateToRoutes = false)
fun selectRouteDate(date: String) = loadRoutes(date = date, showLoading = true, navigateToRoutes = true)
private fun loadRoutes(date: String?, showLoading: Boolean, navigateToRoutes: Boolean) {
viewModelScope.launch {
_state.update { it.copy(loading = showLoading, refreshing = !showLoading, error = null) }
runCatching {
val response = repository.bootstrap(date)
val settings = response.driverAppSettings
_state.update {
it.copy(
screen = if (navigateToRoutes) DriverScreen.Routes else it.screen,
driver = response.session.driver,
routes = response.routes.today,
selectedDate = settings?.selectedDate ?: date ?: it.selectedDate,
minRouteDate = settings?.minDate ?: it.minRouteDate,
maxRouteDate = settings?.maxDate ?: it.maxRouteDate,
autoRefreshSeconds = settings?.autoRefreshSeconds ?: it.autoRefreshSeconds,
allowGalleryUploads = settings?.allowGalleryUploads ?: it.allowGalleryUploads,
imageAuthHeader = repository.imageAuthHeader(),
error = null,
)
}
}.onFailure { throwable ->
_state.update { it.copy(error = throwable.message ?: "Wystapil blad.") }
}
_state.update {
it.copy(loading = false, refreshing = false)
}
}
}
@@ -75,6 +112,10 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
_state.update { it.copy(screen = DriverScreen.Detail, selectedRoute = response.route, error = null) }
}
fun openProfile() {
_state.update { it.copy(screen = DriverScreen.Profile, error = null) }
}
fun uploadPhoto(uri: Uri, source: String) = runLoading {
val route = _state.value.selectedRoute ?: return@runLoading
repository.uploadPhoto(route.id, uri, source)
@@ -90,6 +131,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
when (it.screen) {
DriverScreen.Photo -> it.copy(screen = DriverScreen.Detail, selectedPhoto = null)
DriverScreen.Detail -> it.copy(screen = DriverScreen.Routes, selectedRoute = null)
DriverScreen.Profile -> it.copy(screen = DriverScreen.Routes)
DriverScreen.Otp -> it.copy(screen = DriverScreen.Phone)
else -> it
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 KiB

@@ -0,0 +1,20 @@
package pl.firmatpp.kierowca.domain
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class OtpCodeExtractorTest {
@Test
fun extractsFourToTenDigitOtpFromSmsBody() {
assertEquals("1234", OtpCodeExtractor.extract("Kod TPP: 1234"))
assertEquals("987654", OtpCodeExtractor.extract("Twoj kod weryfikacyjny to 987654."))
assertEquals("1234567890", OtpCodeExtractor.extract("Code 1234567890"))
}
@Test
fun ignoresNumbersThatAreTooShortOrTooLong() {
assertNull(OtpCodeExtractor.extract("Kod 123"))
assertNull(OtpCodeExtractor.extract("Kod 12345678901"))
}
}
+4
View File
@@ -13,6 +13,8 @@ datastore = "1.1.1"
coil = "2.7.0"
camerax = "1.4.1"
work = "2.10.0"
playServicesAuth = "21.6.0"
playServicesAuthApiPhone = "18.3.0"
junit = "4.13.2"
[libraries]
@@ -40,6 +42,8 @@ coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-
junit = { group = "junit", name = "junit", version.ref = "junit" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
play-services-auth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "playServicesAuth" }
play-services-auth-api-phone = { group = "com.google.android.gms", name = "play-services-auth-api-phone", version.ref = "playServicesAuthApiPhone" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }