Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf6e76dcbf | ||
|
|
18baec5020 | ||
|
|
2531ff2b9f | ||
|
|
983a77c9fb | ||
|
|
780463cc3a | ||
|
|
3e37a61024 | ||
|
|
d4f867bf14 |
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "pl.firmatpp.kierowca"
|
applicationId = "pl.firmatpp.kierowca"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 8
|
||||||
versionName = "1.0.0"
|
versionName = "1.0.7"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
buildConfigField("String", "API_BASE_URL", "\"https://api-intranet.firmatpp.pl/api/\"")
|
buildConfigField("String", "API_BASE_URL", "\"https://api-intranet.firmatpp.pl/api/\"")
|
||||||
@@ -64,6 +64,7 @@ dependencies {
|
|||||||
implementation(libs.camera.view)
|
implementation(libs.camera.view)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.compose.material.icons.extended)
|
implementation(libs.compose.material.icons.extended)
|
||||||
|
implementation("androidx.compose.material:material")
|
||||||
implementation(libs.compose.material3)
|
implementation(libs.compose.material3)
|
||||||
implementation(libs.compose.ui)
|
implementation(libs.compose.ui)
|
||||||
implementation(libs.compose.ui.graphics)
|
implementation(libs.compose.ui.graphics)
|
||||||
@@ -71,6 +72,8 @@ dependencies {
|
|||||||
implementation(libs.coroutines.android)
|
implementation(libs.coroutines.android)
|
||||||
implementation(libs.okhttp)
|
implementation(libs.okhttp)
|
||||||
implementation(libs.okhttp.logging)
|
implementation(libs.okhttp.logging)
|
||||||
|
implementation(libs.play.services.auth)
|
||||||
|
implementation(libs.play.services.auth.api.phone)
|
||||||
implementation(libs.retrofit)
|
implementation(libs.retrofit)
|
||||||
implementation(libs.retrofit.gson)
|
implementation(libs.retrofit.gson)
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class DriverRepository(
|
|||||||
return response.driver ?: error("Brak danych kierowcy.")
|
return response.driver ?: error("Brak danych kierowcy.")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun bootstrap(): BootstrapResponse =
|
suspend fun bootstrap(date: String? = null): BootstrapResponse =
|
||||||
api.bootstrap(authHeader(requireToken()))
|
api.bootstrap(authHeader(requireToken()), date)
|
||||||
|
|
||||||
suspend fun route(routeId: String): RouteResponse =
|
suspend fun route(routeId: String): RouteResponse =
|
||||||
api.route(authHeader(requireToken()), routeId)
|
api.route(authHeader(requireToken()), routeId)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import retrofit2.http.Multipart
|
|||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Part
|
import retrofit2.http.Part
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface MobileDriverApi {
|
interface MobileDriverApi {
|
||||||
@POST("mobile/driver/auth/request-otp")
|
@POST("mobile/driver/auth/request-otp")
|
||||||
@@ -28,7 +29,10 @@ interface MobileDriverApi {
|
|||||||
suspend fun logout(@Header("Authorization") authorization: String): Map<String, Boolean>
|
suspend fun logout(@Header("Authorization") authorization: String): Map<String, Boolean>
|
||||||
|
|
||||||
@GET("mobile/driver/bootstrap")
|
@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}")
|
@GET("mobile/driver/routes/{routeId}")
|
||||||
suspend fun route(
|
suspend fun route(
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ data class DriverDto(
|
|||||||
data class BootstrapResponse(
|
data class BootstrapResponse(
|
||||||
val session: DriverSessionDto,
|
val session: DriverSessionDto,
|
||||||
val routes: RoutesBucketDto,
|
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(
|
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.StateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.LocalDate
|
||||||
import pl.firmatpp.kierowca.data.DriverRepository
|
import pl.firmatpp.kierowca.data.DriverRepository
|
||||||
import pl.firmatpp.kierowca.data.model.DriverDto
|
import pl.firmatpp.kierowca.data.model.DriverDto
|
||||||
import pl.firmatpp.kierowca.data.model.DriverRouteDto
|
import pl.firmatpp.kierowca.data.model.DriverRouteDto
|
||||||
import pl.firmatpp.kierowca.data.model.RoutePhotoDto
|
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(
|
data class DriverUiState(
|
||||||
val screen: DriverScreen = DriverScreen.Phone,
|
val screen: DriverScreen = DriverScreen.Phone,
|
||||||
val loading: Boolean = false,
|
val loading: Boolean = false,
|
||||||
|
val refreshing: Boolean = false,
|
||||||
val phone: String = "",
|
val phone: String = "",
|
||||||
|
val otpCode: String = "",
|
||||||
val maskedPhone: String = "",
|
val maskedPhone: String = "",
|
||||||
val driver: DriverDto? = null,
|
val driver: DriverDto? = null,
|
||||||
val routes: List<DriverRouteDto> = emptyList(),
|
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 selectedRoute: DriverRouteDto? = null,
|
||||||
val selectedPhoto: RoutePhotoDto? = null,
|
val selectedPhoto: RoutePhotoDto? = null,
|
||||||
val imageAuthHeader: String? = null,
|
val imageAuthHeader: String? = null,
|
||||||
@@ -45,6 +53,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
it.copy(
|
it.copy(
|
||||||
screen = DriverScreen.Otp,
|
screen = DriverScreen.Otp,
|
||||||
phone = phone,
|
phone = phone,
|
||||||
|
otpCode = "",
|
||||||
maskedPhone = response.phoneMasked.orEmpty(),
|
maskedPhone = response.phoneMasked.orEmpty(),
|
||||||
error = null,
|
error = null,
|
||||||
)
|
)
|
||||||
@@ -57,16 +66,44 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
refreshRoutes()
|
refreshRoutes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshRoutes() = runLoading {
|
fun updateOtpCode(code: String) {
|
||||||
val response = repository.bootstrap()
|
_state.update { it.copy(otpCode = code.filter(Char::isDigit).take(10)) }
|
||||||
_state.update {
|
}
|
||||||
it.copy(
|
|
||||||
screen = DriverScreen.Routes,
|
fun refreshRoutes() = loadRoutes(date = _state.value.selectedDate, showLoading = true, navigateToRoutes = true)
|
||||||
driver = response.session.driver,
|
|
||||||
routes = response.routes.today,
|
fun refreshRoutesSilently() = loadRoutes(date = _state.value.selectedDate, showLoading = false, navigateToRoutes = false)
|
||||||
imageAuthHeader = repository.imageAuthHeader(),
|
|
||||||
error = null,
|
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) }
|
_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 {
|
fun uploadPhoto(uri: Uri, source: String) = runLoading {
|
||||||
val route = _state.value.selectedRoute ?: return@runLoading
|
val route = _state.value.selectedRoute ?: return@runLoading
|
||||||
repository.uploadPhoto(route.id, uri, source)
|
repository.uploadPhoto(route.id, uri, source)
|
||||||
@@ -90,6 +131,7 @@ class DriverViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
when (it.screen) {
|
when (it.screen) {
|
||||||
DriverScreen.Photo -> it.copy(screen = DriverScreen.Detail, selectedPhoto = null)
|
DriverScreen.Photo -> it.copy(screen = DriverScreen.Detail, selectedPhoto = null)
|
||||||
DriverScreen.Detail -> it.copy(screen = DriverScreen.Routes, selectedRoute = null)
|
DriverScreen.Detail -> it.copy(screen = DriverScreen.Routes, selectedRoute = null)
|
||||||
|
DriverScreen.Profile -> it.copy(screen = DriverScreen.Routes)
|
||||||
DriverScreen.Otp -> it.copy(screen = DriverScreen.Phone)
|
DriverScreen.Otp -> it.copy(screen = DriverScreen.Phone)
|
||||||
else -> it
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ datastore = "1.1.1"
|
|||||||
coil = "2.7.0"
|
coil = "2.7.0"
|
||||||
camerax = "1.4.1"
|
camerax = "1.4.1"
|
||||||
work = "2.10.0"
|
work = "2.10.0"
|
||||||
|
playServicesAuth = "21.6.0"
|
||||||
|
playServicesAuthApiPhone = "18.3.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -40,6 +42,8 @@ coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-
|
|||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||||
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", 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 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
retrofit-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
retrofit-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user