Skip to content

Commit

Permalink
✨ Add language selection in debug mode
Browse files Browse the repository at this point in the history
Added the ability to select the language of the app.

- Added a new dialog to select the language.
- Added a new preference to store the selected language.
- Added a new helper class to change the locale of the app.
- Updated the worker to use the selected language.
- Updated the settings screen to add a new option to open the language picker dialog.
  • Loading branch information
lorenzovngl committed Dec 15, 2024
1 parent 140a497 commit 9c9013d
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -100,4 +102,9 @@ class MainActivity : ComponentActivity() {
}
}

override fun attachBaseContext(newBase: Context) {
val locale = PreferencesRepository.getLanguage(newBase)
super.attachBaseContext(LocaleHelper.setLocale(newBase, locale))
}

}
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ 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
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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -117,6 +125,14 @@ fun SettingsScreen(
}
)
}
prefsViewModel?.let {
LanguagePickerDialog(
isDialogOpen = isLanguagePickerDialogOpened,
onDismiss = {
isLanguagePickerDialogOpened = false
}
)
}
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
Expand Down Expand Up @@ -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
)
)
}
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
<item>Colori dinamici</item>
</string-array>
<string name="screenshot_titles_barcode_scanner">Scanner di codici a barre</string>
<string name="apply">Applica</string>
<string name="select_language">Seleziona lingua</string>
<string name="debug_options">Opzioni di debug</string>
<string name="language">Lingua</string>
<string-array name="time_span">
<item>Giorni</item>
<item>Mesi</item>
Expand Down
Loading

0 comments on commit 9c9013d

Please sign in to comment.