Localise the unnamed-calendar placeholder (#329)
CalendarMapper substituted a hardcoded German "(Unbenannter Kalender)" for a null CALENDAR_DISPLAY_NAME, so it showed in every locale wherever calendars are listed — and the calendar editor prefills from that name, so saving wrote the literal into the provider for real. Leave the name blank and add a calendar_unnamed string the views substitute. The write paths now store the trimmed name as given instead of inventing one: a stored placeholder would freeze the creation-time language into the provider, and the editor already refuses a blank name.
This commit is contained in:
@@ -376,7 +376,7 @@ class AndroidCalendarDataSource @Inject constructor(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun createLocalCalendar(displayName: String, color: Int, description: String?): Long {
|
override fun createLocalCalendar(displayName: String, color: Int, description: String?): Long {
|
||||||
val name = displayName.trim().ifEmpty { Fallbacks.UNNAMED_CALENDAR }
|
val name = displayName.trim()
|
||||||
val values = ContentValues().apply {
|
val values = ContentValues().apply {
|
||||||
put(CalendarContract.Calendars.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME)
|
put(CalendarContract.Calendars.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME)
|
||||||
put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
|
put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
|
||||||
@@ -400,7 +400,7 @@ class AndroidCalendarDataSource @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateCalendar(id: Long, displayName: String, color: Int, description: String?) {
|
override fun updateCalendar(id: Long, displayName: String, color: Int, description: String?) {
|
||||||
val name = displayName.trim().ifEmpty { Fallbacks.UNNAMED_CALENDAR }
|
val name = displayName.trim()
|
||||||
val values = ContentValues().apply {
|
val values = ContentValues().apply {
|
||||||
put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, name)
|
put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, name)
|
||||||
put(CalendarContract.Calendars.NAME, name)
|
put(CalendarContract.Calendars.NAME, name)
|
||||||
@@ -464,7 +464,7 @@ class AndroidCalendarDataSource @Inject constructor(
|
|||||||
PackageManager.PERMISSION_GRANTED
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
override fun createManagedCalendar(displayName: String, color: Int, type: SpecialDateType): Long {
|
override fun createManagedCalendar(displayName: String, color: Int, type: SpecialDateType): Long {
|
||||||
val name = displayName.trim().ifEmpty { Fallbacks.UNNAMED_CALENDAR }
|
val name = displayName.trim()
|
||||||
val values = ContentValues().apply {
|
val values = ContentValues().apply {
|
||||||
put(CalendarContract.Calendars.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME)
|
put(CalendarContract.Calendars.ACCOUNT_NAME, LOCAL_ACCOUNT_NAME)
|
||||||
put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
|
put(CalendarContract.Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL)
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ internal fun ColumnReader.toCalendarSource(): CalendarSource {
|
|||||||
val isLocal = accountType == CalendarContract.ACCOUNT_TYPE_LOCAL
|
val isLocal = accountType == CalendarContract.ACCOUNT_TYPE_LOCAL
|
||||||
return CalendarSource(
|
return CalendarSource(
|
||||||
id = getLong(CalendarProjection.IDX_ID),
|
id = getLong(CalendarProjection.IDX_ID),
|
||||||
displayName = getString(CalendarProjection.IDX_DISPLAY_NAME)
|
// Blank when the provider row has no name: the placeholder is a display
|
||||||
?: Fallbacks.UNNAMED_CALENDAR,
|
// string and belongs to the UI, where it can be localised (#329).
|
||||||
|
displayName = getString(CalendarProjection.IDX_DISPLAY_NAME).orEmpty(),
|
||||||
accountName = getString(CalendarProjection.IDX_ACCOUNT_NAME).orEmpty(),
|
accountName = getString(CalendarProjection.IDX_ACCOUNT_NAME).orEmpty(),
|
||||||
accountType = accountType,
|
accountType = accountType,
|
||||||
color = getInt(CalendarProjection.IDX_COLOR),
|
color = getInt(CalendarProjection.IDX_COLOR),
|
||||||
|
|||||||
@@ -312,7 +312,3 @@ internal object ReminderProjection {
|
|||||||
const val IDX_MINUTES = 0
|
const val IDX_MINUTES = 0
|
||||||
const val IDX_METHOD = 1
|
const val IDX_METHOD = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object Fallbacks {
|
|
||||||
const val UNNAMED_CALENDAR = "(Unbenannter Kalender)"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ private fun ExportCalendarPicker(
|
|||||||
calendars.forEachIndexed { index, calendar ->
|
calendars.forEachIndexed { index, calendar ->
|
||||||
val isSelected = calendar.id in selected
|
val isSelected = calendar.id in selected
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
summary = calendar.description,
|
summary = calendar.description,
|
||||||
position = positionOf(index, calendars.size),
|
position = positionOf(index, calendars.size),
|
||||||
leading = { CalendarColorChip(calendar.color) },
|
leading = { CalendarColorChip(calendar.color) },
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ private fun CalendarsList(
|
|||||||
local.forEachIndexed { index, calendar ->
|
local.forEachIndexed { index, calendar ->
|
||||||
val disabled = !calendar.isVisibleInSystem
|
val disabled = !calendar.isVisibleInSystem
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
summary = calendarRowSummary(calendar),
|
summary = calendarRowSummary(calendar),
|
||||||
position = if (index == local.lastIndex) Position.Bottom else Position.Middle,
|
position = if (index == local.lastIndex) Position.Bottom else Position.Middle,
|
||||||
container = MaterialTheme.colorScheme.surfaceContainerHighest,
|
container = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
@@ -254,7 +254,7 @@ private fun CalendarsList(
|
|||||||
leading = { CalendarColorChip(calendar.color, dimIf(disabled)) },
|
leading = { CalendarColorChip(calendar.color, dimIf(disabled)) },
|
||||||
trailing = {
|
trailing = {
|
||||||
EnableSwitch(
|
EnableSwitch(
|
||||||
calendarName = calendar.displayName,
|
calendarName = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
enabled = !disabled,
|
enabled = !disabled,
|
||||||
onToggle = { enabled -> onSetVisible(calendar.id, enabled) },
|
onToggle = { enabled -> onSetVisible(calendar.id, enabled) },
|
||||||
)
|
)
|
||||||
@@ -331,7 +331,7 @@ private fun CalendarsList(
|
|||||||
ordered.forEachIndexed { index, calendar ->
|
ordered.forEachIndexed { index, calendar ->
|
||||||
val disabled = !calendar.isVisibleInSystem || calendar.isNotSynced
|
val disabled = !calendar.isVisibleInSystem || calendar.isNotSynced
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
summary = calendarRowSummary(calendar),
|
summary = calendarRowSummary(calendar),
|
||||||
position = if (index == ordered.lastIndex) Position.Bottom else Position.Middle,
|
position = if (index == ordered.lastIndex) Position.Bottom else Position.Middle,
|
||||||
container = MaterialTheme.colorScheme.surfaceContainerHighest,
|
container = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
@@ -340,7 +340,7 @@ private fun CalendarsList(
|
|||||||
trailing = if (calendar.hasVisibilitySwitch) {
|
trailing = if (calendar.hasVisibilitySwitch) {
|
||||||
{
|
{
|
||||||
EnableSwitch(
|
EnableSwitch(
|
||||||
calendarName = calendar.displayName,
|
calendarName = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
enabled = calendar.isVisibleInSystem,
|
enabled = calendar.isVisibleInSystem,
|
||||||
onToggle = { enabled ->
|
onToggle = { enabled ->
|
||||||
onSetVisible(calendar.id, enabled)
|
onSetVisible(calendar.id, enabled)
|
||||||
|
|||||||
@@ -56,12 +56,14 @@ fun List<CalendarSource>.groupByAccount(): List<CalendarAccountGroup> {
|
|||||||
* account comes from when another account shares the name (#77).
|
* account comes from when another account shares the name (#77).
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun accountGroupTitle(group: CalendarAccountGroup): String =
|
fun accountGroupTitle(group: CalendarAccountGroup): String {
|
||||||
if (!group.ambiguous) {
|
val label = group.label.ifBlank { stringResource(R.string.calendar_unnamed) }
|
||||||
group.label
|
return if (!group.ambiguous) {
|
||||||
|
label
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.calendars_account_from_source, group.label, sourceAppName(group.accountType))
|
stringResource(R.string.calendars_account_from_source, label, sourceAppName(group.accountType))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The human name of the app backing [accountType], falling back to the raw
|
* The human name of the app backing [accountType], falling back to the raw
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ private fun CalendarPickerGroup(
|
|||||||
calendars.forEachIndexed { index, calendar ->
|
calendars.forEachIndexed { index, calendar ->
|
||||||
val isSelected = calendar.id == selectedId
|
val isSelected = calendar.id == selectedId
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
position = if (index == calendars.lastIndex) Position.Bottom else Position.Middle,
|
position = if (index == calendars.lastIndex) Position.Bottom else Position.Middle,
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
leading = { CalendarColorChip(calendar.color) },
|
leading = { CalendarColorChip(calendar.color) },
|
||||||
|
|||||||
@@ -527,7 +527,8 @@ private fun EventDetailContent(
|
|||||||
iconContentDescription = stringResource(R.string.event_detail_calendar),
|
iconContentDescription = stringResource(R.string.event_detail_calendar),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = state.calendarName ?: stringResource(R.string.event_detail_calendar_unknown),
|
text = state.calendarName?.ifBlank { stringResource(R.string.calendar_unnamed) }
|
||||||
|
?: stringResource(R.string.event_detail_calendar_unknown),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -620,7 +620,9 @@ private fun EventEditContent(
|
|||||||
Text(
|
Text(
|
||||||
text = stringResource(
|
text = stringResource(
|
||||||
R.string.event_edit_managed_hint,
|
R.string.event_edit_managed_hint,
|
||||||
selectedCalendar?.displayName.orEmpty(),
|
selectedCalendar?.displayName
|
||||||
|
?.ifBlank { stringResource(R.string.calendar_unnamed) }
|
||||||
|
.orEmpty(),
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@@ -719,6 +721,7 @@ private fun EventEditContent(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = selectedCalendar?.displayName
|
text = selectedCalendar?.displayName
|
||||||
|
?.ifBlank { stringResource(R.string.calendar_unnamed) }
|
||||||
?: stringResource(R.string.event_edit_error_no_calendar),
|
?: stringResource(R.string.event_edit_error_no_calendar),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = if (selectedCalendar == null) {
|
color = if (selectedCalendar == null) {
|
||||||
|
|||||||
@@ -62,15 +62,16 @@ private fun FilterList(
|
|||||||
) {
|
) {
|
||||||
Column(modifier = modifier.fillMaxWidth()) {
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
groups.forEach { group ->
|
groups.forEach { group ->
|
||||||
|
val account = group.account.ifBlank { stringResource(R.string.calendar_unnamed) }
|
||||||
Text(
|
Text(
|
||||||
text = if (group.ambiguous) {
|
text = if (group.ambiguous) {
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.calendars_account_from_source,
|
R.string.calendars_account_from_source,
|
||||||
group.account,
|
account,
|
||||||
sourceAppName(group.accountType),
|
sourceAppName(group.accountType),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
group.account
|
account
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@@ -83,7 +84,7 @@ private fun FilterList(
|
|||||||
)
|
)
|
||||||
group.calendars.forEachIndexed { index, cal ->
|
group.calendars.forEachIndexed { index, cal ->
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = cal.displayName,
|
title = cal.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
position = positionOf(index, group.calendars.size),
|
position = positionOf(index, group.calendars.size),
|
||||||
minHeight = 56.dp,
|
minHeight = 56.dp,
|
||||||
leading = { CalendarColorChip(cal.color) },
|
leading = { CalendarColorChip(cal.color) },
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ internal fun HiddenCalendarsStep(
|
|||||||
calendars.forEachIndexed { index, calendar ->
|
calendars.forEachIndexed { index, calendar ->
|
||||||
val off = !calendar.isVisibleInSystem
|
val off = !calendar.isVisibleInSystem
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
summary = calendar.accountName.takeIf { it.isNotBlank() },
|
summary = calendar.accountName.takeIf { it.isNotBlank() },
|
||||||
position = when {
|
position = when {
|
||||||
calendars.size == 1 -> Position.Alone
|
calendars.size == 1 -> Position.Alone
|
||||||
@@ -141,7 +141,7 @@ internal fun HiddenCalendarsStep(
|
|||||||
trailing = if (calendar.hasVisibilitySwitch) {
|
trailing = if (calendar.hasVisibilitySwitch) {
|
||||||
{
|
{
|
||||||
CalendarSwitch(
|
CalendarSwitch(
|
||||||
calendarName = calendar.displayName,
|
calendarName = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
enabled = !off,
|
enabled = !off,
|
||||||
onToggle = { on -> onSetVisible(calendar.id, on) },
|
onToggle = { on -> onSetVisible(calendar.id, on) },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ internal fun EventFormScreen(
|
|||||||
durationCalendars.forEachIndexed { index, calendar ->
|
durationCalendars.forEachIndexed { index, calendar ->
|
||||||
val override = state.perCalendarEventDuration[calendar.id]
|
val override = state.perCalendarEventDuration[calendar.id]
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
summary = override?.let { durationLabel(it) }
|
summary = override?.let { durationLabel(it) }
|
||||||
?: stringResource(
|
?: stringResource(
|
||||||
R.string.settings_calendar_duration_inherits,
|
R.string.settings_calendar_duration_inherits,
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ internal fun NotificationsScreen(
|
|||||||
// own section — link there instead.
|
// own section — link there instead.
|
||||||
if (calendar.id in state.managedCalendarIds) {
|
if (calendar.id in state.managedCalendarIds) {
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
summary = stringResource(R.string.settings_calendar_reminders_managed_hint),
|
summary = stringResource(R.string.settings_calendar_reminders_managed_hint),
|
||||||
position = Position.Alone,
|
position = Position.Alone,
|
||||||
leading = { CalendarColorChip(calendar.color) },
|
leading = { CalendarColorChip(calendar.color) },
|
||||||
@@ -198,7 +198,7 @@ internal fun NotificationsScreen(
|
|||||||
}
|
}
|
||||||
val expanded = calendar.id in expandedCalendars
|
val expanded = calendar.id in expandedCalendars
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = calendar.displayName,
|
title = calendar.displayName.ifBlank { stringResource(R.string.calendar_unnamed) },
|
||||||
position = if (expanded) Position.Top else Position.Alone,
|
position = if (expanded) Position.Top else Position.Alone,
|
||||||
leading = { CalendarColorChip(calendar.color) },
|
leading = { CalendarColorChip(calendar.color) },
|
||||||
trailing = {
|
trailing = {
|
||||||
|
|||||||
@@ -290,6 +290,9 @@
|
|||||||
<!-- Shared event strings -->
|
<!-- Shared event strings -->
|
||||||
<string name="event_untitled">(No title)</string>
|
<string name="event_untitled">(No title)</string>
|
||||||
|
|
||||||
|
<!-- Stands in for a calendar the provider gave no name (#329) -->
|
||||||
|
<string name="calendar_unnamed">(No name)</string>
|
||||||
|
|
||||||
<!-- First-launch wizard (#163) -->
|
<!-- First-launch wizard (#163) -->
|
||||||
<string name="onboarding_step_counter">Step %1$d of %2$d</string>
|
<string name="onboarding_step_counter">Step %1$d of %2$d</string>
|
||||||
<string name="onboarding_backup_title">Your events live only here</string>
|
<string name="onboarding_backup_title">Your events live only here</string>
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ class CalendarMapperTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `null displayName falls back to placeholder`() {
|
fun `null displayName is left blank for the UI to fill in`() {
|
||||||
val src = reader(displayName = null).toCalendarSource()
|
val src = reader(displayName = null).toCalendarSource()
|
||||||
assertThat(src.displayName).isEqualTo(Fallbacks.UNNAMED_CALENDAR)
|
assertThat(src.displayName).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user