diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 713c0f0..18ba6ae 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -47,6 +47,10 @@ android {
versionName = "1.6.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ androidResources {
+ generateLocaleConfig = true
+ }
}
buildTypes {
diff --git a/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt b/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt
index 6df4870..0d261bc 100644
--- a/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt
+++ b/app/src/foss/java/org/nsh07/pomodoro/billing/TomatoPlusPaywallDialog.kt
@@ -78,7 +78,7 @@ fun TomatoPlusPaywallDialog(
)
Spacer(Modifier.height(16.dp))
Button(onClick = { uriHandler.openUri("https://coff.ee/nsh07") }) {
- Text("Buy Me A Coffee")
+ Text(stringResource(R.string.bmc))
}
}
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
index 2f8d2dd..e523c16 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/SettingsScreen.kt
@@ -18,8 +18,10 @@
package org.nsh07.pomodoro.ui.settingsScreen
import android.annotation.SuppressLint
+import android.app.LocaleManager
import android.content.Intent
import android.net.Uri
+import android.os.Build
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
@@ -46,8 +48,10 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -68,6 +72,7 @@ import org.nsh07.pomodoro.service.TimerService
import org.nsh07.pomodoro.ui.Screen
import org.nsh07.pomodoro.ui.settingsScreen.components.AboutCard
import org.nsh07.pomodoro.ui.settingsScreen.components.ClickableListItem
+import org.nsh07.pomodoro.ui.settingsScreen.components.LocaleBottomSheet
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusPromo
import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings
import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings
@@ -76,6 +81,7 @@ import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
import org.nsh07.pomodoro.ui.settingsScreens
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
+import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
@@ -182,6 +188,22 @@ private fun SettingsScreen(
val context = LocalContext.current
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
+ val currentLocales =
+ if (Build.VERSION.SDK_INT >= 33) {
+ context
+ .getSystemService(LocaleManager::class.java)
+ .applicationLocales
+ } else null
+ val currentLocalesSize = currentLocales?.size() ?: 0
+
+ var showLocaleSheet by remember { mutableStateOf(false) }
+
+ if (showLocaleSheet && currentLocales != null)
+ LocaleBottomSheet(
+ currentLocales = currentLocales,
+ setShowSheet = { showLocaleSheet = it }
+ )
+
NavDisplay(
backStack = backStack,
onBack = backStack::removeLastOrNull,
@@ -266,6 +288,30 @@ private fun SettingsScreen(
}
item { Spacer(Modifier.height(12.dp)) }
+
+ if (currentLocales != null)
+ item {
+ ClickableListItem(
+ leadingContent = {
+ Icon(
+ painterResource(R.drawable.language),
+ contentDescription = null
+ )
+ },
+ headlineContent = { Text(stringResource(R.string.language)) },
+ supportingContent = {
+ Text(
+ if (currentLocalesSize > 0) currentLocales.get(0).displayName
+ else stringResource(R.string.system_default)
+ )
+ },
+ colors = listItemColors,
+ items = 1,
+ index = 0
+ ) { showLocaleSheet = true }
+ }
+
+ item { Spacer(Modifier.height(12.dp)) }
}
}
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt
index 1f85c76..14028fe 100644
--- a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/AboutCard.kt
@@ -129,11 +129,11 @@ fun AboutCard(
) {
Icon(
painterResource(R.drawable.bmc),
- contentDescription = "Buy me a coffee",
+ contentDescription = null,
modifier = Modifier.height(24.dp)
)
- Text(text = "Buy me a coffee")
+ Text(text = stringResource(R.string.bmc))
}
}
@@ -147,11 +147,11 @@ fun AboutCard(
) {
Icon(
painterResource(R.drawable.play_store),
- contentDescription = "Rate on Google Play",
+ contentDescription = null,
modifier = Modifier.size(20.dp)
)
- Text(text = "Rate on Google Play")
+ Text(text = stringResource(R.string.rate_on_google_play))
}
}
}
diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/LocaleBottomSheet.kt b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/LocaleBottomSheet.kt
new file mode 100644
index 0000000..2872fcf
--- /dev/null
+++ b/app/src/main/java/org/nsh07/pomodoro/ui/settingsScreen/components/LocaleBottomSheet.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2025 Nishant Mishra
+ *
+ * This file is part of Tomato - a minimalist pomodoro timer for Android.
+ *
+ * Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Tomato.
+ * If not, see .
+ */
+
+package org.nsh07.pomodoro.ui.settingsScreen.components
+
+import android.app.LocaleConfig
+import android.app.LocaleManager
+import android.os.Build
+import android.os.LocaleList
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.MaterialTheme.colorScheme
+import androidx.compose.material3.MaterialTheme.shapes
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+import org.nsh07.pomodoro.R
+import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
+import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape
+import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
+import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
+import java.util.Locale
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun LocaleBottomSheet(
+ currentLocales: LocaleList,
+ setShowSheet: (Boolean) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+
+ val supportedLocales = remember {
+ if (Build.VERSION.SDK_INT >= 33) {
+ LocaleConfig(context).supportedLocales
+ } else null
+ }
+ val supportedLocalesSize = supportedLocales?.size() ?: 0
+
+ val supportedLocalesList: List? = remember {
+ if (supportedLocales != null) {
+ buildList {
+ for (i in 0 until supportedLocalesSize) {
+ add(AppLocale(supportedLocales.get(i), supportedLocales.get(i).displayName))
+ }
+ sortWith(compareBy { it.name })
+ }
+ } else null
+ }
+
+ val bottomSheetState = rememberModalBottomSheetState()
+ val listState = rememberLazyListState()
+
+ ModalBottomSheet(
+ onDismissRequest = { setShowSheet(false) },
+ sheetState = bottomSheetState,
+ modifier = modifier
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(
+ text = stringResource(R.string.choose_language),
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ if (supportedLocalesList != null) {
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ state = listState,
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .clip(shapes.largeIncreased)
+ ) {
+ item {
+ ListItem(
+ headlineContent = {
+ Text(stringResource(R.string.system_default))
+ },
+ trailingContent = {
+ if (currentLocales.isEmpty)
+ Icon(
+ painterResource(R.drawable.check),
+ contentDescription = stringResource(R.string.selected)
+ )
+ },
+ colors =
+ if (currentLocales.isEmpty)
+ ListItemDefaults.colors(
+ containerColor = colorScheme.primaryContainer.copy(
+ 0.3f
+ )
+ )
+ else listItemColors,
+ modifier = Modifier
+ .clip(if (currentLocales.isEmpty) CircleShape else shapes.largeIncreased)
+ .clickable(
+ onClick = {
+ scope
+ .launch { bottomSheetState.hide() }
+ .invokeOnCompletion {
+ if (Build.VERSION.SDK_INT >= 33) {
+ context
+ .getSystemService(LocaleManager::class.java)
+ .applicationLocales = LocaleList()
+ }
+ setShowSheet(false)
+ }
+ }
+ )
+ )
+ }
+ item {
+ Spacer(Modifier.height(12.dp))
+ }
+ itemsIndexed(
+ supportedLocalesList,
+ key = { _: Int, it: AppLocale -> it.name }
+ ) { index, it ->
+ ListItem(
+ headlineContent = {
+ Text(it.name)
+ },
+ trailingContent = {
+ if (!currentLocales.isEmpty && it.locale == currentLocales.get(0))
+ Icon(
+ painterResource(R.drawable.check),
+ tint = colorScheme.primary,
+ contentDescription = stringResource(R.string.selected)
+ )
+ },
+ colors =
+ if (!currentLocales.isEmpty && it.locale == currentLocales.get(0))
+ ListItemDefaults.colors(containerColor = colorScheme.primaryContainer)
+ else listItemColors,
+ modifier = Modifier
+ .clip(
+ if (!currentLocales.isEmpty && it.locale == currentLocales.get(0))
+ CircleShape
+ else when (index) {
+ 0 -> topListItemShape
+ supportedLocalesSize - 1 -> bottomListItemShape
+ else -> middleListItemShape
+ }
+ )
+ .clickable(
+ onClick = {
+ scope
+ .launch { bottomSheetState.hide() }
+ .invokeOnCompletion { _ ->
+ if (Build.VERSION.SDK_INT >= 33) {
+ context.getSystemService(LocaleManager::class.java)
+ .applicationLocales =
+ LocaleList(it.locale)
+ }
+ setShowSheet(false)
+ }
+ }
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+data class AppLocale(
+ val locale: Locale,
+ val name: String
+)
\ No newline at end of file
diff --git a/app/src/main/res/drawable/language.xml b/app/src/main/res/drawable/language.xml
new file mode 100644
index 0000000..c1ab95e
--- /dev/null
+++ b/app/src/main/res/drawable/language.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties
new file mode 100644
index 0000000..d694416
--- /dev/null
+++ b/app/src/main/res/resources.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2025 Nishant Mishra
+#
+# This file is part of Tomato - a minimalist pomodoro timer for Android.
+#
+# Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with Tomato.
+# If not, see .
+#
+unqualifiedResLocale=en
\ 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 79d3d99..a608b21 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -87,4 +87,9 @@
Adapt theme colors from your wallpaper
Tomato FOSS
All features are unlocked in this version. If my app made a difference in your life, please consider supporting me by donating on %1$s.
+ Language
+ Choose language
+ Rate on Google Play
+ BuyMeACoffee
+ Selected
\ No newline at end of file