diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index e5a6dbd..8fa9e03 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -12,19 +12,22 @@ android { applicationId = "com.jmp.wayback" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() - versionCode = 5 + versionCode = 10 versionName = "1.0.0" } + + buildTypes { + release { + isMinifyEnabled = true + } + } + packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } - buildTypes { - getByName("release") { - isMinifyEnabled = true - } - } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -48,4 +51,5 @@ dependencies { implementation(libs.koin.core) implementation(libs.koin.core.android) implementation(libs.androidx.core.splashscreen) + coreLibraryDesugaring(libs.desugar.jdk.libs) } diff --git a/androidApp/keystore/bcprov-jdk18on-1.79.jar b/androidApp/keystore/bcprov-jdk18on-1.79.jar new file mode 100644 index 0000000..ce7a6a3 Binary files /dev/null and b/androidApp/keystore/bcprov-jdk18on-1.79.jar differ diff --git a/androidApp/keystore/encryption_public_key.pem b/androidApp/keystore/encryption_public_key.pem new file mode 100644 index 0000000..a17ce16 --- /dev/null +++ b/androidApp/keystore/encryption_public_key.pem @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnE3NmWDsBVLLp+/c8MAC +pQomk3ybI7rD8+tRQXOw1Q4lX7yiTXdR5sLZjZgo04nshkOmZbO8MBpZtwayrcqu +KWtkTKaFFYvpIlLdJryH/vng+Ut7qvhTn3KFPqPzwuEVD1ERe56LHTK7bZDhI0WK +SLVAELU8QwfACBCWCFG+PDKpcdJjczCUoyJDREp+8NMpAd2pC3Ndfkvj9+YlrdLM +2u9yWlxZgFLeAqUTamFvtU9wPdQCTvHBZ7jvQ7gE9Smcla11BTIMv5vFgzMxDHci +FPPS7oqQY5jnM1j8n+IlYfPmTalpuV5JBjpg2HSaEMNcYgl1b4ZFPSNvCwyC3a5t +z+YV7pHPgJFdpTdSERkSi0qFQgTLQpqksVd50A3Yzbmt51GTnqNO+F9WEDLxau8u +xXrCkBf0gZBm4F0RvfVpSVBsWzfZfEDAy0+FRk045tANu9U5uooKRzlfhK/xZsp4 +qgb+qviMiG1NsBS4P0Bbtbk/WC8ljYeASOuaPFJWYOYtAgMBAAE= +-----END PUBLIC KEY----- diff --git a/androidApp/keystore/keystore b/androidApp/keystore/keystore index 86e34cf..86b8ba1 100644 Binary files a/androidApp/keystore/keystore and b/androidApp/keystore/keystore differ diff --git a/androidApp/keystore/output.zip b/androidApp/keystore/output.zip new file mode 100644 index 0000000..e862f98 Binary files /dev/null and b/androidApp/keystore/output.zip differ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 871f32f..8c8ad0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,7 @@ composeShimmer = "1.3.1" compottie = "2.0.0-rc02" constraintlayoutComposeMultiplatform = "0.5.0" coreSplashscreen = "1.0.1" +desugar_jdk_libs = "2.1.3" kotlin = "2.0.21" navigationComposeVersion = "2.7.0-alpha07" navigationRuntimeKtx = "2.8.3" @@ -49,6 +50,7 @@ compottie-dot = { module = "io.github.alexzhirkevich:compottie-dot", version.ref compottie-network = { module = "io.github.alexzhirkevich:compottie-network", version.ref = "compottie" } compottie-resources = { module = "io.github.alexzhirkevich:compottie-resources", version.ref = "compottie" } constraintlayout-compose-multiplatform = { module = "tech.annexflow.compose:constraintlayout-compose-multiplatform", version.ref = "constraintlayoutComposeMultiplatform" } +desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" } navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationComposeVersion" } androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } compose-uitooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } diff --git a/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Either.kt b/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Either.kt index b9a2c93..dafecfe 100644 --- a/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Either.kt +++ b/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Either.kt @@ -18,9 +18,9 @@ sealed class Either { throw Failure.NoConnectivityException Success(call()) } catch (exception: Failure.NoConnectivityException) { - Error(Failure.NoConnectivity()) + Error(Failure.NoConnectivity) } catch (exception: Exception) { - Error(Failure.UnknownError()) + Error(Failure.UnknownError) } } } diff --git a/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Failure.kt b/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Failure.kt index ce04db4..0ac5dad 100644 --- a/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Failure.kt +++ b/shared/common/src/commonMain/kotlin/com.jmp.wayback/common/Failure.kt @@ -1,14 +1,16 @@ package com.jmp.wayback.common -sealed class Failure(val message: Int? = null) { +sealed class Failure { data object NoConnectivityException : Exception() - class NoConnectivity : Failure() + data object NoConnectivity : Failure() - class UnknownError : Failure() + data object UnknownError : Failure() - class ErrorDisablingOnboarding : Failure() + data object ErrorDisablingOnboarding : Failure() + + data object LocationNotFound : Failure() data object ErrorSavingParkingInformation : Failure() } diff --git a/shared/presentation/build.gradle.kts b/shared/presentation/build.gradle.kts index b1c17b0..a489412 100644 --- a/shared/presentation/build.gradle.kts +++ b/shared/presentation/build.gradle.kts @@ -90,14 +90,17 @@ android { defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() } + sourceSets["main"].apply { manifest.srcFile("src/androidMain/AndroidManifest.xml") res.srcDirs("src/androidMain/resources") } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } + buildFeatures { compose = true } diff --git a/shared/presentation/src/androidMain/kotlin/com/jmp/wayback/presentation/app/provider/location/LocationProvider.kt b/shared/presentation/src/androidMain/kotlin/com/jmp/wayback/presentation/app/provider/location/LocationProvider.kt index 8119056..086050d 100644 --- a/shared/presentation/src/androidMain/kotlin/com/jmp/wayback/presentation/app/provider/location/LocationProvider.kt +++ b/shared/presentation/src/androidMain/kotlin/com/jmp/wayback/presentation/app/provider/location/LocationProvider.kt @@ -16,6 +16,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale import androidx.core.content.ContextCompat +import com.google.android.gms.location.CurrentLocationRequest import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices import com.jmp.wayback.presentation.R @@ -98,8 +99,10 @@ class LocationProvider { @SuppressLint("MissingPermission") suspend fun getUserLocation(): Location? = suspendCancellableCoroutine { continuation -> - fusedLocationClient.lastLocation - .addOnSuccessListener { location -> + fusedLocationClient.getCurrentLocation( + CurrentLocationRequest.Builder().build(), + null + ).addOnSuccessListener { location -> if (location != null) { getAddressFromLatLong( context = context, diff --git a/shared/presentation/src/commonMain/composeResources/values/strings.xml b/shared/presentation/src/commonMain/composeResources/values/strings.xml index ef9a4c0..b9bf9be 100644 --- a/shared/presentation/src/commonMain/composeResources/values/strings.xml +++ b/shared/presentation/src/commonMain/composeResources/values/strings.xml @@ -32,4 +32,8 @@ Since You got a note stop parking + We are having trouble finding your location + An error occurred parking your vehicle + Error + OK diff --git a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/MainScreen.kt b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/MainScreen.kt index 855323c..9cd4a72 100644 --- a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/MainScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/MainScreen.kt @@ -117,6 +117,9 @@ fun MainScreen( imagePath = generalUiState.data.nonParkedUiState.imagePath ) ) + }, + onDismissAlertRequest = { + viewModel.sendIntent(MainIntent.DismissParkingErrorAlert) } ) } diff --git a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/nonparked/NonParkedScreen.kt b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/nonparked/NonParkedScreen.kt index 06d3fb6..396f419 100644 --- a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/nonparked/NonParkedScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/view/nonparked/NonParkedScreen.kt @@ -1,21 +1,30 @@ package com.jmp.wayback.presentation.main.view.nonparked +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material.AlertDialog import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import com.jmp.wayback.presentation.app.common.compose.Colors.DialogActionColor +import com.jmp.wayback.presentation.app.common.compose.Colors.DialogContainerColor import com.jmp.wayback.presentation.app.common.compose.fadingEdge import com.jmp.wayback.presentation.main.viewmodel.NonParkedUiState import org.jetbrains.compose.resources.stringResource @@ -28,9 +37,10 @@ fun NonParkedScreen( onCameraButtonClicked: () -> Unit, onRemovePictureClicked: () -> Unit, onParkClicked: () -> Unit, + onDismissAlertRequest: () -> Unit, ) { ConstraintLayout(modifier = modifier) { - val (header, body, footer) = createRefs() + val (header, body, footer, alert) = createRefs() NonParkedScreenHeader( modifier = Modifier @@ -90,5 +100,57 @@ fun NonParkedScreen( fontWeight = FontWeight.ExtraBold ) } + + AnimatedVisibility( + modifier = Modifier.constrainAs(alert) { + start.linkTo(parent.start) + top.linkTo(parent.top) + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + }, + visible = uiState.error != null, + enter = EnterTransition.None, + exit = ExitTransition.None + ) { + uiState.error?.let { error -> + AlertDialog( + modifier = Modifier.fillMaxWidth(), + onDismissRequest = onDismissAlertRequest, + backgroundColor = DialogContainerColor, + shape = RoundedCornerShape(15), + title = { + Text( + text = stringResource(uiState.errorAlertTitle), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + color = Color.LightGray + ) + }, + text = { + Text( + text = stringResource(error), + fontWeight = FontWeight.Light, + fontSize = 15.sp, + color = Color.LightGray + ) + }, + confirmButton = { + TextButton( + onClick = onDismissAlertRequest, + colors = ButtonDefaults.textButtonColors( + backgroundColor = Color.Transparent, + contentColor = Color.White + ) + ) { + Text( + text = stringResource(uiState.errorAlertConfirmButtonText), + color = DialogActionColor, + textAlign = TextAlign.Center + ) + } + } + ) + } + } } } diff --git a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainIntent.kt b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainIntent.kt index 72bb6f4..20a1417 100644 --- a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainIntent.kt +++ b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainIntent.kt @@ -12,6 +12,8 @@ sealed class MainIntent { data object DismissStopParkingDialog : MainIntent() + data object DismissParkingErrorAlert : MainIntent() + data class ParkIntent( val detail: String, val imagePath: String? diff --git a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainUiProvider.kt b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainUiProvider.kt index b591b9a..6f8da2b 100644 --- a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainUiProvider.kt +++ b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainUiProvider.kt @@ -40,6 +40,8 @@ import wayback.shared.presentation.generated.resources.parked_screen_stop_parkin import wayback.shared.presentation.generated.resources.parked_screen_stop_parking_dialog_title import wayback.shared.presentation.generated.resources.parked_screen_title import wayback.shared.presentation.generated.resources.parked_screen_title_image_description +import wayback.shared.presentation.generated.resources.non_parked_screen_error_alert_title +import wayback.shared.presentation.generated.resources.non_parked_screen_error_alert_confirm_button class MainUiProvider { @@ -74,7 +76,9 @@ class MainUiProvider { Res.string.non_parked_screen_animated_text_4, Res.string.non_parked_screen_animated_text_5, Res.string.non_parked_screen_animated_text_6 - ) + ), + errorAlertTitle = Res.string.non_parked_screen_error_alert_title, + errorAlertConfirmButtonText = Res.string.non_parked_screen_error_alert_confirm_button ) private fun provideParkedUiState(parkingInformation: ParkingInformation): ParkedUiState = diff --git a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainViewModel.kt b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainViewModel.kt index 822f108..5aebc63 100644 --- a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainViewModel.kt +++ b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/MainViewModel.kt @@ -2,6 +2,7 @@ package com.jmp.wayback.presentation.main.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.jmp.wayback.common.Failure import com.jmp.wayback.common.doOnError import com.jmp.wayback.common.doOnSuccess import com.jmp.wayback.domain.interactor.ClearParkingInformation @@ -21,6 +22,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import wayback.shared.presentation.generated.resources.Res +import wayback.shared.presentation.generated.resources.location_not_found +import wayback.shared.presentation.generated.resources.parking_error import kotlin.coroutines.resume typealias UiState = GeneralUiState @@ -78,6 +82,16 @@ class MainViewModel( } } + private fun dismissParkingErrorAlert() { + _uiState.asLoaded()?.data?.let { state -> + updateUiState( + GeneralUiState.Loaded( + state.copy(nonParkedUiState = state.nonParkedUiState.copy(error = null)) + ) + ) + } + } + fun sendIntent(intent: MainIntent) { when (intent) { is MainIntent.UpdateDetailIntent -> updateDetail(intent.detail) @@ -85,6 +99,7 @@ class MainViewModel( is MainIntent.RemovePicture -> removePicture() is MainIntent.ShowStopParkingDialog -> updateStopDialogVisibility(true) is MainIntent.DismissStopParkingDialog -> updateStopDialogVisibility(false) + is MainIntent.DismissParkingErrorAlert -> dismissParkingErrorAlert() is MainIntent.ParkIntent -> park(intent.detail, intent.imagePath) is MainIntent.StopParking -> stopParking() } @@ -129,8 +144,10 @@ class MainViewModel( updatePlatformSpecificIsParkedStatus(isParked = true) updateUiStateToLoadingParking(false) }.doOnError { - updateUiStateToLoadingParking(false) + handleParkingError(it) } + } ?: run { + handleParkingError() } } } @@ -148,9 +165,8 @@ class MainViewModel( when (checkLocationPermissions()) { true -> saveParkingInformation(image) false -> requestLocationPermissions { - if (it) { - saveParkingInformation(image) - } else updateUiStateToLoadingParking(false) + if (it) saveParkingInformation(image) + else updateUiStateToLoadingParking(false) } } } @@ -206,6 +222,26 @@ class MainViewModel( } } + private fun handleParkingError(failure: Failure? = null) { + val error = when (failure) { + is Failure.LocationNotFound, null -> Res.string.location_not_found + else -> Res.string.parking_error + } + + uiState.asLoaded()?.data?.let { state -> + updateUiState( + GeneralUiState.Loaded( + state.copy( + nonParkedUiState = state.nonParkedUiState.copy( + error = error, + parking = false + ) + ) + ) + ) + } + } + private fun StateFlow.asLoaded(): GeneralUiState.Loaded? = this.value as? GeneralUiState.Loaded } diff --git a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/NonParkedUiState.kt b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/NonParkedUiState.kt index d247bdd..f07b65f 100644 --- a/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/NonParkedUiState.kt +++ b/shared/presentation/src/commonMain/kotlin/com/jmp/wayback/presentation/main/viewmodel/NonParkedUiState.kt @@ -21,5 +21,8 @@ data class NonParkedUiState( val parkButtonText: StringResource, val imagePath: String? = null, val parking: Boolean = false, - val animatedTexts: List + val animatedTexts: List, + val error: StringResource? = null, + val errorAlertTitle: StringResource, + val errorAlertConfirmButtonText: StringResource )