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..b9f3d2e9
--- /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.name) {
+ 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 e82043bb..ad593088 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
@@ -22,6 +23,7 @@ class PreferencesRepository {
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(
@@ -213,5 +215,28 @@ class PreferencesRepository {
return context.getSharedPreferences(sharedPrefs, Context.MODE_PRIVATE)
.edit().putBoolean(keyMonochromeIcons, monochromeIconsEnabled).apply()
}
+
+ 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().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 cc688a3b..e28133ba 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
@@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
+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
@@ -100,4 +102,9 @@ class MainActivity : ComponentActivity() {
}
}
+ override fun attachBaseContext(newBase: Context) {
+ val locale = PreferencesRepository.getLanguage(newBase)
+ super.attachBaseContext(LocaleHelper.setLocale(newBase, locale))
+ }
+
}
\ No newline at end of file
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 d3196d60..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
@@ -75,6 +80,9 @@ fun SettingsScreen(
var isDateFormatDialogOpened by remember {
mutableStateOf(false)
}
+ var isLanguagePickerDialogOpened by remember {
+ mutableStateOf(false)
+ }
val notificationTimeHour =
prefsViewModel?.getNotificationTimeHour(context)?.collectAsState()?.value
@@ -117,6 +125,14 @@ fun SettingsScreen(
}
)
}
+ prefsViewModel?.let {
+ LanguagePickerDialog(
+ isDialogOpen = isLanguagePickerDialogOpened,
+ onDismiss = {
+ isLanguagePickerDialogOpened = false
+ }
+ )
+ }
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
@@ -301,6 +317,31 @@ fun SettingsScreen(
}
)
}
+
+ 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/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 06b6cd71..a4cfe6f0 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -110,6 +110,10 @@
- Colori dinamici
Scanner di codici a barre
+ Applica
+ Seleziona lingua
+ Opzioni di debug
+ Lingua
- Giorni
- Mesi
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 1e742422..db2ee4b8 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -109,4 +109,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 d25dc5c0..a93ccf68 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -112,6 +112,10 @@
Barcode scanner
Made with ❤️
+ Apply
+ Select language
+ Debug Options
+ Language
- Days
- Months