Skip to content

Commit 72304f1

Browse files
nichu42Copilot
andcommitted
feat: add app-wide text scale setting
- Add FontScaleHelper to persist and apply an app-wide text scale factor. - Wrap MainActivity and WidgetConfigActivity compositions in a custom LocalDensity so only text (sp) scales, not layout (dp). - Add a Text Size slider in SettingsScreen (range 80%–200%, default 100%). - Apply changes immediately via activity recreation when the user releases the slider. - Add English and German string resources. No database bump required: scale is stored in SharedPreferences. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fcb4a2a commit 72304f1

6 files changed

Lines changed: 438 additions & 296 deletions

File tree

app/src/main/java/de/nichu42/boxviewer/MainActivity.kt

Lines changed: 283 additions & 278 deletions
Large diffs are not rendered by default.

app/src/main/java/de/nichu42/boxviewer/ui/SettingsScreen.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import androidx.compose.ui.res.stringResource
3333
import de.nichu42.boxviewer.R
3434
import de.nichu42.boxviewer.util.ApiLogger
3535
import de.nichu42.boxviewer.util.CrashHandler
36+
import de.nichu42.boxviewer.util.FontScaleHelper
3637
import de.nichu42.boxviewer.util.LocaleHelper
3738
import kotlinx.coroutines.launch
3839

@@ -192,6 +193,72 @@ fun SettingsScreen(
192193

193194
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
194195

196+
// App-wide text scale
197+
var textScale by remember { mutableFloatStateOf(FontScaleHelper.getSavedTextScale(context)) }
198+
Column(modifier = Modifier.fillMaxWidth()) {
199+
Row(
200+
modifier = Modifier.fillMaxWidth(),
201+
horizontalArrangement = Arrangement.SpaceBetween,
202+
verticalAlignment = Alignment.CenterVertically
203+
) {
204+
Column(modifier = Modifier.weight(1f)) {
205+
Text(
206+
stringResource(R.string.settings_text_scale_label),
207+
style = MaterialTheme.typography.bodyMedium,
208+
fontWeight = FontWeight.Bold
209+
)
210+
Text(
211+
stringResource(R.string.settings_text_scale_description),
212+
style = MaterialTheme.typography.bodySmall,
213+
color = MaterialTheme.colorScheme.onSurfaceVariant
214+
)
215+
}
216+
Text(
217+
text = String.format(java.util.Locale.getDefault(), "%.0f%%", textScale * 100),
218+
style = MaterialTheme.typography.bodyMedium,
219+
fontWeight = FontWeight.Bold,
220+
color = MaterialTheme.colorScheme.primary
221+
)
222+
}
223+
Spacer(modifier = Modifier.height(8.dp))
224+
Slider(
225+
value = textScale,
226+
onValueChange = { textScale = it },
227+
onValueChangeFinished = {
228+
FontScaleHelper.setTextScale(context, textScale)
229+
(context as? android.app.Activity)?.recreate()
230+
},
231+
valueRange = FontScaleHelper.VALUE_RANGE,
232+
modifier = Modifier.fillMaxWidth(),
233+
colors = SliderDefaults.colors(
234+
thumbColor = MaterialTheme.colorScheme.primary,
235+
activeTrackColor = MaterialTheme.colorScheme.primary
236+
)
237+
)
238+
Row(
239+
modifier = Modifier.fillMaxWidth(),
240+
horizontalArrangement = Arrangement.SpaceBetween
241+
) {
242+
Text(
243+
stringResource(R.string.settings_text_scale_compact),
244+
style = MaterialTheme.typography.bodySmall,
245+
color = MaterialTheme.colorScheme.onSurfaceVariant
246+
)
247+
Text(
248+
stringResource(R.string.settings_text_scale_default),
249+
style = MaterialTheme.typography.bodySmall,
250+
color = MaterialTheme.colorScheme.onSurfaceVariant
251+
)
252+
Text(
253+
stringResource(R.string.settings_text_scale_large),
254+
style = MaterialTheme.typography.bodySmall,
255+
color = MaterialTheme.colorScheme.onSurfaceVariant
256+
)
257+
}
258+
}
259+
260+
HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
261+
195262
Row(
196263
modifier = Modifier.fillMaxWidth(),
197264
horizontalArrangement = Arrangement.SpaceBetween,

app/src/main/java/de/nichu42/boxviewer/ui/WidgetConfigActivity.kt

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.material3.Surface
1212
import androidx.compose.ui.Modifier
1313
import de.nichu42.boxviewer.data.db.SenseBoxDatabase
1414
import de.nichu42.boxviewer.data.repository.SenseBoxRepository
15+
import de.nichu42.boxviewer.util.FontScaleHelper
1516
import de.nichu42.boxviewer.util.LocaleHelper
1617

1718
import de.nichu42.boxviewer.ui.theme.MyApplicationTheme
@@ -40,28 +41,31 @@ class WidgetConfigActivity : AppCompatActivity() {
4041

4142
val db = SenseBoxDatabase.getDatabase(applicationContext)
4243
val repository = SenseBoxRepository(applicationContext, db)
44+
val appTextScale = FontScaleHelper.getSavedTextScale(this)
4345

4446
setContent {
4547
MyApplicationTheme(dynamicColor = false) {
46-
Surface(
47-
modifier = Modifier.fillMaxSize(),
48-
color = MaterialTheme.colorScheme.background
49-
) {
50-
WidgetConfigScreen(
51-
repository = repository,
52-
appWidgetId = appWidgetId,
53-
onConfigSaved = {
54-
val resultValue = Intent().apply {
55-
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
48+
FontScaleHelper.ApplyTextScale(scale = appTextScale) {
49+
Surface(
50+
modifier = Modifier.fillMaxSize(),
51+
color = MaterialTheme.colorScheme.background
52+
) {
53+
WidgetConfigScreen(
54+
repository = repository,
55+
appWidgetId = appWidgetId,
56+
onConfigSaved = {
57+
val resultValue = Intent().apply {
58+
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
59+
}
60+
setResult(RESULT_OK, resultValue)
61+
finish()
62+
},
63+
onConfigCancelled = {
64+
setResult(RESULT_CANCELED)
65+
finish()
5666
}
57-
setResult(RESULT_OK, resultValue)
58-
finish()
59-
},
60-
onConfigCancelled = {
61-
setResult(RESULT_CANCELED)
62-
finish()
63-
}
64-
)
67+
)
68+
}
6569
}
6670
}
6771
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package de.nichu42.boxviewer.util
2+
3+
import android.content.Context
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.CompositionLocalProvider
6+
import androidx.compose.runtime.remember
7+
import androidx.compose.ui.platform.LocalDensity
8+
import androidx.compose.ui.unit.Density
9+
10+
/**
11+
* Manages a user-defined app-wide text scale factor persisted in [app_prefs].
12+
*
13+
* This scales only text (`sp`), not layout (`dp`), by wrapping the composition
14+
* in a modified [LocalDensity] that multiplies the system's [fontScale].
15+
*/
16+
object FontScaleHelper {
17+
18+
private const val PREFS_NAME = "app_prefs"
19+
private const val KEY_APP_TEXT_SCALE = "app_text_scale"
20+
21+
/** Default text scale factor. */
22+
const val DEFAULT_SCALE = 1.0f
23+
24+
/** Allowed range for the user-adjustable scale. */
25+
val VALUE_RANGE = 0.8f..2.0f
26+
27+
/** Returns the persisted text scale, or [DEFAULT_SCALE] if none is set. */
28+
fun getSavedTextScale(context: Context): Float {
29+
return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
30+
.getFloat(KEY_APP_TEXT_SCALE, DEFAULT_SCALE)
31+
.coerceIn(VALUE_RANGE)
32+
}
33+
34+
/** Persists the user's text scale choice. */
35+
fun setTextScale(context: Context, scale: Float) {
36+
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
37+
.edit()
38+
.putFloat(KEY_APP_TEXT_SCALE, scale.coerceIn(VALUE_RANGE))
39+
.apply()
40+
}
41+
42+
/**
43+
* Wraps [content] in a [LocalDensity] whose font scale is multiplied by
44+
* [scale]. Use this at the root of the activity composition.
45+
*/
46+
@Composable
47+
fun ApplyTextScale(scale: Float = DEFAULT_SCALE, content: @Composable () -> Unit) {
48+
val currentDensity = LocalDensity.current
49+
val scaledDensity = remember(currentDensity, scale) {
50+
Density(currentDensity.density, currentDensity.fontScale * scale.coerceIn(VALUE_RANGE))
51+
}
52+
CompositionLocalProvider(LocalDensity provides scaledDensity) {
53+
content()
54+
}
55+
}
56+
}

app/src/main/res/values-de/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
<string name="settings_conditional_formatting_description">Färbe Sensorwerte basierend auf ihrem aktuellen Messwert ein.</string>
4848
<string name="settings_language_label">Sprache</string>
4949
<string name="settings_language_description">Wähle die Sprache für die App-Benutzeroberfläche.</string>
50+
<string name="settings_text_scale_label">Textgröße</string>
51+
<string name="settings_text_scale_description">Passe die app-weite Textgröße an. Das kann auf großen Bildschirmen oder im Auto helfen.</string>
52+
<string name="settings_text_scale_compact">80% (Kompakt)</string>
53+
<string name="settings_text_scale_default">100% (Standard)</string>
54+
<string name="settings_text_scale_large">200% (Groß)</string>
5055
<string name="settings_aqi_standard_label">AQI-Standard</string>
5156
<string name="settings_aqi_standard_description">Wähle die Berechnung des Luftqualitätsindex für AQI-Sensoren.</string>
5257
<string name="settings_temperature_unit_label">Temperatureinheit</string>

app/src/main/res/values/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
<string name="settings_conditional_formatting_description">Colorize sensor values based on their current measurement level.</string>
6161
<string name="settings_language_label">Language</string>
6262
<string name="settings_language_description">Choose the language used for the app interface.</string>
63+
<string name="settings_text_scale_label">Text Size</string>
64+
<string name="settings_text_scale_description">Adjust the app-wide text size. This can help on large screens or in cars.</string>
65+
<string name="settings_text_scale_compact">80% (Compact)</string>
66+
<string name="settings_text_scale_default">100% (Default)</string>
67+
<string name="settings_text_scale_large">200% (Large)</string>
6368
<string name="settings_aqi_standard_label">AQI Standard</string>
6469
<string name="settings_aqi_standard_description">Choose the air quality index calculation used for AQI sensors.</string>
6570
<string name="settings_temperature_unit_label">Temperature Unit</string>

0 commit comments

Comments
 (0)