diff --git a/README.md b/README.md
index 91a5ecce..e7a8ba22 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,7 @@ This simple app helps you avoid forgetting to consume foods that are about to ex
+
## ❤️ Support
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 037a3c7b..95b28b27 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -26,14 +26,14 @@ try {
android {
namespace = "com.lorenzovainigli.foodexpirationdates"
- compileSdk = 34
+ compileSdk = 35
defaultConfig {
applicationId = "com.lorenzovainigli.foodexpirationdates"
minSdk = 24
- targetSdk = 34
- versionCode = 33
- versionName = "2.4.2"
+ targetSdk = 35
+ versionCode = 35
+ versionName = "2.5.1"
base.archivesName.set("FoodExpirationDates-$versionName")
@@ -138,6 +138,10 @@ dependencies {
implementation(libs.androidx.navigation.compose)
testImplementation(libs.junit)
testImplementation(libs.junit.jupiter)
+ testImplementation(libs.mockito.core)
+ testImplementation(libs.mockito.kotlin)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.mockk)
androidTestImplementation(libs.test.core.ktx)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.espresso.core)
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/Contributor.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/Contributor.kt
index 044bfbe9..5277a57a 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/Contributor.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/Contributor.kt
@@ -43,4 +43,5 @@ val contributors = listOf(
Contributor(name = "Maha Rajan", username = "Maha-Rajan"),
Contributor(name = "Anurag Kanojiya", username = "anuragkanojiya1"),
Contributor(name = "Prakash Irom", username = "PrakashIrom"),
+ Contributor(name = "serAKL16lysA", username = "serAKL16lysA"),
)
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/LocaleHelper.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/LocaleHelper.kt
new file mode 100644
index 00000000..974ea84b
--- /dev/null
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/LocaleHelper.kt
@@ -0,0 +1,60 @@
+package com.lorenzovainigli.foodexpirationdates.model
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.res.Configuration
+import com.lorenzovainigli.foodexpirationdates.view.MainActivity
+import java.util.Locale
+import kotlin.jvm.java
+
+enum class Language(val code: String, val label: String) {
+ SYSTEM("system", "System"),
+ ARABIC("ar", "العربية"),
+ CHINESE_TRADITIONAL("zh-TW", "繁體中文"),
+ ENGLISH("en", "English"),
+ FRENCH("fr", "Français"),
+ GERMAN("de", "Deutsch"),
+ HINDI("hi", "हिन्दी"),
+ INDONESIAN("id", "Bahasa Indonesia"),
+ ITALIAN("it", "Italiano"),
+ JAPANESE("ja", "日本語"),
+ POLISH("pl", "Polski"),
+ RUSSIAN("ru", "Русский"),
+ SPANISH("es", "Español"),
+ TAMIL("ta", "தமிழ்"),
+ TURKISH("tr", "Türkçe"),
+ VIETNAMESE("vi", "Tiếng Việt");
+
+ companion object {
+ fun fromCode(code: String): Language {
+ return Language.entries.find { it.code == code } ?: SYSTEM
+ }
+ }
+}
+
+object LocaleHelper {
+
+ fun setLocale(context: Context, language: String): Context {
+ val locale = if (language == Language.SYSTEM.code) {
+ Locale.getDefault()
+ } else {
+ Locale(language)
+ }
+ Locale.setDefault(locale)
+
+ val config = Configuration(context.resources.configuration)
+ config.setLocale(locale)
+ config.setLayoutDirection(locale)
+
+ return context.createConfigurationContext(config)
+ }
+
+ fun changeLanguage(context: Context, newLanguage: String) {
+ setLocale(context, newLanguage)
+ val intent = Intent(context, MainActivity::class.java) // or current activity
+ intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ context.startActivity(intent)
+ (context as Activity).finish()
+ }
+}
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepository.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepository.kt
index 900dbce3..1e855065 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepository.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepository.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.view.Window
import android.view.WindowManager
import com.lorenzovainigli.foodexpirationdates.R
+import com.lorenzovainigli.foodexpirationdates.model.Language
import java.lang.Exception
import java.text.DateFormat
import java.text.SimpleDateFormat
@@ -21,6 +22,8 @@ class PreferencesRepository {
const val keyThemeMode = "theme_mode"
const val keyTopBarFont = "top_bar_font"
const val keyDynamicColors = "dynamic_colors"
+ const val keyMonochromeIcons = "monochrome_icons"
+ const val keyLanguage = "language"
private val availLocaleDateFormats = arrayOf(DateFormat.SHORT, DateFormat.MEDIUM)
private val availOtherDateFormats =
arrayOf(
@@ -174,8 +177,8 @@ class PreferencesRepository {
sharedPrefs: String = sharedPrefsName,
): Boolean {
try {
- return context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
- .getBoolean(keyDynamicColors, false)
+ return context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
+ .getBoolean(keyDynamicColors, false)
} catch (e: Exception){
e.printStackTrace()
}
@@ -186,9 +189,64 @@ class PreferencesRepository {
context: Context,
sharedPrefs: String = sharedPrefsName,
dynamicColorsEnabled: Boolean
+ ): Boolean {
+ try {
+ context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
+ .edit().putBoolean(keyDynamicColors, dynamicColorsEnabled).apply()
+ return true
+ } catch (_: Exception){
+ return false
+ }
+ }
+
+ fun getMonochromeIcons(
+ context: Context,
+ sharedPrefs: String = sharedPrefsName,
+ ): Boolean {
+ try {
+ return context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
+ .getBoolean(keyMonochromeIcons, true)
+ } catch (e: Exception){
+ e.printStackTrace()
+ }
+ return true
+ }
+
+ fun setMonochromeIcons(
+ context: Context,
+ sharedPrefs: String = sharedPrefsName,
+ monochromeIconsEnabled: Boolean
+ ): Boolean {
+ try {
+ context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
+ .edit().putBoolean(keyMonochromeIcons, monochromeIconsEnabled).apply()
+ return true
+ } catch (_: Exception){
+ return false
+ }
+ }
+
+ fun getLanguage(
+ context: Context,
+ sharedPrefs: String = sharedPrefsName,
+ ): String {
+ try {
+ return context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
+ .getString(keyLanguage, Language.SYSTEM.code)
+ ?: Language.SYSTEM.code
+ } catch (e: Exception){
+ e.printStackTrace()
+ }
+ return Language.SYSTEM.code
+ }
+
+ fun setLanguage(
+ context: Context,
+ sharedPrefs: String = sharedPrefsName,
+ language: String
) {
return context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
- .edit().putBoolean(keyDynamicColors, dynamicColorsEnabled).apply()
+ .edit().putString(keyLanguage, language).apply()
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt
index 386f5070..e2409308 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/model/worker/CheckExpirationsWorker.kt
@@ -4,10 +4,13 @@ import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
+import com.lorenzovainigli.foodexpirationdates.BuildConfig
import com.lorenzovainigli.foodexpirationdates.R
+import com.lorenzovainigli.foodexpirationdates.model.LocaleHelper
import com.lorenzovainigli.foodexpirationdates.model.NotificationManager.Companion.CHANNEL_REMINDERS_ID
import com.lorenzovainigli.foodexpirationdates.model.entity.computeExpirationDate
import com.lorenzovainigli.foodexpirationdates.model.repository.ExpirationDateRepository
+import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository
import com.lorenzovainigli.foodexpirationdates.showNotification
import kotlinx.coroutines.flow.first
import java.util.Calendar
@@ -57,10 +60,16 @@ class CheckExpirationsWorker @Inject constructor(
var message = ""
if (sb.toString().length > 2)
message = sb.toString().substring(0, sb.toString().length - 2) + "."
+ val context = if (BuildConfig.DEBUG) {
+ LocaleHelper.setLocale(
+ context = applicationContext,
+ language = PreferencesRepository.getLanguage(applicationContext)
+ )
+ } else applicationContext
showNotification(
- context = applicationContext,
+ context = context,
channelId = CHANNEL_REMINDERS_ID,
- title = applicationContext.getString(R.string.your_food_is_expiring),
+ title = context.getString(R.string.your_food_is_expiring),
message = message
)
return Result.success()
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt
index 3bc1e6ad..fe652c64 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/MainActivity.kt
@@ -1,5 +1,6 @@
package com.lorenzovainigli.foodexpirationdates.view
+import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -17,9 +18,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.core.view.WindowCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
+import com.lorenzovainigli.foodexpirationdates.BuildConfig
+import com.lorenzovainigli.foodexpirationdates.model.LocaleHelper
import com.lorenzovainigli.foodexpirationdates.model.NotificationManager
import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository
import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository.Companion.checkAndSetSecureFlags
@@ -37,7 +39,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- WindowCompat.setDecorFitsSystemWindows(window, false)
+ enableEdgeToEdge()
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { viewModel.isSplashScreenLoading.value }
@@ -105,4 +107,13 @@ class MainActivity : ComponentActivity() {
}
}
+ override fun attachBaseContext(newBase: Context) {
+ if (BuildConfig.DEBUG) {
+ val locale = PreferencesRepository.getLanguage(newBase)
+ super.attachBaseContext(LocaleHelper.setLocale(newBase, locale))
+ } else {
+ super.attachBaseContext(newBase)
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt
index 5500602d..e85a6917 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/FoodCard.kt
@@ -64,6 +64,7 @@ fun FoodCard(
) {
val context = LocalContext.current
val dateFormat = PreferencesRepository.getUserDateFormat(context)
+ val monochromeIcons = PreferencesRepository.getMonochromeIcons(context)
val sdf = SimpleDateFormat(dateFormat, context.resources.configuration.locales[0])
val today = Calendar.getInstance()
val twoDaysAgo = Calendar.getInstance().apply {
@@ -144,10 +145,12 @@ fun FoodCard(
.size(36.dp)
.alpha(0.8f)
) {
- drawImage(
- image = imageBitmap,
- colorFilter = ColorFilter.tint(color, BlendMode.Color)
- )
+ if (monochromeIcons) {
+ drawImage(
+ image = imageBitmap,
+ colorFilter = ColorFilter.tint(color, BlendMode.Color)
+ )
+ }
drawImage(
image = imageBitmap,
blendMode = BlendMode.DstAtop
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/LanguagePickerDialog.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/LanguagePickerDialog.kt
new file mode 100644
index 00000000..43bc5786
--- /dev/null
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/LanguagePickerDialog.kt
@@ -0,0 +1,121 @@
+package com.lorenzovainigli.foodexpirationdates.view.composable
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+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.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.lorenzovainigli.foodexpirationdates.R
+import com.lorenzovainigli.foodexpirationdates.model.Language
+import com.lorenzovainigli.foodexpirationdates.model.LocaleHelper
+import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository
+import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme
+
+@Composable
+fun LanguagePickerDialog(
+ isDialogOpen: Boolean = true,
+ onDismiss: () -> Unit = {}
+) {
+ if (isDialogOpen) {
+ val context = LocalContext.current
+ val storedLanguage = PreferencesRepository.getLanguage(context)
+ var selectedLanguage = remember {
+ mutableStateOf(storedLanguage)
+ }
+ Dialog(
+ onDismissRequest = onDismiss
+ ) {
+ Card(
+ shape = RoundedCornerShape(10.dp),
+ elevation = CardDefaults.cardElevation(
+ defaultElevation = 8.dp
+ )
+ ) {
+ Column(
+ modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
+ ) {
+ Text(
+ modifier = Modifier.padding(4.dp),
+ text = stringResource(R.string.select_language),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ Column(
+ modifier = Modifier
+ .height(480.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Language.entries.forEach { language ->
+ Row(
+ modifier = Modifier.fillMaxWidth(1f).clickable(
+ onClick = {
+ selectedLanguage.value = language.code
+ }),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RadioButton(
+ selected = selectedLanguage.value == language.code,
+ onClick = {
+ selectedLanguage.value = language.code
+ }
+ )
+ Text(
+ text = language.label,
+ color = if (selectedLanguage.value == language.code)
+ MaterialTheme.colorScheme.primary
+ else Color.Unspecified
+ )
+ }
+ }
+ }
+ Button(
+ modifier = Modifier.align(Alignment.End),
+ onClick = {
+ LocaleHelper.changeLanguage(context, selectedLanguage.value)
+ PreferencesRepository.setLanguage(
+ context,
+ language = selectedLanguage.value
+ )
+ }
+ ) {
+ Text(stringResource(R.string.apply))
+ }
+ }
+ }
+ }
+ }
+}
+
+@PreviewLightDark
+@Composable
+fun LanguagePickerDialogPreview() {
+ FoodExpirationDatesTheme {
+ Surface {
+ LanguagePickerDialog()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt
index ee990038..21fb698b 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/view/composable/screen/SettingsScreen.kt
@@ -5,6 +5,7 @@ import android.os.Build
import android.util.Log
import android.view.WindowManager
import androidx.annotation.RequiresApi
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -12,6 +13,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
@@ -37,7 +39,9 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
import androidx.compose.ui.unit.dp
+import com.lorenzovainigli.foodexpirationdates.BuildConfig
import com.lorenzovainigli.foodexpirationdates.R
+import com.lorenzovainigli.foodexpirationdates.model.Language
import com.lorenzovainigli.foodexpirationdates.model.NotificationManager
import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository
import com.lorenzovainigli.foodexpirationdates.model.repository.PreferencesRepository.Companion.getScreenProtectionEnabled
@@ -46,6 +50,7 @@ import com.lorenzovainigli.foodexpirationdates.ui.theme.FoodExpirationDatesTheme
import com.lorenzovainigli.foodexpirationdates.view.MainActivity
import com.lorenzovainigli.foodexpirationdates.view.composable.AutoResizedText
import com.lorenzovainigli.foodexpirationdates.view.composable.DateFormatDialog
+import com.lorenzovainigli.foodexpirationdates.view.composable.LanguagePickerDialog
import com.lorenzovainigli.foodexpirationdates.view.composable.NotificationTimeBottomSheet
import com.lorenzovainigli.foodexpirationdates.view.composable.SettingsItem
import com.lorenzovainigli.foodexpirationdates.view.preview.LanguagePreviews
@@ -64,6 +69,8 @@ fun SettingsScreen(
?: PreferencesRepository.Companion.ThemeMode.SYSTEM.ordinal
val dynamicColorsState = prefsViewModel?.getDynamicColors(context)?.collectAsState()?.value
?: false
+ val monochromeIconsState = prefsViewModel?.getMonochromeIcons(context)?.collectAsState()?.value
+ ?: true
val topBarFontState = prefsViewModel?.getTopBarFont(context)?.collectAsState()?.value
?: PreferencesRepository.Companion.TopBarFont.NORMAL.ordinal
@@ -73,6 +80,9 @@ fun SettingsScreen(
var isDateFormatDialogOpened by remember {
mutableStateOf(false)
}
+ var isLanguagePickerDialogOpened by remember {
+ mutableStateOf(false)
+ }
val notificationTimeHour =
prefsViewModel?.getNotificationTimeHour(context)?.collectAsState()?.value
@@ -115,6 +125,14 @@ fun SettingsScreen(
}
)
}
+ prefsViewModel?.let {
+ LanguagePickerDialog(
+ isDialogOpen = isLanguagePickerDialogOpened,
+ onDismiss = {
+ isLanguagePickerDialogOpened = false
+ }
+ )
+ }
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
@@ -284,6 +302,46 @@ fun SettingsScreen(
)
}
}
+ SettingsItem(
+ label = stringResource(R.string.monochrome_icons)
+ ) {
+ Spacer(
+ Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ )
+ Switch(
+ checked = monochromeIconsState,
+ onCheckedChange = {
+ prefsViewModel?.setMonochromeIcons(context, it)
+ }
+ )
+ }
+
+ if (BuildConfig.DEBUG) {
+ Text(
+ text = stringResource(R.string.debug_options),
+ style = MaterialTheme.typography.labelLarge
+ )
+ SettingsItem(
+ label = stringResource(R.string.language)
+ ) {
+ Spacer(
+ Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ )
+ BasicText(
+ modifier = Modifier.clickable {
+ isLanguagePickerDialogOpened = true
+ },
+ text = Language.fromCode(PreferencesRepository.getLanguage(context)).label,
+ style = MaterialTheme.typography.headlineSmall.copy(
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ )
+ }
+ }
}
}
diff --git a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/PreferencesViewModel.kt b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/PreferencesViewModel.kt
index 813122cc..a5c1398b 100644
--- a/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/PreferencesViewModel.kt
+++ b/app/src/main/java/com/lorenzovainigli/foodexpirationdates/viewmodel/PreferencesViewModel.kt
@@ -27,6 +27,8 @@ class PreferencesViewModel @Inject constructor(): ViewModel() {
private var themeMode = _themeMode.asStateFlow()
private var _dynamicColors = MutableStateFlow(false)
private var dynamicColors = _dynamicColors.asStateFlow()
+ private var _monochromeIcons = MutableStateFlow(true)
+ private var monochromeIcons = _monochromeIcons.asStateFlow()
private var _topBarFont = MutableStateFlow(0)
private var topbarFont = _topBarFont.asStateFlow()
@@ -124,4 +126,20 @@ class PreferencesViewModel @Inject constructor(): ViewModel() {
_dynamicColors.value = colors
}
+ fun getMonochromeIcons(context: Context): StateFlow {
+ viewModelScope.launch {
+ _monochromeIcons.value = PreferencesRepository.getMonochromeIcons(context)
+ }
+ return monochromeIcons
+ }
+
+ fun setMonochromeIcons(context: Context, icons: Boolean) {
+ viewModelScope.launch {
+ PreferencesRepository.setMonochromeIcons(
+ context = context,
+ monochromeIconsEnabled = icons
+ )
+ }
+ _monochromeIcons.value = icons
+ }
}
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index cfc24212..5ca6e4df 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -21,10 +21,7 @@
Eine Rezension schreiben
Quelltext
Funktionen
- - Anzeige einer Liste mit Lebensmittelverfallsdaten in aufsteigender zeitlicher Reihenfolge
-\n - Einträge hinzufügen, bearbeiten und löschen
-\n - Eröffnungsdatum
-\n - Benachrichtigungen
+ - Anzeige einer Liste mit Lebensmittelverfallsdaten in aufsteigender zeitlicher Reihenfolge \n - Einträge hinzufügen, bearbeiten und löschen \n - Produktabfrage mittels Strichtcodescanner und Open Food Facts API \n - Öffnungsdatum \n - Benachrichtigungen
Dieses Projekt unterstützen
Fehler melden
Version %1$s
@@ -57,7 +54,7 @@
System
Dunkel
%1$s gelöscht
- Abbrechen
+ Rückgängig
Normal
Fett
Extrafett
@@ -104,4 +101,12 @@
Wiederholen
Kontakte
Hergestellt mit ❤️
+ Bitte scannen Sie einen Strichcode
+ Produktinformationen abrufen…
+ Scanfehler
+ Bildschirmschutz aktivieren
+ Schützen Sie Ihren Bildschirm vor Screenshots und Bildschirmaufzeichnungen.
+ Datenschutz
+ Geöffnet
+ Strichcode-Scanner
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index e3d1d1f7..00ffb466 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -61,7 +61,7 @@
Negrita
Negrita extra
Estilo de fuente de la barra superior
- Comportamiento
+ Conducta
Apariencia
Enviar un e-mail
Contactos
@@ -111,4 +111,9 @@
Activar la protección de pantalla
Protege tu pantalla de capturas y grabaciones de pantalla.
Privacidad
+ Idioma
+ Aplicar
+ Opciones de depuración
+ Iconos monocromáticos
+ Selecciona el idioma
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index e1ec40f2..bc359810 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -1,6 +1,6 @@
- Scadenze alimenti
+ Scadenze Alimenti
@string/app_name
%1$d giorni fa
Ieri
@@ -92,7 +92,6 @@
Attiva protezione schermo
Proteggi la tua schermata dagli screenshot e dalla registrazione dello schermo.
Privacy
-
- Uova
- Formaggio
@@ -111,8 +110,14 @@
- Colori dinamici
Scanner di codici a barre
+ Made with ❤️
+ Applica
+ Seleziona lingua
+ Opzioni di debug
+ Lingua
- Giorni
- Mesi
+ Icone monocromatiche
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 1e742422..4770e3ce 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -1,13 +1,14 @@
- Food Expiration Dates
- Food Expirations
+ 食品の期限リマインダー
+ 食品の期限
%1$d日前
昨日
今日
明日
%1$d日後
食品を追加
+ 画面をスクリーンショットや画面録画から保護する。
食品を編集
食品名
賞味期限
@@ -65,6 +66,7 @@
上部バーのフォントスタイル
行動
外観
+ プライバシー
メールを送信
連絡先
このアプリケーションに関する詳細な情報を入手したい場合、またはフィードバックの送信、バグ報告、機能リクエスト、単に開発者に連絡したい場合は、次のボタンを使用してメールを送信してください。\nバグレポートや機能リクエストがある場合は、このアプリケーションのGitHubリポジトリでissueを開くこともできます。
@@ -109,4 +111,11 @@
スキャンエラー
バーコードスキャナー
♥で作られました
+ 適用
+ 言語を選択
+ デバッグオプション
+ 言語
+ 画面保護をオンにする
+ 開封済み
+ モノクロアイコン
\ No newline at end of file
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index da7a41a7..6cb3ac7e 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -107,4 +107,10 @@
Lỗi kết nối
Đang lấy thông tin sản phẩm…
Lỗi quét
+ Đã mở
+ Máy quét mã vạch
+ Làm bằng ❤️
+ Bật bảo vệ màn hình
+ Quyền riêng tư
+ Bảo vệ màn hình của bạn khỏi chụp màn hình và ghi màn hình.
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index daeb9984..0f2ad5af 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -88,4 +88,8 @@
選擇日期格式
語言環境格式
你的食物快過期了!
+ 已開啟的
+ 禁止螢幕擷圖和螢幕錄影以保護您的隱私。
+ 啟用螢幕隱私保護
+ 隱私
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6bf12e85..a93ccf68 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -91,6 +91,7 @@
Retry
Scanning error
Opened
+ Monochrome icons
- Eggs
@@ -111,6 +112,10 @@
Barcode scanner
Made with ❤️
+ Apply
+ Select language
+ Debug Options
+ Language
- Days
- Months
diff --git a/app/src/test/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepositoryTest.kt b/app/src/test/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepositoryTest.kt
new file mode 100644
index 00000000..a871f3b1
--- /dev/null
+++ b/app/src/test/java/com/lorenzovainigli/foodexpirationdates/model/repository/PreferencesRepositoryTest.kt
@@ -0,0 +1,149 @@
+package com.lorenzovainigli.foodexpirationdates.model.repository
+
+import android.content.Context
+import android.content.SharedPreferences
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class PreferencesRepositoryTest {
+
+ @Test
+ fun `getDynamicColors() - SharedPreferences throws exception`() {
+ val context = mockk()
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.getBoolean(any(), any()) } throws RuntimeException("SharedPrefs Error")
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.getDynamicColors(context, "")
+ assertFalse(result)
+ }
+
+ @Test
+ fun `getDynamicColors() - SharedPreferences returns the expected value`() {
+ val context = mockk()
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.getBoolean(any(), any()) } returns true
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.getDynamicColors(context, "")
+ assertTrue(result)
+ }
+
+ @Test
+ fun `getDynamicColors() - Invalid sharedPrefs name`() {
+ val context = mockk()
+ val invalidSharedPrefsName = "invalid_name"
+ every {
+ context.getSharedPreferences(invalidSharedPrefsName, any())
+ } throws RuntimeException("SharedPrefs Error")
+ val result = PreferencesRepository.getDynamicColors(context, invalidSharedPrefsName)
+ assertFalse(result)
+ }
+
+ @Test
+ fun `setDynamicColors() - SharedPreferences throws exception`() {
+ val context = mockk()
+ val sharedPrefsEditor: SharedPreferences.Editor = mockk(relaxed = true)
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.edit() } returns sharedPrefsEditor
+ every { sharedPrefsEditor.putBoolean(any(), any()) } throws RuntimeException("SharedPrefs Error")
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.setDynamicColors(context, "", true)
+ assertFalse(result)
+ }
+
+ @Test
+ fun `setDynamicColors() - SharedPreferences successfully sets the value`() {
+ val context = mockk()
+ val sharedPrefsEditor: SharedPreferences.Editor = mockk(relaxed = true)
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.edit() } returns sharedPrefsEditor
+ every { sharedPrefsEditor.putBoolean(any(), any()) } returns sharedPrefsEditor
+ every { sharedPrefsEditor.apply() } just Runs
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.setDynamicColors(context, "", true)
+ assertTrue(result)
+ }
+
+ @Test
+ fun `setDynamicColors() - Invalid sharedPrefs name`() {
+ val context = mockk()
+ val invalidSharedPrefsName = "invalid_name"
+ every {
+ context.getSharedPreferences(invalidSharedPrefsName, any())
+ } throws RuntimeException("SharedPrefs Error")
+ val result = PreferencesRepository.setDynamicColors(context, invalidSharedPrefsName, true)
+ assertFalse(result)
+ }
+
+ @Test
+ fun `getMonochromeIcons() - SharedPreferences throws exception`() {
+ val context = mockk()
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.getBoolean(any(), any()) } throws RuntimeException("SharedPrefs Error")
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.getMonochromeIcons(context, "")
+ assertTrue(result)
+ }
+
+ @Test
+ fun `getMonochromeIcons() - SharedPreferences returns the expected value`() {
+ val context = mockk()
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.getBoolean(any(), any()) } returns false
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.getMonochromeIcons(context, "")
+ assertFalse(result)
+ }
+
+ @Test
+ fun `getMonochromeIcons() - Invalid sharedPrefs name`() {
+ val context = mockk()
+ val invalidSharedPrefsName = "invalid_name"
+ every {
+ context.getSharedPreferences(invalidSharedPrefsName, any())
+ } throws RuntimeException("SharedPrefs Error")
+ val result = PreferencesRepository.getMonochromeIcons(context, invalidSharedPrefsName)
+ assertTrue(result)
+ }
+
+ @Test
+ fun `setMonochromeIcons() - SharedPreferences throws exception`() {
+ val context = mockk()
+ val sharedPrefsEditor: SharedPreferences.Editor = mockk(relaxed = true)
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.edit() } returns sharedPrefsEditor
+ every { sharedPrefsEditor.putBoolean(any(), any()) } throws RuntimeException("SharedPrefs Error")
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.setMonochromeIcons(context, "", true)
+ assertFalse(result)
+ }
+
+ @Test
+ fun `setMonochromeIcons() - SharedPreferences successfully sets the value`() {
+ val context = mockk()
+ val sharedPrefsEditor: SharedPreferences.Editor = mockk(relaxed = true)
+ val sharedPrefs: SharedPreferences = mockk()
+ every { sharedPrefs.edit() } returns sharedPrefsEditor
+ every { sharedPrefsEditor.putBoolean(any(), any()) } returns sharedPrefsEditor
+ every { sharedPrefsEditor.apply() } just Runs
+ every { context.getSharedPreferences(any(), any()) } returns sharedPrefs
+ val result = PreferencesRepository.setMonochromeIcons(context, "", true)
+ assertTrue(result)
+ }
+
+ @Test
+ fun `setMonochromeIcons() - Invalid sharedPrefs name`() {
+ val context = mockk()
+ val invalidSharedPrefsName = "invalid_name"
+ every {
+ context.getSharedPreferences(invalidSharedPrefsName, any())
+ } throws RuntimeException("SharedPrefs Error")
+ val result = PreferencesRepository.setMonochromeIcons(context, invalidSharedPrefsName, true)
+ assertFalse(result)
+ }
+
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 8b85d736..8c1fc671 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,37 +1,41 @@
[versions]
-activity-compose = "1.9.2"
-agp = "8.7.0"
+activity-compose = "1.9.3"
+agp = "8.7.3"
androidx-test-core-ktx = "1.6.1"
androidx-test-ext-junit = "1.2.1"
-compose-bom = "2024.09.03"
+compose-bom = "2024.12.01"
barcode-scanning = "17.3.0"
-camera-core = "1.3.4"
-camera-mlkit-vision = "1.4.0-rc01"
+camera-core = "1.4.1"
+camera-mlkit-vision = "1.4.1"
coil-compose = "2.7.0"
converter-gson = "2.11.0"
-core-ktx = "1.13.1"
-dagger-hilt = "2.52"
+core-ktx = "1.15.0"
+dagger-hilt = "2.54"
espresso-core = "3.6.1"
-firebase-bom = "33.4.0"
+firebase-bom = "33.7.0"
firebase-crashlytics-gradle = "3.0.2"
google-services = "4.4.2"
-guava = "33.3.1-jre"
+guava = "33.4.0-jre"
hilt-common = "1.2.0"
junit = "4.13.2"
-junit-jupiter = "5.11.2"
-kotlin = "2.0.20"
-ksp = "2.0.20-1.0.25"
-lifecycle-runtime-ktx = "2.8.6"
-navigation-compose = "2.8.2"
+junit-jupiter = "5.11.4"
+kotlin = "2.1.0"
+ksp = "2.1.0-1.0.29"
+lifecycle-runtime-ktx = "2.8.7"
+mockito = "3.12.4"
+mockito-kotlin = "3.2.0"
+mockk = "1.13.5"
+navigation-compose = "2.8.5"
opencsv = "5.9"
-paparazzi = "1.3.4"
+paparazzi = "1.3.5"
retrofit = "2.11.0"
+robolectric = "4.10"
room = "2.6.1"
+splashscreen = "1.0.1"
test-rules = "1.6.1"
test-runner = "1.6.2"
uiautomator = "2.3.0"
-work-runtime-ktx = "2.9.1"
-splashscreen = "1.0.1"
+work-runtime-ktx = "2.10.0"
[libraries]
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
@@ -76,16 +80,20 @@ kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
material3 = { group = "androidx.compose.material3", name = "material3" }
material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
+mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" }
+mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockito-kotlin" }
+mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
opencsv = { module = "com.opencsv:opencsv", version.ref = "opencsv" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" }
+splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splashscreen" }
test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidx-test-core-ktx" }
ui = { group = "androidx.compose.ui", name = "ui" }
ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "splashscreen" }
[plugins]
app-cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi"}