diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e0e3a20..9e2e7fa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,8 +12,8 @@ android { applicationId = "pl.firmatpp.kierowca" minSdk = 26 targetSdk = 35 - versionCode = 4 - versionName = "1.0.3" + versionCode = 5 + versionName = "1.0.4" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "API_BASE_URL", "\"https://api-intranet.firmatpp.pl/api/\"") diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt index cf60c62..2cbdf1b 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverApp.kt @@ -9,9 +9,11 @@ import android.content.IntentFilter import android.content.pm.PackageManager import android.net.Uri import android.os.Build +import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable @@ -46,10 +48,8 @@ import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.CameraAlt import androidx.compose.material.icons.outlined.CalendarToday import androidx.compose.material.icons.outlined.LocalShipping -import androidx.compose.material.icons.outlined.Menu import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Phone -import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card @@ -78,6 +78,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType @@ -94,6 +95,7 @@ import java.io.File import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.Locale +import pl.firmatpp.kierowca.R import pl.firmatpp.kierowca.data.model.DriverRouteDto import pl.firmatpp.kierowca.data.model.RoutePhotoDto import pl.firmatpp.kierowca.domain.OtpCodeExtractor @@ -103,6 +105,9 @@ import pl.firmatpp.kierowca.ui.theme.TppColors @Composable fun DriverApp(viewModel: DriverViewModel) { val state by viewModel.state.collectAsState() + BackHandler(enabled = state.screen != DriverScreen.Phone && state.screen != DriverScreen.Routes) { + viewModel.back() + } SmsUserConsentEffect( enabled = state.screen == DriverScreen.Phone || state.screen == DriverScreen.Otp, onCode = viewModel::updateOtpCode, @@ -112,7 +117,8 @@ fun DriverApp(viewModel: DriverViewModel) { when (state.screen) { DriverScreen.Phone -> PhoneScreen(state, viewModel::requestOtp) DriverScreen.Otp -> OtpScreen(state, viewModel::updateOtpCode, viewModel::verifyOtp, viewModel::back) - DriverScreen.Routes -> RoutesScreen(state, viewModel::refreshRoutes, viewModel::openRoute) + DriverScreen.Routes -> RoutesScreen(state, viewModel::refreshRoutes, viewModel::openProfile, viewModel::openRoute) + DriverScreen.Profile -> ProfileScreen(state, viewModel::refreshRoutes, viewModel::openProfile, viewModel::logout) DriverScreen.Detail -> DetailScreen(state, viewModel::back, viewModel::uploadPhoto, viewModel::openPhoto) DriverScreen.Photo -> PhotoScreen(state, viewModel::back) } @@ -184,11 +190,18 @@ private fun AuthShell(title: String, subtitle: String, onBack: (() -> Unit)? = n private fun RoutesScreen( state: DriverUiState, onRefresh: () -> Unit, + onProfile: () -> Unit, onRoute: (String) -> Unit, ) { Scaffold( - topBar = { StitchHeader(onRefresh = onRefresh) }, - bottomBar = { StitchBottomBar(driverName = state.driver?.displayName.orEmpty(), onRefresh = onRefresh) }, + topBar = { StitchHeader() }, + bottomBar = { + StitchBottomBar( + activeScreen = DriverScreen.Routes, + onRoutes = onRefresh, + onProfile = onProfile, + ) + }, containerColor = TppColors.Surface, ) { padding -> LazyColumn( @@ -222,38 +235,26 @@ private fun RoutesScreen( } @Composable -private fun StitchHeader(onRefresh: () -> Unit) { +private fun StitchHeader() { Column(Modifier.fillMaxWidth().background(Color.White).statusBarsPadding()) { - Row( + Box( Modifier.fillMaxWidth().height(104.dp).padding(horizontal = 24.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + contentAlignment = Alignment.Center, ) { - IconButton(onClick = onRefresh) { - Icon(Icons.Outlined.Menu, contentDescription = "Menu", tint = TppColors.Muted, modifier = Modifier.size(32.dp)) - } - TppWordmark() - IconButton(onClick = { }) { - Icon(Icons.Outlined.Person, contentDescription = "Profil", tint = TppColors.Muted, modifier = Modifier.size(32.dp)) - } + TppLogo() } HorizontalDivider(color = TppColors.Outline.copy(alpha = 0.8f), thickness = 2.dp) } } @Composable -private fun TppWordmark() { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Column(verticalArrangement = Arrangement.spacedBy(3.dp)) { - repeat(3) { - Box(Modifier.width(28.dp).height(4.dp).background(TppColors.Forest)) - } - } - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text("FIRMA", color = TppColors.LogoLeaf, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold) - Text("TPP", color = TppColors.Ink, style = MaterialTheme.typography.headlineLarge, fontWeight = FontWeight.Black) - } - } +private fun TppLogo() { + Image( + painter = painterResource(R.drawable.tpp5), + contentDescription = "TPP", + contentScale = ContentScale.Fit, + modifier = Modifier.width(142.dp).height(50.dp), + ) } @Composable @@ -356,7 +357,60 @@ private fun RouteTimeline(origin: String, destination: String) { } @Composable -private fun StitchBottomBar(driverName: String, onRefresh: () -> Unit) { +private fun ProfileScreen( + state: DriverUiState, + onRoutes: () -> Unit, + onProfile: () -> Unit, + onLogout: () -> Unit, +) { + Scaffold( + topBar = { StitchHeader() }, + bottomBar = { + StitchBottomBar( + activeScreen = DriverScreen.Profile, + onRoutes = onRoutes, + onProfile = onProfile, + ) + }, + containerColor = TppColors.Surface, + ) { padding -> + Column( + Modifier + .fillMaxSize() + .padding(padding) + .padding(horizontal = 20.dp, vertical = 24.dp), + verticalArrangement = Arrangement.spacedBy(18.dp), + ) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = Color.White), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, TppColors.Outline), + ) { + Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Icon(Icons.Outlined.Person, contentDescription = null, tint = TppColors.Forest, modifier = Modifier.size(34.dp)) + Text( + "Witaj ${state.driver?.displayName.orEmpty().ifBlank { "Kierowco" }}", + style = MaterialTheme.typography.headlineSmall, + fontWeight = FontWeight.Bold, + color = TppColors.Ink, + ) + } + } + Button( + onClick = onLogout, + modifier = Modifier.fillMaxWidth().height(56.dp), + colors = ButtonDefaults.buttonColors(containerColor = TppColors.Forest), + shape = RoundedCornerShape(4.dp), + ) { + Text("Wyloguj", fontWeight = FontWeight.Bold) + } + } + } +} + +@Composable +private fun StitchBottomBar(activeScreen: DriverScreen, onRoutes: () -> Unit, onProfile: () -> Unit) { Column(Modifier.fillMaxWidth().background(Color.White).navigationBarsPadding()) { HorizontalDivider(color = TppColors.Outline.copy(alpha = 0.9f), thickness = 2.dp) Row( @@ -364,8 +418,8 @@ private fun StitchBottomBar(driverName: String, onRefresh: () -> Unit) { horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically, ) { - BottomNavItem("Zlecenia", Icons.Outlined.LocalShipping, active = true, onClick = onRefresh) - BottomNavItem(driverName.ifBlank { "Profil" }, Icons.Outlined.Person, active = false, onClick = { }) + BottomNavItem("Zlecenia", Icons.Outlined.LocalShipping, active = activeScreen == DriverScreen.Routes, onClick = onRoutes) + BottomNavItem("Profil", Icons.Outlined.Person, active = activeScreen == DriverScreen.Profile, onClick = onProfile) } } } diff --git a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt index 00a49ec..1035701 100644 --- a/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt +++ b/app/src/main/java/pl/firmatpp/kierowca/ui/DriverViewModel.kt @@ -13,7 +13,7 @@ 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, @@ -81,6 +81,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) @@ -96,6 +100,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 } diff --git a/app/src/main/res/drawable/tpp5.png b/app/src/main/res/drawable/tpp5.png new file mode 100644 index 0000000..782ccbb Binary files /dev/null and b/app/src/main/res/drawable/tpp5.png differ