Merge pull request #114 from nsh07/language-selection

Language selection on API 33+
This commit is contained in:
Nishant Mishra
2025-10-31 22:25:18 +05:30
committed by GitHub
9 changed files with 315 additions and 9 deletions

View File

@@ -94,9 +94,8 @@ translating this project into languages you know.
## Download
- **Google Play Store** (recommended): Tomato will soon be available (currently in closed testing)
on the Google Play Store.
[You can find it through this link](https://play.google.com/store/apps/details?id=org.nsh07.pomodoro).
- **Google Play Store** (recommended): Tomato is available on the Google Play Store.
[You can download it through this link](https://play.google.com/store/apps/details?id=org.nsh07.pomodoro).
- **F-Droid** (recommended): Tomato is available on the official F-Droid repository. Simply open
your preferred F-Droid app and search for Tomato. Updates on F-Droid are generally a week late. To
get faster updates, you can install it through
@@ -109,7 +108,7 @@ translating this project into languages you know.
> To [verify](https://developer.android.com/studio/command-line/apksigner#usage-verify) the APK
> downloaded from GitHub, use the following signing certificate fingerprints:
> ```
> SHA1: B1:4E:17:93:11:E8:DB:D5:35:EF:8D:E9:FB:8F:FF:08:F8:EC:65:08
> SHA1: B1:4E:17:93:11:E8:DB:D5:35:EF:8D:E9:FB:8F:FF:08:F8:EC:65:08
> SHA256: 07:BE:F3:05:81:BA:EE:8F:45:EC:93:E4:7E:E6:8E:F2:08:74:E5:0E:F5:70:9C:78:B2:EE:67:AC:86:BE:4C:3D
> ```
> The SHA256 and MD5 hashes of the individual APK files are also available in the `checksum.txt`

View File

@@ -47,6 +47,10 @@ android {
versionName = "1.6.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
androidResources {
generateLocaleConfig = true
}
}
buildTypes {

View File

@@ -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))
}
}
}

View File

@@ -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)) }
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<AppLocale>? = 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
)

View File

@@ -0,0 +1,26 @@
<!--
~ 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 <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#e3e3e3"
android:pathData="M480,880q-82,0 -155,-31.5t-127.5,-86Q143,708 111.5,635T80,480q0,-83 31.5,-155.5t86,-127Q252,143 325,111.5T480,80q83,0 155.5,31.5t127,86q54.5,54.5 86,127T880,480q0,82 -31.5,155t-86,127.5q-54.5,54.5 -127,86T480,880ZM480,798q26,-36 45,-75t31,-83L404,640q12,44 31,83t45,75ZM376,782q-18,-33 -31.5,-68.5T322,640L204,640q29,50 72.5,87t99.5,55ZM584,782q56,-18 99.5,-55t72.5,-87L638,640q-9,38 -22.5,73.5T584,782ZM170,560h136q-3,-20 -4.5,-39.5T300,480q0,-21 1.5,-40.5T306,400L170,400q-5,20 -7.5,39.5T160,480q0,21 2.5,40.5T170,560ZM386,560h188q3,-20 4.5,-39.5T580,480q0,-21 -1.5,-40.5T574,400L386,400q-3,20 -4.5,39.5T380,480q0,21 1.5,40.5T386,560ZM654,560h136q5,-20 7.5,-39.5T800,480q0,-21 -2.5,-40.5T790,400L654,400q3,20 4.5,39.5T660,480q0,21 -1.5,40.5T654,560ZM638,320h118q-29,-50 -72.5,-87T584,178q18,33 31.5,68.5T638,320ZM404,320h152q-12,-44 -31,-83t-45,-75q-26,36 -45,75t-31,83ZM204,320h118q9,-38 22.5,-73.5T376,178q-56,18 -99.5,55T204,320Z" />
</vector>

View File

@@ -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 <https://www.gnu.org/licenses/>.
#
unqualifiedResLocale=en

View File

@@ -87,4 +87,9 @@
<string name="dynamic_color_desc">Adapt theme colors from your wallpaper</string>
<string name="tomato_foss">Tomato FOSS</string>
<string name="tomato_foss_desc">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.</string>
<string name="language">Language</string>
<string name="choose_language">Choose language</string>
<string name="rate_on_google_play">Rate on Google Play</string>
<string name="bmc">BuyMeACoffee</string>
<string name="selected">Selected</string>
</resources>