Skip to content

Commit

Permalink
Connected firestore to add and edit products
Browse files Browse the repository at this point in the history
  • Loading branch information
maxschwinghammer committed Jan 5, 2025
1 parent f21d396 commit ed25706
Show file tree
Hide file tree
Showing 22 changed files with 565 additions and 612 deletions.
7 changes: 2 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ android {

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["appAuthRedirectScheme"] = "com.freshkeeper"
buildConfigField(
"String",
"GOOGLE_API_KEY",
"\"${project.properties["GOOGLE_API_KEY"]}\"",
)
}

buildTypes {
Expand Down Expand Up @@ -86,6 +81,7 @@ dependencies {
implementation(libs.firebase.analytics)
implementation(libs.firebase.ui.auth)
implementation(libs.firebase.firestore)
implementation(libs.gms.google.services)
implementation(libs.gms.play.services.mlkit.image.labeling)
implementation(libs.google.accompanist.pager)
implementation(libs.google.accompanist.pager.indicators)
Expand All @@ -99,6 +95,7 @@ dependencies {
implementation(libs.okhttp)
implementation(libs.play.services.auth)
implementation(libs.play.services.base)
implementation(libs.play.services.identity)
implementation(libs.play.services.mlkit.text.recognition)
implementation(libs.play.services.mlkit.text.recognition.common)
implementation(libs.text.recognition)
Expand Down
7 changes: 6 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}
}
-keepclassmembers class com.freshkeeper.screens.home.viewmodel.FoodItem {
public <init>();
}
-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.**
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools" package="com.freshkeeper">

<uses-feature
android:name="android.hardware.camera"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,132 @@
package com.freshkeeper.screens.authentication

import android.content.Context
import android.util.Log
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.credentials.Credential
import androidx.credentials.CustomCredential
import androidx.fragment.app.FragmentActivity
import androidx.navigation.NavController
import com.freshkeeper.ERROR_TAG
import com.freshkeeper.R
import com.freshkeeper.UNEXPECTED_CREDENTIAL
import com.freshkeeper.model.service.AccountService
import com.freshkeeper.screens.AppViewModel
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential.Companion.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import kotlin.coroutines.resume

@HiltViewModel
class GoogleViewModel
@Inject
constructor(
private val accountService: AccountService,
) : AppViewModel() {
private val _errorMessage = MutableStateFlow<Int?>(null)

fun onSignInWithGoogle(
credential: Credential,
navController: NavController,
context: Context,
activity: FragmentActivity,
) {
launchCatching {
if (credential is CustomCredential &&
credential.type ==
TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
) {
val googleIdTokenCredential =
GoogleIdTokenCredential.createFrom(
credential.data,
)
accountService.signInWithGoogle(googleIdTokenCredential.idToken)
navController.navigate("home") { launchSingleTop = true }
} else {
Log.e(ERROR_TAG, UNEXPECTED_CREDENTIAL)
if (isBiometricEnabled(context)) {
launchCatching {
val authenticated = authenticateBiometric(context, activity, credential)
if (authenticated) {
navController.navigate("home") { launchSingleTop = true }
} else {
_errorMessage.value = R.string.biometric_auth_failed
}
}
} else {
launchCatching {
if (credential is CustomCredential &&
credential.type ==
TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
) {
val googleIdTokenCredential =
GoogleIdTokenCredential.createFrom(
credential.data,
)
accountService.signInWithGoogle(googleIdTokenCredential.idToken)
navController.navigate("home") { launchSingleTop = true }
} else {
Log.e(ERROR_TAG, UNEXPECTED_CREDENTIAL)
}
}
}
}

private fun isBiometricEnabled(context: Context): Boolean {
val sharedPreferences =
context.getSharedPreferences(
"user_preferences",
Context.MODE_PRIVATE,
)
return sharedPreferences.getBoolean("biometric_enabled", false)
}

private suspend fun authenticateBiometric(
context: Context,
activity: FragmentActivity,
credential: Credential?,
): Boolean =
suspendCancellableCoroutine { continuation ->
val executor = ContextCompat.getMainExecutor(context)
val biometricPrompt =
BiometricPrompt(
activity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt
.AuthenticationResult,
) {
if (credential is CustomCredential &&
credential.type
== TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
) {
val googleIdTokenCredential =
GoogleIdTokenCredential.createFrom(credential.data)
launchCatching {
accountService.signInWithGoogle(
googleIdTokenCredential
.idToken,
)
continuation.resume(true)
}
} else {
continuation.resume(false)
}
}

override fun onAuthenticationFailed() {
continuation.resume(false)
}

override fun onAuthenticationError(
errorCode: Int,
errString: CharSequence,
) {
continuation.resume(false)
}
},
)

val promptInfo =
BiometricPrompt.PromptInfo
.Builder()
.setTitle("Biometrische Authentifizierung")
.setSubtitle("Bitte authentifizieren Sie sich, um fortzufahren")
.setNegativeButtonText("Abbrechen")
.build()

biometricPrompt.authenticate(promptInfo)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ 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.fragment.app.FragmentActivity
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.freshkeeper.R
Expand All @@ -47,10 +48,11 @@ fun SignInScreen(
googleViewModel: GoogleViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val activity = context as FragmentActivity

LaunchedEffect(Unit) {
launchCredManBottomSheet(context) { result ->
googleViewModel.onSignInWithGoogle(result, navController)
googleViewModel.onSignInWithGoogle(result, navController, context, activity)
}
}

Expand Down Expand Up @@ -118,7 +120,12 @@ fun SignInScreen(
Spacer(Modifier.padding(4.dp))

AuthenticationButton(buttonText = R.string.sign_in_with_google) { credential ->
googleViewModel.onSignInWithGoogle(credential, navController)
googleViewModel.onSignInWithGoogle(
credential,
navController,
context,
activity,
)
}

Spacer(Modifier.padding(8.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.fragment.app.FragmentActivity
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.freshkeeper.R
Expand All @@ -54,6 +55,7 @@ fun SignUpScreen(
googleViewModel: GoogleViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val activity = context as FragmentActivity

FreshKeeperTheme {
Scaffold {
Expand Down Expand Up @@ -124,7 +126,12 @@ fun SignUpScreen(
Spacer(Modifier.padding(4.dp))

AuthenticationButton(R.string.sign_up_with_google) { credential ->
googleViewModel.onSignInWithGoogle(credential, navController)
googleViewModel.onSignInWithGoogle(
credential,
navController,
context,
activity,
)
}

Spacer(Modifier.padding(8.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ class SignUpViewModel
activity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
override fun onAuthenticationSucceeded(
result: BiometricPrompt
.AuthenticationResult,
) {
continuation.resume(true)
}

Expand Down
49 changes: 24 additions & 25 deletions app/src/main/java/com/freshkeeper/screens/home/ExpiryDateAnalyis.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.freshkeeper.screens.home

import java.time.LocalDate
import java.util.Locale
import java.time.ZoneOffset

fun getMaxDaysOfMonth(
month: Int,
year: Int,
): Int = LocalDate.of(year, month, 1).lengthOfMonth()

fun isValidDate(date: String): Boolean {
val parts = date.split("-", "/", ".")
Expand All @@ -27,7 +32,8 @@ fun isValidDate(date: String): Boolean {
val year = parts[1].toInt()
val fullYear = if (year < 100) year + 2000 else year

val parsedExpiryDate = LocalDate.of(fullYear, month, 1)
val maxDays = getMaxDaysOfMonth(month, fullYear)
val parsedExpiryDate = LocalDate.of(fullYear, month, maxDays)
val currentDate = LocalDate.now()
val tenYearsLater = currentDate.plusYears(10)

Expand All @@ -42,34 +48,27 @@ fun isValidDate(date: String): Boolean {
}
}

fun formatDate(date: String): String {
fun convertToUnixTimestamp(date: String): Long {
val parts = date.split("-", "/", ".")
val locale = Locale.ROOT
return when (parts.size) {
3 -> {
val year =
if (parts[2].length == 2) {
"20${parts[2]}"
} else {
parts[2]
}
String.format(
locale,
"%02d.%02d.%04d",
parts[0].toInt(),
parts[1].toInt(),
year.toInt(),
)
val day = parts[0].toInt()
val month = parts[1].toInt()
val year = parts[2].toInt()
val fullYear = if (year < 100) year + 2000 else year

val parsedExpiryDate = LocalDate.of(fullYear, month, day)
parsedExpiryDate.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()
}
2 -> {
val year =
if (parts[1].length == 2) {
"20${parts[1]}"
} else {
parts[1]
}
String.format(locale, "%02d.%02d.%04d", 31, parts[0].toInt(), year.toInt())
val month = parts[0].toInt()
val year = parts[1].toInt()
val fullYear = if (year < 100) year + 2000 else year

val maxDays = getMaxDaysOfMonth(month, fullYear)
val parsedExpiryDate = LocalDate.of(fullYear, month, maxDays)
parsedExpiryDate.atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()
}
else -> date
else -> 0L
}
}
15 changes: 11 additions & 4 deletions app/src/main/java/com/freshkeeper/screens/home/ExpiryDatePicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,18 @@ import java.util.Locale
@Composable
fun ExpiryDatePicker(
expiryDate: Long?,
onDateChange: (Long?) -> Unit,
modifier: Modifier = Modifier,
) {
var selectedDate by remember { mutableStateOf(expiryDate) }
var showModal by remember { mutableStateOf(false) }
val currentDate = System.currentTimeMillis()

OutlinedTextField(
value = selectedDate?.let { convertMillisToDate(it) } ?: "DD.MM.YYYY",
value = selectedDate?.let { convertMillisToDate(it) } ?: convertMillisToDate(currentDate),
onValueChange = { },
label = { Text(stringResource(R.string.expiry_date)) },
placeholder = { Text("DD.MM.YYYY") },
placeholder = { Text(convertMillisToDate(currentDate)) },
trailingIcon = {
Icon(Icons.Default.DateRange, contentDescription = stringResource(R.string.select_date))
},
Expand All @@ -76,7 +78,11 @@ fun ExpiryDatePicker(

if (showModal) {
DatePickerModal(
onDateSelected = { selectedDate = it },
initialDate = selectedDate ?: currentDate,
onDateSelected = { date ->
selectedDate = date
onDateChange(date)
},
onDismiss = { showModal = false },
)
}
Expand All @@ -91,10 +97,11 @@ fun convertMillisToDate(millis: Long): String {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DatePickerModal(
initialDate: Long?,
onDateSelected: (Long?) -> Unit,
onDismiss: () -> Unit,
) {
val datePickerState = rememberDatePickerState()
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = initialDate)

DatePickerDialog(
modifier = Modifier.size(400.dp, 550.dp),
Expand Down
Loading

0 comments on commit ed25706

Please sign in to comment.