Add german localization (#60)

This commit is contained in:
timklge 2025-08-16 19:56:34 +02:00 committed by GitHub
parent b038641326
commit 4c5b6aac15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 240 additions and 90 deletions

View File

@ -72,7 +72,7 @@ tasks.register("generateManifest") {
"latestVersionCode" to android.defaultConfig.versionCode, "latestVersionCode" to android.defaultConfig.versionCode,
"developer" to "github.com/timklge", "developer" to "github.com/timklge",
"description" to "Open-source extension that adds colored power or heart rate progress bars to the edges of the screen, similar to the LEDs on Wahoo computers", "description" to "Open-source extension that adds colored power or heart rate progress bars to the edges of the screen, similar to the LEDs on Wahoo computers",
"releaseNotes" to "* Add gear data sources\n* Show zero value on bars to indicate sensor availability\n* Fix pedal balance values\n* Add pedal balance data source\n* Add option to split bars", "releaseNotes" to "* Add german localization\n* Add gear data sources\n* Show zero value on bars to indicate sensor availability\n* Fix pedal balance values\n* Add pedal balance data source\n* Add option to split bars",
"screenshotUrls" to listOf( "screenshotUrls" to listOf(
"$baseUrl/powerbar_min.gif", "$baseUrl/powerbar_min.gif",
"$baseUrl/powerbar0.png", "$baseUrl/powerbar0.png",

View File

@ -3,17 +3,17 @@ package de.timklge.karoopowerbar
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
enum class CustomProgressBarSize(val id: String, val label: String, val fontSize: Float, val barHeight: Float) { enum class CustomProgressBarSize(val id: String, val labelResId: Int, val fontSize: Float, val barHeight: Float) {
SMALL("small", "Small", 35f, 10f), SMALL("small", R.string.size_small, 35f, 10f),
MEDIUM("medium", "Medium", 40f, 15f), MEDIUM("medium", R.string.size_medium, 40f, 15f),
LARGE("large", "Large", 60f, 25f), LARGE("large", R.string.size_large, 60f, 25f),
} }
@Serializable @Serializable
enum class CustomProgressBarFontSize(val id: String, val label: String, val fontSize: Float) { enum class CustomProgressBarFontSize(val id: String, val labelResId: Int, val fontSize: Float) {
SMALL("small", "Small", 35f), SMALL("small", R.string.size_small, 35f),
MEDIUM("medium", "Medium", 40f), MEDIUM("medium", R.string.size_medium, 40f),
LARGE("large", "Large", 60f); LARGE("large", R.string.size_large, 60f);
companion object { companion object {
fun fromSize(size: CustomProgressBarSize): CustomProgressBarFontSize { fun fromSize(size: CustomProgressBarSize): CustomProgressBarFontSize {
@ -27,11 +27,11 @@ enum class CustomProgressBarFontSize(val id: String, val label: String, val font
} }
@Serializable @Serializable
enum class CustomProgressBarBarSize(val id: String, val label: String, val barHeight: Float) { enum class CustomProgressBarBarSize(val id: String, val labelResId: Int, val barHeight: Float) {
NONE("none", "None", 0f), NONE("none", R.string.size_none, 0f),
SMALL("small", "Small", 10f), SMALL("small", R.string.size_small, 10f),
MEDIUM("medium", "Medium", 15f), MEDIUM("medium", R.string.size_medium, 15f),
LARGE("large", "Large", 25f); LARGE("large", R.string.size_large, 25f);
companion object { companion object {
fun fromSize(size: CustomProgressBarSize): CustomProgressBarBarSize { fun fromSize(size: CustomProgressBarSize): CustomProgressBarBarSize {

View File

@ -79,7 +79,7 @@ class ForegroundService : Service() {
private fun setupForeground() { private fun setupForeground() {
val channelId = "de.timklge.karoopowerbar" val channelId = "de.timklge.karoopowerbar"
val channelName = "Background Service" val channelName = getString(R.string.notification_channel_name)
val chan = NotificationChannel( val chan = NotificationChannel(
channelId, channelId,
channelName, channelName,
@ -88,14 +88,13 @@ class ForegroundService : Service() {
val manager = val manager =
checkNotNull(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?) checkNotNull(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?)
manager.createNotificationChannel(chan)
val notificationBuilder: NotificationCompat.Builder = manager.createNotificationChannel(chan)
NotificationCompat.Builder(this, channelId) val notificationBuilder = NotificationCompat.Builder(this, channelId)
val notification: Notification = notificationBuilder.setOngoing(true) val notification = notificationBuilder.setOngoing(true)
.setContentTitle("Powerbar service running")
.setContentText("Displaying on top of other apps")
.setSmallIcon(R.drawable.bar) .setSmallIcon(R.drawable.bar)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.notification_text))
.setPriority(NotificationManager.IMPORTANCE_MIN) .setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE) .setCategory(Notification.CATEGORY_SERVICE)
.build() .build()

View File

@ -50,6 +50,7 @@ import androidx.compose.ui.focus.FocusState
import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -77,22 +78,22 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlin.math.roundToInt import kotlin.math.roundToInt
enum class SelectedSource(val id: String, val label: String) { enum class SelectedSource(val id: String, val labelResId: Int) {
NONE("none", "None"), NONE("none", R.string.source_none),
HEART_RATE("hr", "Heart Rate"), HEART_RATE("hr", R.string.source_heart_rate),
POWER("power", "Power"), POWER("power", R.string.source_power),
POWER_3S("power_3s", "Power (3 sec avg)"), POWER_3S("power_3s", R.string.source_power_3s),
POWER_10S("power_10s", "Power (10 sec avg)"), POWER_10S("power_10s", R.string.source_power_10s),
SPEED("speed", "Speed"), SPEED("speed", R.string.source_speed),
SPEED_3S("speed_3s", "Speed (3 sec avg)"), SPEED_3S("speed_3s", R.string.source_speed_3s),
CADENCE("cadence", "Cadence"), CADENCE("cadence", R.string.source_cadence),
CADENCE_3S("cadence_3s", "Cadence (3 sec avg)"), CADENCE_3S("cadence_3s", R.string.source_cadence_3s),
GRADE("grade", "Grade"), GRADE("grade", R.string.source_grade),
POWER_BALANCE("power_balance", "Power Balance"), POWER_BALANCE("power_balance", R.string.source_power_balance),
ROUTE_PROGRESS("route_progress", "Route Progress"), ROUTE_PROGRESS("route_progress", R.string.source_route_progress),
REMAINING_ROUTE("route_progress_remaining", "Route Remaining"), REMAINING_ROUTE("route_progress_remaining", R.string.source_route_remaining),
FRONT_GEAR("front_gear", "Front Gear"), FRONT_GEAR("front_gear", R.string.source_front_gear),
REAR_GEAR("rear_gear", "Rear Gear"); REAR_GEAR("rear_gear", R.string.source_rear_gear);
fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S fun isPower() = this == POWER || this == POWER_3S || this == POWER_10S
} }
@ -121,7 +122,7 @@ fun BarSelectDialog(currentSelectedSource: SelectedSource, onHide: () -> Unit, o
onSelect(pattern) onSelect(pattern)
}) })
Text( Text(
text = pattern.label, text = stringResource(pattern.labelResId),
modifier = Modifier.padding(start = 10.dp) modifier = Modifier.padding(start = 10.dp)
) )
} }
@ -305,7 +306,7 @@ fun MainScreen(onFinish: () -> Unit) {
Column(modifier = Modifier Column(modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background)) { .background(MaterialTheme.colorScheme.background)) {
TopAppBar(title = { Text("Powerbar") }) TopAppBar(title = { Text(stringResource(R.string.powerbar_title)) })
Column(modifier = Modifier Column(modifier = Modifier
.padding(5.dp) .padding(5.dp)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
@ -313,11 +314,11 @@ fun MainScreen(onFinish: () -> Unit) {
if (showAlerts){ if (showAlerts){
if(!karooConnected){ if(!karooConnected){
Text(modifier = Modifier.padding(5.dp), text = "Could not read device status. Is your Karoo updated?") Text(modifier = Modifier.padding(5.dp), text = stringResource(R.string.karoo_connection_error))
} }
if (!givenPermissions) { if (!givenPermissions) {
Text(modifier = Modifier.padding(5.dp), text = "You have not granted the permission to show the power bar overlay. Please do so.") Text(modifier = Modifier.padding(5.dp), text = stringResource(R.string.permission_not_granted))
FilledTonalButton(modifier = Modifier FilledTonalButton(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -325,17 +326,17 @@ fun MainScreen(onFinish: () -> Unit) {
val myIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) val myIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivity(ctx, myIntent, null) startActivity(ctx, myIntent, null)
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Give permission") Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_give_permission))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Give permission") Text(stringResource(R.string.give_permission))
} }
} }
} }
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp)) {
Text("Top Bar", style = MaterialTheme.typography.titleMedium) Text(stringResource(R.string.top_bar), style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text("Split") Text(stringResource(R.string.split))
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Switch(checked = splitTopBar, onCheckedChange = { Switch(checked = splitTopBar, onCheckedChange = {
splitTopBar = it splitTopBar = it
@ -350,9 +351,9 @@ fun MainScreen(onFinish: () -> Unit) {
onClick = { onClick = {
topBarLeftDialogVisible = true topBarLeftDialogVisible = true
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_select), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar (Left): ${topSelectedSourceLeft.label}", modifier = Modifier.weight(1.0f)) Text(stringResource(R.string.top_bar_left, stringResource(topSelectedSourceLeft.labelResId)), modifier = Modifier.weight(1.0f))
} }
if (topBarLeftDialogVisible){ if (topBarLeftDialogVisible){
@ -369,9 +370,9 @@ fun MainScreen(onFinish: () -> Unit) {
onClick = { onClick = {
topBarRightDialogVisible = true topBarRightDialogVisible = true
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_select), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar (Right): ${topSelectedSourceRight.label}", modifier = Modifier.weight(1.0f)) Text(stringResource(R.string.top_bar_right, stringResource(topSelectedSourceRight.labelResId)), modifier = Modifier.weight(1.0f))
} }
if (topBarRightDialogVisible){ if (topBarRightDialogVisible){
@ -388,9 +389,9 @@ fun MainScreen(onFinish: () -> Unit) {
onClick = { onClick = {
topBarDialogVisible = true topBarDialogVisible = true
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_select), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Top Bar: ${topSelectedSource.label}", modifier = Modifier.weight(1.0f)) Text(stringResource(R.string.top_bar_single, stringResource(topSelectedSource.labelResId)), modifier = Modifier.weight(1.0f))
} }
} }
@ -403,9 +404,9 @@ fun MainScreen(onFinish: () -> Unit) {
} }
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp)) {
Text("Bottom Bar", style = MaterialTheme.typography.titleMedium) Text(stringResource(R.string.bottom_bar), style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text("Split") Text(stringResource(R.string.split))
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Switch(checked = splitBottomBar, onCheckedChange = { Switch(checked = splitBottomBar, onCheckedChange = {
splitBottomBar = it splitBottomBar = it
@ -420,9 +421,9 @@ fun MainScreen(onFinish: () -> Unit) {
onClick = { onClick = {
bottomBarLeftDialogVisible = true bottomBarLeftDialogVisible = true
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_select), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Bottom Bar (Left): ${bottomSelectedSourceLeft.label}", modifier = Modifier.weight(1.0f)) Text(stringResource(R.string.bottom_bar_left, stringResource(bottomSelectedSourceLeft.labelResId)), modifier = Modifier.weight(1.0f))
} }
if (bottomBarLeftDialogVisible){ if (bottomBarLeftDialogVisible){
@ -439,9 +440,9 @@ fun MainScreen(onFinish: () -> Unit) {
onClick = { onClick = {
bottomBarRightDialogVisible = true bottomBarRightDialogVisible = true
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_select), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Bottom Bar (Right): ${bottomSelectedSourceRight.label}", modifier = Modifier.weight(1.0f)) Text(stringResource(R.string.bottom_bar_right, stringResource(bottomSelectedSourceRight.labelResId)), modifier = Modifier.weight(1.0f))
} }
if (bottomBarRightDialogVisible){ if (bottomBarRightDialogVisible){
@ -458,9 +459,9 @@ fun MainScreen(onFinish: () -> Unit) {
onClick = { onClick = {
bottomBarDialogVisible = true bottomBarDialogVisible = true
}) { }) {
Icon(Icons.Default.Build, contentDescription = "Select", modifier = Modifier.size(20.dp)) Icon(Icons.Default.Build, contentDescription = stringResource(R.string.content_desc_select), modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text("Bottom Bar: ${bottomSelectedSource.label}", modifier = Modifier.weight(1.0f)) Text(stringResource(R.string.bottom_bar_single, stringResource(bottomSelectedSource.labelResId)), modifier = Modifier.weight(1.0f))
} }
} }
@ -473,22 +474,22 @@ fun MainScreen(onFinish: () -> Unit) {
} }
apply { apply {
val dropdownOptions = CustomProgressBarBarSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } val dropdownOptions = CustomProgressBarBarSize.entries.toList().map { unit -> DropdownOption(unit.id, stringResource(unit.labelResId)) }
val dropdownInitialSelection by remember(barBarSize) { val dropdownInitialSelection by remember(barBarSize) {
mutableStateOf(dropdownOptions.find { option -> option.id == barBarSize.id }!!) mutableStateOf(dropdownOptions.find { option -> option.id == barBarSize.id }!!)
} }
Dropdown(label = "Bar Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption -> Dropdown(label = stringResource(R.string.bar_size), options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
barBarSize = CustomProgressBarBarSize.entries.find { unit -> unit.id == selectedOption.id }!! barBarSize = CustomProgressBarBarSize.entries.find { unit -> unit.id == selectedOption.id }!!
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
} }
} }
apply { apply {
val dropdownOptions = CustomProgressBarFontSize.entries.toList().map { unit -> DropdownOption(unit.id, unit.label) } val dropdownOptions = CustomProgressBarFontSize.entries.toList().map { unit -> DropdownOption(unit.id, stringResource(unit.labelResId)) }
val dropdownInitialSelection by remember(barFontSize) { val dropdownInitialSelection by remember(barFontSize) {
mutableStateOf(dropdownOptions.find { option -> option.id == barFontSize.id }!!) mutableStateOf(dropdownOptions.find { option -> option.id == barFontSize.id }!!)
} }
Dropdown(label = "Text Size", options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption -> Dropdown(label = stringResource(R.string.text_size), options = dropdownOptions, selected = dropdownInitialSelection) { selectedOption ->
barFontSize = CustomProgressBarFontSize.entries.find { unit -> unit.id == selectedOption.id }!! barFontSize = CustomProgressBarFontSize.entries.find { unit -> unit.id == selectedOption.id }!!
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
} }
@ -506,8 +507,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(right = 2.dp) .absolutePadding(right = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { minSpeed = it.filter { c -> c.isDigit() } }, onValueChange = { minSpeed = it.filter { c -> c.isDigit() } },
label = { Text("Min Speed") }, label = { Text(stringResource(R.string.min_speed)) },
suffix = { Text(if (isImperial) "mph" else "kph") }, suffix = { Text(stringResource(if (isImperial) R.string.unit_mph else R.string.unit_kph)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
) )
@ -517,8 +518,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(left = 2.dp) .absolutePadding(left = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { maxSpeed = it.filter { c -> c.isDigit() } }, onValueChange = { maxSpeed = it.filter { c -> c.isDigit() } },
label = { Text("Max Speed") }, label = { Text(stringResource(R.string.max_speed)) },
suffix = { Text(if (isImperial) "mph" else "kph") }, suffix = { Text(stringResource(if (isImperial) R.string.unit_mph else R.string.unit_kph)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
) )
@ -535,7 +536,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Use custom power range") Text(stringResource(R.string.use_custom_power_range))
} }
if(useCustomPowerRange){ if(useCustomPowerRange){
@ -545,8 +546,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(right = 2.dp) .absolutePadding(right = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { customMinPower = it.filter { c -> c.isDigit() } }, onValueChange = { customMinPower = it.filter { c -> c.isDigit() } },
label = { Text("Min Power", fontSize = 12.sp) }, label = { Text(stringResource(R.string.min_power), fontSize = 12.sp) },
suffix = { Text("W") }, suffix = { Text(stringResource(R.string.unit_watts)) },
placeholder = { Text("$profileMinPower") }, placeholder = { Text("$profileMinPower") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
@ -557,8 +558,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(left = 2.dp) .absolutePadding(left = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { customMaxPower = it.filter { c -> c.isDigit() } }, onValueChange = { customMaxPower = it.filter { c -> c.isDigit() } },
label = { Text("Max Power", fontSize = 12.sp) }, label = { Text(stringResource(R.string.max_power), fontSize = 12.sp) },
suffix = { Text("W") }, suffix = { Text(stringResource(R.string.unit_watts)) },
placeholder = { Text("$profileMaxPower") }, placeholder = { Text("$profileMaxPower") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
@ -577,7 +578,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Use custom HR range") Text(stringResource(R.string.use_custom_hr_range))
} }
if (useCustomHrRange){ if (useCustomHrRange){
@ -587,8 +588,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(right = 2.dp) .absolutePadding(right = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { customMinHr = it.filter { c -> c.isDigit() } }, onValueChange = { customMinHr = it.filter { c -> c.isDigit() } },
label = { Text("Min Hr") }, label = { Text(stringResource(R.string.min_hr)) },
suffix = { Text("bpm") }, suffix = { Text(stringResource(R.string.unit_bpm)) },
placeholder = { if(profileRestHr > 0) Text("$profileRestHr") else Unit }, placeholder = { if(profileRestHr > 0) Text("$profileRestHr") else Unit },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
@ -599,8 +600,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(left = 2.dp) .absolutePadding(left = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { customMaxHr = it.filter { c -> c.isDigit() } }, onValueChange = { customMaxHr = it.filter { c -> c.isDigit() } },
label = { Text("Max Hr") }, label = { Text(stringResource(R.string.max_hr)) },
suffix = { Text("bpm") }, suffix = { Text(stringResource(R.string.unit_bpm)) },
placeholder = { if(profileMaxHr > 0) Text("$profileMaxHr") else Unit }, placeholder = { if(profileMaxHr > 0) Text("$profileMaxHr") else Unit },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
@ -621,8 +622,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(right = 2.dp) .absolutePadding(right = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { minCadence = it.filter { c -> c.isDigit() } }, onValueChange = { minCadence = it.filter { c -> c.isDigit() } },
label = { Text("Min Cadence") }, label = { Text(stringResource(R.string.min_cadence)) },
suffix = { Text("rpm") }, suffix = { Text(stringResource(R.string.unit_rpm)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
) )
@ -632,8 +633,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(left = 2.dp) .absolutePadding(left = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { maxCadence = it.filter { c -> c.isDigit() } }, onValueChange = { maxCadence = it.filter { c -> c.isDigit() } },
label = { Text("Min Cadence") }, label = { Text(stringResource(R.string.max_cadence)) },
suffix = { Text("rpm") }, suffix = { Text(stringResource(R.string.unit_rpm)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
) )
@ -650,8 +651,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(right = 2.dp) .absolutePadding(right = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { minGrade = it.filterIndexed { index, c -> c.isDigit() || (c == '-' && index == 0) } }, onValueChange = { minGrade = it.filterIndexed { index, c -> c.isDigit() || (c == '-' && index == 0) } },
label = { Text("Min Grade") }, label = { Text(stringResource(R.string.min_grade)) },
suffix = { Text("%") }, suffix = { Text(stringResource(R.string.unit_percent)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
) )
@ -661,8 +662,8 @@ fun MainScreen(onFinish: () -> Unit) {
.absolutePadding(left = 2.dp) .absolutePadding(left = 2.dp)
.onFocusEvent(::updateFocus), .onFocusEvent(::updateFocus),
onValueChange = { maxGrade = it.filterIndexed { index, c -> c.isDigit() || (c == '-' && index == 0) } }, onValueChange = { maxGrade = it.filterIndexed { index, c -> c.isDigit() || (c == '-' && index == 0) } },
label = { Text("Max Grade") }, label = { Text(stringResource(R.string.max_grade)) },
suffix = { Text("%") }, suffix = { Text(stringResource(R.string.unit_percent)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true singleLine = true
) )
@ -675,7 +676,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Color based on HR / power zones") Text(stringResource(R.string.color_based_on_zones))
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@ -684,7 +685,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Show value on bars") Text(stringResource(R.string.show_value_on_bars))
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@ -693,7 +694,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Solid background") Text(stringResource(R.string.solid_background))
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@ -702,7 +703,7 @@ fun MainScreen(onFinish: () -> Unit) {
coroutineScope.launch { updateSettings() } coroutineScope.launch { updateSettings() }
}) })
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(10.dp))
Text("Only show while riding") Text(stringResource(R.string.only_show_while_riding))
} }
Spacer(modifier = Modifier.padding(30.dp)) Spacer(modifier = Modifier.padding(30.dp))
@ -711,7 +712,7 @@ fun MainScreen(onFinish: () -> Unit) {
Image( Image(
painter = painterResource(id = R.drawable.back), painter = painterResource(id = R.drawable.back),
contentDescription = "Back", contentDescription = stringResource(R.string.content_desc_back),
modifier = Modifier modifier = Modifier
.align(Alignment.BottomStart) .align(Alignment.BottomStart)
.padding(bottom = 10.dp) .padding(bottom = 10.dp)

View File

@ -0,0 +1,77 @@
<resources>
<string name="app_name">Powerbar</string>
<string name="extension_name">Powerbar</string>
<!-- Main Screen Strings -->
<string name="powerbar_title">Powerbar</string>
<string name="karoo_connection_error">Gerätestatus konnte nicht gelesen werden. Ist Ihr Karoo aktualisiert?</string>
<string name="permission_not_granted">Sie haben die Berechtigung zum Anzeigen der Powerbar nicht erteilt. Bitte tun Sie dies.</string>
<string name="give_permission">Berechtigung erteilen</string>
<string name="top_bar">Oben</string>
<string name="bottom_bar">Unten</string>
<string name="split">Teilen</string>
<string name="top_bar_left">Oben (Links): %s</string>
<string name="top_bar_right">Oben (Rechts): %s</string>
<string name="top_bar_single">Oben: %s</string>
<string name="bottom_bar_left">Unten (Links): %s</string>
<string name="bottom_bar_right">Unten (Rechts): %s</string>
<string name="bottom_bar_single">Unten: %s</string>
<string name="bar_size">Balkengröße</string>
<string name="text_size">Textgröße</string>
<string name="min_speed">Min. Geschwindigkeit</string>
<string name="max_speed">Max. Geschwindigkeit</string>
<string name="use_custom_power_range">Eigenen Leistungsbereich verwenden</string>
<string name="min_power">Min. Leistung</string>
<string name="max_power">Max. Leistung</string>
<string name="use_custom_hr_range">Eigenen HF-Bereich verwenden</string>
<string name="min_hr">Min. HF</string>
<string name="max_hr">Max. HF</string>
<string name="min_cadence">Min. Trittfrequenz</string>
<string name="max_cadence">Max. Trittfrequenz</string>
<string name="min_grade">Min. Steigung</string>
<string name="max_grade">Max. Steigung</string>
<string name="color_based_on_zones">Farbe basierend auf HF-/Leistungszonen</string>
<string name="show_value_on_bars">Werte auf Balken anzeigen</string>
<string name="solid_background">Fester Hintergrund</string>
<string name="only_show_while_riding">Nur während der Fahrt anzeigen</string>
<!-- Data Source Labels -->
<string name="source_none">Keine</string>
<string name="source_heart_rate">Herzfrequenz</string>
<string name="source_power">Leistung</string>
<string name="source_power_3s">Leistung (3 Sek. Ø)</string>
<string name="source_power_10s">Leistung (10 Sek. Ø)</string>
<string name="source_speed">Geschwindigkeit</string>
<string name="source_speed_3s">Geschwindigkeit (3 Sek. Ø)</string>
<string name="source_cadence">Trittfrequenz</string>
<string name="source_cadence_3s">Trittfrequenz (3 Sek. Ø)</string>
<string name="source_grade">Steigung</string>
<string name="source_power_balance">Leistungsbalance</string>
<string name="source_route_progress">Routenfortschritt</string>
<string name="source_route_remaining">Verbleibende Route</string>
<string name="source_front_gear">Vorderer Gang</string>
<string name="source_rear_gear">Hinterer Gang</string>
<!-- Units -->
<string name="unit_mph">mph</string>
<string name="unit_kph">km/h</string>
<string name="unit_watts">W</string>
<string name="unit_bpm">S/min</string>
<string name="unit_rpm">U/min</string>
<string name="unit_percent">%</string>
<!-- Content Descriptions -->
<string name="content_desc_give_permission">Berechtigung erteilen</string>
<string name="content_desc_select">Auswählen</string>
<string name="content_desc_back">Zurück</string>
<!-- Notification -->
<string name="notification_text">Anzeige über anderen Apps</string>
<string name="notification_channel_name">Hintergrunddienst</string>
<!-- Size Options -->
<string name="size_none">Keine</string>
<string name="size_small">Klein</string>
<string name="size_medium">Mittel</string>
<string name="size_large">Groß</string>
</resources>

View File

@ -1,4 +1,77 @@
<resources> <resources>
<string name="app_name">Powerbar</string> <string name="app_name">Powerbar</string>
<string name="extension_name">Powerbar</string> <string name="extension_name">Powerbar</string>
<!-- Main Screen Strings -->
<string name="powerbar_title">Powerbar</string>
<string name="karoo_connection_error">Could not read device status. Is your Karoo updated?</string>
<string name="permission_not_granted">You have not granted the permission to show the power bar overlay. Please do so.</string>
<string name="give_permission">Give permission</string>
<string name="top_bar">Top Bar</string>
<string name="bottom_bar">Bottom Bar</string>
<string name="split">Split</string>
<string name="top_bar_left">Top Bar (Left): %s</string>
<string name="top_bar_right">Top Bar (Right): %s</string>
<string name="top_bar_single">Top Bar: %s</string>
<string name="bottom_bar_left">Bottom Bar (Left): %s</string>
<string name="bottom_bar_right">Bottom Bar (Right): %s</string>
<string name="bottom_bar_single">Bottom Bar: %s</string>
<string name="bar_size">Bar Size</string>
<string name="text_size">Text Size</string>
<string name="min_speed">Min Speed</string>
<string name="max_speed">Max Speed</string>
<string name="use_custom_power_range">Use custom power range</string>
<string name="min_power">Min Power</string>
<string name="max_power">Max Power</string>
<string name="use_custom_hr_range">Use custom HR range</string>
<string name="min_hr">Min Hr</string>
<string name="max_hr">Max Hr</string>
<string name="min_cadence">Min Cadence</string>
<string name="max_cadence">Max Cadence</string>
<string name="min_grade">Min Grade</string>
<string name="max_grade">Max Grade</string>
<string name="color_based_on_zones">Color based on HR / power zones</string>
<string name="show_value_on_bars">Show value on bars</string>
<string name="solid_background">Solid background</string>
<string name="only_show_while_riding">Only show while riding</string>
<!-- Data Source Labels -->
<string name="source_none">None</string>
<string name="source_heart_rate">Heart Rate</string>
<string name="source_power">Power</string>
<string name="source_power_3s">Power (3 sec avg)</string>
<string name="source_power_10s">Power (10 sec avg)</string>
<string name="source_speed">Speed</string>
<string name="source_speed_3s">Speed (3 sec avg)</string>
<string name="source_cadence">Cadence</string>
<string name="source_cadence_3s">Cadence (3 sec avg)</string>
<string name="source_grade">Grade</string>
<string name="source_power_balance">Power Balance</string>
<string name="source_route_progress">Route Progress</string>
<string name="source_route_remaining">Route Remaining</string>
<string name="source_front_gear">Front Gear</string>
<string name="source_rear_gear">Rear Gear</string>
<!-- Units -->
<string name="unit_mph">mph</string>
<string name="unit_kph">kph</string>
<string name="unit_watts">W</string>
<string name="unit_bpm">bpm</string>
<string name="unit_rpm">rpm</string>
<string name="unit_percent">%</string>
<!-- Content Descriptions -->
<string name="content_desc_give_permission">Give permission</string>
<string name="content_desc_select">Select</string>
<string name="content_desc_back">Back</string>
<!-- Notification -->
<string name="notification_text">Displaying on top of other apps</string>
<string name="notification_channel_name">Background Service</string>
<!-- Size Options -->
<string name="size_none">None</string>
<string name="size_small">Small</string>
<string name="size_medium">Medium</string>
<string name="size_large">Large</string>
</resources> </resources>