Compare commits
27 Commits
v2.19.0
...
release/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8119be02 | ||
|
|
7e843aa740 | ||
| 9775e0a652 | |||
| 2cf7d590bd | |||
|
|
8c76cbdf5e | ||
|
|
e0fdadc7ca | ||
|
|
136167b54c | ||
| 02fd599e6b | |||
| b0412a96f3 | |||
|
|
06dbb89868 | ||
|
|
b002f45f29 | ||
|
|
c1eb702384 | ||
|
|
307517e85a | ||
|
|
a8ba227e8b | ||
|
|
cbfdfb9710 | ||
| d85003416e | |||
|
|
61c516149e | ||
|
|
560225cb8c | ||
|
|
2e8367afd4 | ||
| 8c40109341 | |||
|
|
4259c996b6 | ||
| d4218227bd | |||
|
|
6a4c5084a5 | ||
|
|
e38b9cde1f | ||
|
|
dbae8b5bcf | ||
|
|
2d06b7ad32 | ||
|
|
10a674deba |
33
CHANGELOG.md
33
CHANGELOG.md
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Month-view event titles no longer end in an ellipsis. The "…" took the width
|
||||
of a couple of characters and told you nothing you couldn't already see, so
|
||||
the title now simply runs to the edge of its chip — a few more letters per
|
||||
cell, which is often the difference between two events you can tell apart and
|
||||
two you can't ([#164]).
|
||||
|
||||
## [2.19.1] — 2026-08-11
|
||||
|
||||
### Added
|
||||
- **Drag to reschedule can be turned off.** Settings → Views → *Drag to
|
||||
reschedule*, on by default. Off, no event can be picked up in the month, week
|
||||
or day view — for anyone who kept moving appointments by accident while
|
||||
scrolling ([#173]).
|
||||
- An early Brazilian Portuguese translation. Calendula has started speaking
|
||||
Brazilian Portuguese, contributed as a community translation through
|
||||
[Calendula's Weblate](https://weblate.dev.jeanlucmakiola.de/projects/calendula/).
|
||||
About a third of the app is covered, so the rest still shows in English — you
|
||||
can pick it under Settings → Language or in Android's per-app language
|
||||
settings. Thanks to
|
||||
[bkrz](https://weblate.dev.jeanlucmakiola.de/user/bkrz/) for getting it
|
||||
started; help finishing it is very welcome.
|
||||
|
||||
### Fixed
|
||||
- **The language picker forgot which language was picked.** Android hands the
|
||||
applied language back with its script filled in — Simplified Chinese goes in
|
||||
as `zh-CN` and comes out as `zh-Hans-CN` — so after a restart no entry in
|
||||
Settings → Language showed as selected, and the row above it named the
|
||||
language the long way round. Only languages that imply a script were
|
||||
affected.
|
||||
|
||||
## [2.19.0] — 2026-08-10
|
||||
|
||||
### Added
|
||||
@@ -1389,3 +1420,5 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#80]: https://codeberg.org/jlmakiola/calendula/issues/80
|
||||
[#123]: https://codeberg.org/jlmakiola/calendula/issues/123
|
||||
[#163]: https://codeberg.org/jlmakiola/calendula/issues/163
|
||||
[#173]: https://codeberg.org/jlmakiola/calendula/issues/173
|
||||
[#164]: https://codeberg.org/jlmakiola/calendula/issues/164
|
||||
|
||||
@@ -28,8 +28,8 @@ android {
|
||||
// which builds this version and then creates the matching vX.Y.Z tag +
|
||||
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
||||
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
||||
versionCode = 21900
|
||||
versionName = "2.19.0"
|
||||
versionCode = 21901
|
||||
versionName = "2.19.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -301,6 +301,19 @@ class SettingsPrefs @Inject constructor(
|
||||
store.edit { it[TODAY_BUTTON_IN_TOOLBAR_KEY] = enabled }
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether events can be dragged to another slot in the calendar views (#68,
|
||||
* #173). Defaults to ON. Off means no block registers a drag gesture at all;
|
||||
* rescheduling then goes through the edit form.
|
||||
*/
|
||||
val dragToReschedule: Flow<Boolean> = store.data.map { prefs ->
|
||||
prefs[DRAG_TO_RESCHEDULE_KEY] ?: true
|
||||
}
|
||||
|
||||
suspend fun setDragToReschedule(enabled: Boolean) {
|
||||
store.edit { it[DRAG_TO_RESCHEDULE_KEY] = enabled }
|
||||
}
|
||||
|
||||
/**
|
||||
* How far ahead the in-app Agenda screen shows events (v2.11). Defaults to
|
||||
* [AgendaRange.Month] — a month of upcoming events. Independent of the
|
||||
@@ -945,6 +958,7 @@ class SettingsPrefs @Inject constructor(
|
||||
internal val MONTH_VIEW_STYLE_KEY = stringPreferencesKey("month_view_style")
|
||||
internal val TIMELINE_SCALE_KEY = stringPreferencesKey("timeline_scale")
|
||||
internal val TODAY_BUTTON_IN_TOOLBAR_KEY = booleanPreferencesKey("today_button_in_toolbar")
|
||||
internal val DRAG_TO_RESCHEDULE_KEY = booleanPreferencesKey("drag_to_reschedule")
|
||||
internal val DEFAULT_VIEW_KEY = stringPreferencesKey("default_view")
|
||||
internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views")
|
||||
internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order")
|
||||
|
||||
@@ -312,9 +312,13 @@ fun CalendarHost(
|
||||
heldEditKey = key
|
||||
editKey = key
|
||||
}
|
||||
val moveScope = remember(movableCalendarIds, reschedule) {
|
||||
// Off by preference (#173) switches off the gesture, not the scope: blocks
|
||||
// register no drag, while the edit form and its TalkBack action stay put.
|
||||
val dragToReschedule = viewModel.dragToReschedule.collectAsStateWithLifecycle().value
|
||||
val moveScope = remember(movableCalendarIds, reschedule, dragToReschedule) {
|
||||
EventMoveScope(
|
||||
movableCalendarIds = movableCalendarIds,
|
||||
dragEnabled = dragToReschedule,
|
||||
move = reschedule::move,
|
||||
inFlight = reschedule.inFlight,
|
||||
undoStarted = reschedule.undoStarted,
|
||||
|
||||
@@ -53,4 +53,18 @@ class CalendarHostViewModel @Inject constructor(
|
||||
started = SharingStarted.WhileSubscribed(5_000L),
|
||||
initialValue = false,
|
||||
)
|
||||
|
||||
/**
|
||||
* Whether events may be dragged to another slot to reschedule them (#68, #173).
|
||||
* Off until the stored value arrives, even though the preference itself defaults
|
||||
* on: the wrong guess costs an enabled user a gesture for the few frames a cold
|
||||
* start takes to read DataStore, but re-arms the one thing a disabled user
|
||||
* switched off to be rid of.
|
||||
*/
|
||||
val dragToReschedule: StateFlow<Boolean> = prefs.dragToReschedule
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000L),
|
||||
initialValue = false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ fun AgendaScreen(
|
||||
AgendaTopBar(
|
||||
selectedView = selectedView,
|
||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||
quickSwitchViews = quickSwitchViews,
|
||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||
onOpenSearch = onOpenSearch,
|
||||
showTodayButton = todayInToolbar,
|
||||
@@ -409,6 +410,7 @@ private fun AgendaEmpty(modifier: Modifier = Modifier) {
|
||||
private fun AgendaTopBar(
|
||||
selectedView: CalendarView,
|
||||
onCycleView: () -> Unit,
|
||||
quickSwitchViews: List<CalendarView>,
|
||||
onOpenDrawer: () -> Unit,
|
||||
onOpenSearch: () -> Unit,
|
||||
showTodayButton: Boolean,
|
||||
@@ -440,6 +442,7 @@ private fun AgendaTopBar(
|
||||
}
|
||||
ViewSwitcherPill(
|
||||
current = selectedView,
|
||||
cycle = quickSwitchViews,
|
||||
onCycle = onCycleView,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
|
||||
@@ -57,7 +57,8 @@ fun CalendarView.next(available: List<CalendarView> = IMPLEMENTED_VIEWS): Calend
|
||||
* implemented view — the settings screen reorders the whole set — while [cycle]
|
||||
* is the subset the pill actually steps through, in [order]. The navigation
|
||||
* drawer keeps its own separate order and always lists every view, so a view
|
||||
* disabled here stays reachable there.
|
||||
* disabled here stays reachable there — including when [cycle] is emptied and
|
||||
* the pill disappears altogether.
|
||||
*/
|
||||
data class QuickSwitchConfig(
|
||||
val order: List<CalendarView>,
|
||||
@@ -67,14 +68,15 @@ data class QuickSwitchConfig(
|
||||
val cycle: List<CalendarView> get() = order.filter { it in enabled }
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Fewest views that keep the switch meaningful. A single target is not a
|
||||
* switch, so below this the pill is hidden rather than special-cased (#150);
|
||||
* the drawer still reaches every view.
|
||||
*/
|
||||
const val MIN_CYCLE = 2
|
||||
|
||||
/** All views, in default order, all enabled. */
|
||||
val Default = QuickSwitchConfig(IMPLEMENTED_VIEWS, IMPLEMENTED_VIEWS.toSet())
|
||||
|
||||
/**
|
||||
* Fewest views that keep the switch meaningful — a "switch" needs at
|
||||
* least two targets, so the settings screen blocks disabling below this.
|
||||
*/
|
||||
const val MIN_ENABLED = 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ class EventMoveScope(
|
||||
* the repository writes whatever it is handed — so this gate is load-bearing.
|
||||
*/
|
||||
val movableCalendarIds: Set<Long>,
|
||||
/**
|
||||
* Whether the *gesture* is on (#173). Off gates the drag alone: [edit] and the
|
||||
* TalkBack action below stay, since invoking a menu item can't cause the
|
||||
* accidental drags the setting exists to stop.
|
||||
*/
|
||||
val dragEnabled: Boolean,
|
||||
/** False when the drop was refused outright, so nothing will be written. */
|
||||
val move: (MoveRequest) -> Boolean,
|
||||
/**
|
||||
@@ -91,6 +97,17 @@ fun ghostAlpha(lifted: Boolean): Float = animateFloatAsState(
|
||||
label = "ghost-alpha",
|
||||
).value
|
||||
|
||||
/**
|
||||
* Whether [event] can be picked up and dragged — its calendar allows moving and
|
||||
* the gesture is switched on. Not the same question as [eventMoveAction], which
|
||||
* survives the setting.
|
||||
*/
|
||||
@Composable
|
||||
fun eventDragAllowed(event: EventInstance): Boolean {
|
||||
val move = LocalEventMove.current ?: return false
|
||||
return move.dragEnabled && move.allows(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* A TalkBack action that opens [event] in the edit form, so rescheduling isn't
|
||||
* pointer-only. Null when this event can't be moved.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
|
||||
/** How an event chip's title should overflow: what to pass to `Text`. */
|
||||
data class EventTitleOverflow(val overflow: TextOverflow, val softWrap: Boolean)
|
||||
|
||||
/**
|
||||
* Overflow for an event chip's title (#164). Chips are narrow enough that the
|
||||
* "…" costs a couple of readable characters, so the title runs to the chip's
|
||||
* edge and clips mid-glyph instead.
|
||||
*
|
||||
* Two cases keep the ellipsis:
|
||||
*
|
||||
* - **[rtl].** With `softWrap` off Compose lays the line out at its full
|
||||
* intrinsic width and clips to the node's left edge, which in RTL is the *end*
|
||||
* of the string — an Arabic title would lose its beginning. The ellipsis
|
||||
* truncates at the logical end in both directions.
|
||||
* - **More than one line** ([singleLine] false). Wrapping needs `softWrap` on,
|
||||
* and clipping with it on breaks the last line at the last whole word — less
|
||||
* title than the ellipsis showed, not more.
|
||||
*/
|
||||
fun eventTitleOverflowFor(rtl: Boolean, singleLine: Boolean): EventTitleOverflow =
|
||||
if (rtl || !singleLine) {
|
||||
EventTitleOverflow(TextOverflow.Ellipsis, softWrap = true)
|
||||
} else {
|
||||
EventTitleOverflow(TextOverflow.Clip, softWrap = false)
|
||||
}
|
||||
|
||||
/** [eventTitleOverflowFor] against the current layout direction. */
|
||||
@Composable
|
||||
fun eventTitleOverflow(singleLine: Boolean = true): EventTitleOverflow =
|
||||
eventTitleOverflowFor(
|
||||
rtl = LocalLayoutDirection.current == LayoutDirection.Rtl,
|
||||
singleLine = singleLine,
|
||||
)
|
||||
@@ -464,11 +464,13 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier =
|
||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||
) {
|
||||
Column {
|
||||
val titleOverflow = eventTitleOverflow()
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = titleOverflow.overflow,
|
||||
softWrap = titleOverflow.softWrap,
|
||||
color = eventInk(fill, alpha = 0.85f),
|
||||
)
|
||||
Text(
|
||||
|
||||
@@ -10,13 +10,19 @@ import androidx.compose.ui.res.stringResource
|
||||
/**
|
||||
* Top-bar pill that shows the current view and cycles to the next one on tap
|
||||
* (spec M1: Month → Week → Day → Month, restricted to [IMPLEMENTED_VIEWS]).
|
||||
*
|
||||
* Renders nothing when [cycle] holds fewer than [QuickSwitchConfig.MIN_CYCLE]
|
||||
* views (#150), so it drops straight into an app bar's `actions` slot without a
|
||||
* wrapping condition — the same way [TodayAction] handles being turned off.
|
||||
*/
|
||||
@Composable
|
||||
fun ViewSwitcherPill(
|
||||
current: CalendarView,
|
||||
cycle: List<CalendarView>,
|
||||
onCycle: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (cycle.size < QuickSwitchConfig.MIN_CYCLE) return
|
||||
FilledTonalButton(
|
||||
onClick = onCycle,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
|
||||
@@ -65,7 +65,6 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.customActions
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -92,7 +91,9 @@ import de.jeanlucmakiola.calendula.ui.common.TimelineDragController
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDragOverlay
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
||||
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventDragAllowed
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventTitleOverflow
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
||||
import de.jeanlucmakiola.calendula.ui.common.startInstant
|
||||
@@ -242,6 +243,7 @@ fun DayScreen(
|
||||
currentYear = currentYear,
|
||||
selectedView = selectedView,
|
||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||
quickSwitchViews = quickSwitchViews,
|
||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||
onOpenSearch = onOpenSearch,
|
||||
onJumpToDate = jumpToDate,
|
||||
@@ -410,6 +412,7 @@ private fun DayTopBar(
|
||||
currentYear: Int,
|
||||
selectedView: CalendarView,
|
||||
onCycleView: () -> Unit,
|
||||
quickSwitchViews: List<CalendarView>,
|
||||
onOpenDrawer: () -> Unit,
|
||||
onOpenSearch: () -> Unit,
|
||||
onJumpToDate: (LocalDate) -> Unit,
|
||||
@@ -444,6 +447,7 @@ private fun DayTopBar(
|
||||
}
|
||||
ViewSwitcherPill(
|
||||
current = selectedView,
|
||||
cycle = quickSwitchViews,
|
||||
onCycle = onCycleView,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
@@ -518,11 +522,13 @@ private fun AllDayBar(
|
||||
.semantics { contentDescription = title },
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
val titleOverflow = eventTitleOverflow()
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = titleOverflow.overflow,
|
||||
softWrap = titleOverflow.softWrap,
|
||||
color = eventInk(fill),
|
||||
)
|
||||
}
|
||||
@@ -737,7 +743,7 @@ private fun EventBlock(
|
||||
val moveAction = eventMoveAction(block.event)
|
||||
// A block clipped at the top continues from the previous day: its top edge
|
||||
// is midnight, not the event's start, so dragging it would invent a time.
|
||||
val draggable = moveAction != null && block.beginsOn(date, zone)
|
||||
val draggable = eventDragAllowed(block.event) && block.beginsOn(date, zone)
|
||||
val dragModifier = rememberEventDragSource(
|
||||
enabled = draggable,
|
||||
key = block.event.instanceId,
|
||||
@@ -765,11 +771,13 @@ private fun EventBlock(
|
||||
) {
|
||||
Column {
|
||||
if (showTitle) {
|
||||
val titleOverflow = eventTitleOverflow(singleLine = showTime)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
maxLines = if (showTime) 1 else 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = titleOverflow.overflow,
|
||||
softWrap = titleOverflow.softWrap,
|
||||
color = eventInk(fill, alpha = 0.85f),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ import androidx.compose.ui.geometry.Offset
|
||||
import kotlin.math.roundToInt
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberDragSurface
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventTitleOverflow
|
||||
import de.jeanlucmakiola.calendula.ui.common.MoveTarget
|
||||
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
||||
@@ -386,6 +387,7 @@ fun MonthScreen(
|
||||
titleDate = LocalDate(titleMonth.year, titleMonth.month, 1),
|
||||
selectedView = selectedView,
|
||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||
quickSwitchViews = quickSwitchViews,
|
||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||
onOpenSearch = onOpenSearch,
|
||||
onJumpToDate = jumpToDate,
|
||||
@@ -650,6 +652,7 @@ private fun MonthTopBar(
|
||||
titleDate: LocalDate,
|
||||
selectedView: CalendarView,
|
||||
onCycleView: () -> Unit,
|
||||
quickSwitchViews: List<CalendarView>,
|
||||
onOpenDrawer: () -> Unit,
|
||||
onOpenSearch: () -> Unit,
|
||||
onJumpToDate: (LocalDate) -> Unit,
|
||||
@@ -683,6 +686,7 @@ private fun MonthTopBar(
|
||||
}
|
||||
ViewSwitcherPill(
|
||||
current = selectedView,
|
||||
cycle = quickSwitchViews,
|
||||
onCycle = onCycleView,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
@@ -2172,7 +2176,7 @@ private fun monthChipDragModifier(
|
||||
rowHeightPx: Float,
|
||||
isRtl: Boolean,
|
||||
): Modifier = rememberDragSurface(
|
||||
enabled = moveScope != null && controller != null,
|
||||
enabled = moveScope?.dragEnabled == true && controller != null,
|
||||
key = week.days.first(),
|
||||
onPickUp = { local, pointerInRoot, nodeInRoot, size ->
|
||||
val bandTop = band[0]?.takeIf { it.isAttached }?.positionInRoot()?.y
|
||||
@@ -2352,11 +2356,13 @@ private fun MonthBar(
|
||||
},
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
val titleOverflow = eventTitleOverflow()
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = titleOverflow.overflow,
|
||||
softWrap = titleOverflow.softWrap,
|
||||
color = eventInk(fill),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -302,13 +302,14 @@ private fun ReportProblemRow(position: Position) {
|
||||
@Composable
|
||||
private fun LanguageRow(position: Position) {
|
||||
val context = LocalContext.current
|
||||
val supported = remember { AppLanguage.supportedTags(context, R.xml.locales_config) }
|
||||
// Setting a locale recreates the activity; mirror the choice locally so the
|
||||
// row updates instantly even before the recreation lands.
|
||||
var current by remember { mutableStateOf(AppLanguage.currentTag()) }
|
||||
var current by remember { mutableStateOf(AppLanguage.currentTag(supported)) }
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// null = follow the system; the rest are BCP-47 tags from locales_config.xml.
|
||||
val options = remember { listOf<String?>(null) + AppLanguage.supportedTags(context, R.xml.locales_config) }
|
||||
val options = remember(supported) { listOf<String?>(null) + supported }
|
||||
|
||||
GroupedRow(
|
||||
title = stringResource(R.string.settings_language),
|
||||
|
||||
@@ -42,6 +42,8 @@ data class SettingsUiState(
|
||||
val showWeekNumbers: Boolean = false,
|
||||
/** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */
|
||||
val todayButtonInToolbar: Boolean = false,
|
||||
/** Whether events can be dragged to another slot to reschedule them (#68, #173). */
|
||||
val dragToReschedule: Boolean = true,
|
||||
/** How far ahead the in-app Agenda screen shows events (v2.11). */
|
||||
val agendaScreenRange: AgendaRange = AgendaRange.Month,
|
||||
/** How far ahead the agenda widget shows events (v2.11). */
|
||||
|
||||
@@ -145,9 +145,15 @@ class SettingsViewModel @Inject constructor(
|
||||
prefs.showWeekNumbers,
|
||||
prefs.agendaShowToday,
|
||||
prefs.softenCalendarColors,
|
||||
combine(
|
||||
prefs.todayButtonInToolbar,
|
||||
) { hourLines, weekNumbers, showToday, soften, todayInToolbar ->
|
||||
DisplayToggles(hourLines, weekNumbers, showToday, soften, todayInToolbar)
|
||||
prefs.dragToReschedule,
|
||||
::Pair,
|
||||
),
|
||||
) { hourLines, weekNumbers, showToday, soften, (todayInToolbar, dragToMove) ->
|
||||
DisplayToggles(
|
||||
hourLines, weekNumbers, showToday, soften, todayInToolbar, dragToMove,
|
||||
)
|
||||
},
|
||||
) { view, screenRange, widgetRange, timeFormat, toggles ->
|
||||
ViewSettings(
|
||||
@@ -157,6 +163,7 @@ class SettingsViewModel @Inject constructor(
|
||||
agendaShowToday = toggles.agendaShowToday,
|
||||
softenColors = toggles.softenColors,
|
||||
todayButtonInToolbar = toggles.todayButtonInToolbar,
|
||||
dragToReschedule = toggles.dragToReschedule,
|
||||
)
|
||||
},
|
||||
combine(
|
||||
@@ -189,6 +196,7 @@ class SettingsViewModel @Inject constructor(
|
||||
agendaShowToday = views.agendaShowToday,
|
||||
softenColors = views.softenColors,
|
||||
todayButtonInToolbar = views.todayButtonInToolbar,
|
||||
dragToReschedule = views.dragToReschedule,
|
||||
agendaShowRangeBar = misc.showRangeBar,
|
||||
autofocusEventTitle = misc.autofocusEventTitle,
|
||||
pastEventDisplay = misc.pastEventDisplay,
|
||||
@@ -289,6 +297,7 @@ class SettingsViewModel @Inject constructor(
|
||||
val agendaShowToday: Boolean,
|
||||
val softenColors: Boolean,
|
||||
val todayButtonInToolbar: Boolean,
|
||||
val dragToReschedule: Boolean,
|
||||
)
|
||||
|
||||
private data class DisplayToggles(
|
||||
@@ -297,6 +306,7 @@ class SettingsViewModel @Inject constructor(
|
||||
val agendaShowToday: Boolean,
|
||||
val softenColors: Boolean,
|
||||
val todayButtonInToolbar: Boolean,
|
||||
val dragToReschedule: Boolean,
|
||||
)
|
||||
|
||||
private data class MiscSettings(
|
||||
@@ -534,6 +544,10 @@ class SettingsViewModel @Inject constructor(
|
||||
viewModelScope.launch { prefs.setTodayButtonInToolbar(enabled) }
|
||||
}
|
||||
|
||||
fun setDragToReschedule(enabled: Boolean) {
|
||||
viewModelScope.launch { prefs.setDragToReschedule(enabled) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the launcher label (#44). The card highlights at once, then settles
|
||||
* on what the component state reports; the writes are binder round-trips and
|
||||
@@ -577,15 +591,15 @@ class SettingsViewModel @Inject constructor(
|
||||
|
||||
/**
|
||||
* Enable or disable [view] in the quick-switch cycle, via an atomic
|
||||
* read-modify-write so a concurrent reorder can't clobber it. The
|
||||
* MIN_ENABLED floor is re-checked inside the transform, because the screen's
|
||||
* own guard reads an async-echoed snapshot.
|
||||
* read-modify-write so a concurrent reorder can't clobber it. Any number of
|
||||
* views may be turned off; below two the pill hides itself (#150).
|
||||
*/
|
||||
fun setQuickSwitchViewEnabled(view: CalendarView, enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
prefs.updateQuickSwitch { config ->
|
||||
val next = if (enabled) config.enabled + view else config.enabled - view
|
||||
if (next.size < QuickSwitchConfig.MIN_ENABLED) config else config.copy(enabled = next)
|
||||
config.copy(
|
||||
enabled = if (enabled) config.enabled + view else config.enabled - view,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker
|
||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
||||
import de.jeanlucmakiola.calendula.ui.common.PickerDescription
|
||||
import de.jeanlucmakiola.calendula.ui.common.QuickSwitchConfig
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineScale
|
||||
import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel
|
||||
import de.jeanlucmakiola.calendula.ui.common.descriptionRes
|
||||
@@ -55,8 +54,8 @@ import java.time.format.TextStyle as JavaTextStyle
|
||||
* it belongs to, plus the two cross-view ordering lists (#24, #69).
|
||||
*
|
||||
* The quick-switch cycle and the drawer list are independent orders — a view
|
||||
* off in the cycle is still reachable from the drawer. The cycle needs at least
|
||||
* [QuickSwitchConfig.MIN_ENABLED] targets.
|
||||
* off in the cycle is still reachable from the drawer, including when the cycle
|
||||
* is emptied and the pill disappears (#150).
|
||||
*/
|
||||
@Composable
|
||||
internal fun ViewsScreen(
|
||||
@@ -114,7 +113,7 @@ internal fun ViewsScreen(
|
||||
GroupedRow(
|
||||
title = stringResource(R.string.settings_dim_completed),
|
||||
summary = stringResource(R.string.settings_dim_completed_summary),
|
||||
position = Position.Bottom,
|
||||
position = Position.Middle,
|
||||
trailing = {
|
||||
Switch(
|
||||
checked = state.dimCompletedEvents,
|
||||
@@ -123,6 +122,18 @@ internal fun ViewsScreen(
|
||||
},
|
||||
onClick = { viewModel.setDimCompletedEvents(!state.dimCompletedEvents) },
|
||||
)
|
||||
GroupedRow(
|
||||
title = stringResource(R.string.settings_drag_to_reschedule),
|
||||
summary = stringResource(R.string.settings_drag_to_reschedule_summary),
|
||||
position = Position.Bottom,
|
||||
trailing = {
|
||||
Switch(
|
||||
checked = state.dragToReschedule,
|
||||
onCheckedChange = viewModel::setDragToReschedule,
|
||||
)
|
||||
},
|
||||
onClick = { viewModel.setDragToReschedule(!state.dragToReschedule) },
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
SectionHeader(stringResource(R.string.settings_month_header))
|
||||
@@ -209,8 +220,6 @@ internal fun ViewsScreen(
|
||||
SectionHeader(stringResource(R.string.settings_quick_switch_header))
|
||||
SettingsHint(stringResource(R.string.settings_quick_switch_hint))
|
||||
Spacer(Modifier.height(8.dp))
|
||||
// Turning a view off is blocked once only the minimum remain enabled.
|
||||
val canDisable = config.enabled.size > QuickSwitchConfig.MIN_ENABLED
|
||||
ReorderableColumn(
|
||||
items = config.order,
|
||||
keyOf = { it },
|
||||
@@ -226,8 +235,6 @@ internal fun ViewsScreen(
|
||||
trailing = {
|
||||
Switch(
|
||||
checked = checked,
|
||||
// Keep the last two on: with fewer, the pill can't switch.
|
||||
enabled = !checked || canDisable,
|
||||
onCheckedChange = { on -> viewModel.setQuickSwitchViewEnabled(view, on) },
|
||||
)
|
||||
},
|
||||
|
||||
@@ -72,7 +72,6 @@ import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.customActions
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -100,7 +99,9 @@ import de.jeanlucmakiola.calendula.ui.common.TimelineDragController
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDragOverlay
|
||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
||||
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventDragAllowed
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventTitleOverflow
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
||||
import de.jeanlucmakiola.calendula.ui.common.startInstant
|
||||
@@ -265,6 +266,7 @@ fun WeekScreen(
|
||||
currentYear = currentYear,
|
||||
selectedView = selectedView,
|
||||
onCycleView = { onSelectView(selectedView.next(quickSwitchViews)) },
|
||||
quickSwitchViews = quickSwitchViews,
|
||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||
onOpenSearch = onOpenSearch,
|
||||
onJumpToDate = jumpToDate,
|
||||
@@ -445,6 +447,7 @@ private fun WeekTopBar(
|
||||
currentYear: Int,
|
||||
selectedView: CalendarView,
|
||||
onCycleView: () -> Unit,
|
||||
quickSwitchViews: List<CalendarView>,
|
||||
onOpenDrawer: () -> Unit,
|
||||
onOpenSearch: () -> Unit,
|
||||
onJumpToDate: (LocalDate) -> Unit,
|
||||
@@ -479,6 +482,7 @@ private fun WeekTopBar(
|
||||
}
|
||||
ViewSwitcherPill(
|
||||
current = selectedView,
|
||||
cycle = quickSwitchViews,
|
||||
onCycle = onCycleView,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
@@ -655,11 +659,13 @@ private fun AllDayBar(
|
||||
.semantics { contentDescription = title },
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
val titleOverflow = eventTitleOverflow()
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = titleOverflow.overflow,
|
||||
softWrap = titleOverflow.softWrap,
|
||||
color = eventInk(fill),
|
||||
)
|
||||
}
|
||||
@@ -905,7 +911,7 @@ private fun EventBlock(
|
||||
val moveAction = eventMoveAction(block.event)
|
||||
// A block clipped at the top continues from the previous day: its top edge
|
||||
// is midnight, not the event's start, so dragging it would invent a time.
|
||||
val draggable = moveAction != null && block.beginsOn(date, zone)
|
||||
val draggable = eventDragAllowed(block.event) && block.beginsOn(date, zone)
|
||||
val dragModifier = rememberEventDragSource(
|
||||
enabled = draggable,
|
||||
key = block.event.instanceId,
|
||||
@@ -933,11 +939,13 @@ private fun EventBlock(
|
||||
) {
|
||||
Column {
|
||||
if (showTitle) {
|
||||
val titleOverflow = eventTitleOverflow(singleLine = titleMaxLines == 1)
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
maxLines = titleMaxLines,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = titleOverflow.overflow,
|
||||
softWrap = titleOverflow.softWrap,
|
||||
color = eventInk(fill, alpha = 0.85f),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
<string name="state_failure_provider">Kalender konnte nicht gelesen werden.</string>
|
||||
<!-- Permission-Flow (F1) -->
|
||||
<string name="permission_rationale_title">Alle Termine, schön im Blick</string>
|
||||
<string name="permission_rationale_body">Calendula braucht Zugriff auf deinen Kalender, um deine Termine zu zeigen und zu verwalten. Mehr braucht sie nicht um zu funktionieren — und nichts verlässt dein Gerät.</string>
|
||||
<string name="permission_rationale_body">Calendula braucht Zugriff auf deinen Kalender, um deine Termine zu zeigen und zu verwalten.</string>
|
||||
<string name="permission_request_button">Kalender-Zugriff erlauben</string>
|
||||
<string name="permission_denied_title">Kalender-Zugriff abgelehnt</string>
|
||||
<string name="permission_denied_body">Ohne Kalender-Zugriff kann Calendula keine Termine anzeigen. Du kannst den Zugriff in den System-Einstellungen wieder erlauben.</string>
|
||||
<string name="permission_open_settings_button">System-Einstellungen öffnen</string>
|
||||
<string name="permission_retry_button">Erneut versuchen</string>
|
||||
<string name="permission_benefit_private_title">Bleibt auf deinem Gerät</string>
|
||||
<string name="permission_benefit_private_body">Deine Kalender werden lokal gelesen und verlassen das Telefon nie.</string>
|
||||
<string name="permission_benefit_private_title">Datenschutz by Design</string>
|
||||
<string name="permission_benefit_private_body">Keine Internetberechtigung, keine Telemetrie — nichts verlässt dein Telefon.</string>
|
||||
<string name="permission_benefit_sync_title">Alle Kalender vereint</string>
|
||||
<string name="permission_benefit_sync_body">Google, CalDAV, lokal — alles, was synchronisiert ist, erscheint automatisch.</string>
|
||||
<string name="permission_benefit_privacy_title">Kein Tracking, niemals</string>
|
||||
@@ -310,9 +310,9 @@
|
||||
<string name="settings_language">App-Sprache</string>
|
||||
<string name="settings_language_auto">Systemstandard</string>
|
||||
<!-- Hub category subtitles -->
|
||||
<string name="settings_appearance_subtitle">Design, Standardansicht, Wochenstart</string>
|
||||
<string name="settings_event_form_subtitle">Standardfelder für neue Termine</string>
|
||||
<string name="settings_notifications_subtitle">Termin-Erinnerungen</string>
|
||||
<string name="settings_appearance_subtitle">Theme, Farben, Schriftarten</string>
|
||||
<string name="settings_event_form_subtitle">Standardfelder und Verhalten</string>
|
||||
<string name="settings_notifications_subtitle">Erinnerungen und Zustellung</string>
|
||||
<string name="settings_section_about">Über</string>
|
||||
<string name="settings_license">Lizenz</string>
|
||||
<string name="settings_license_value">MIT</string>
|
||||
@@ -424,7 +424,7 @@
|
||||
<string name="settings_calendar_reminders_title">Erinnerungen pro Kalender</string>
|
||||
<string name="settings_translate">Hilf beim Übersetzen</string>
|
||||
<string name="settings_translate_hint">Füge eine Sprache auf Weblate hinzu oder verbessere sie</string>
|
||||
<string name="settings_views_subtitle">Schnellwechsellnopf und Menüreihenfolge</string>
|
||||
<string name="settings_views_subtitle">Standartansicht, Anordnung, Menüreihenfolge</string>
|
||||
<string name="settings_special_dates_subtitle">Kontakte-Geburtstage und Jubiläen</string>
|
||||
<string name="settings_section_special_dates">Besondere Termine von Kontakten</string>
|
||||
<string name="settings_special_dates_enable">Kontakttermine anzeigen</string>
|
||||
@@ -498,4 +498,154 @@
|
||||
<string name="event_edit_timezone_none">Keine Zeitzone passt zu “%1$s”</string>
|
||||
<string name="event_edit_timezone_local_time">%1$s deiner Zeit</string>
|
||||
<string name="import_reminder_prompt_title">Deine Standarterinnerung anwenden?</string>
|
||||
<plurals name="import_reminder_prompt_body_existing">
|
||||
<item quantity="one">Dieses Event wurde mit %1$d Erinnerung importiert.</item>
|
||||
<item quantity="other">Dieses Event wurde mit %1$d Erinnerungen importiert.</item>
|
||||
</plurals>
|
||||
<string name="settings_widget_size">Agenda-Widget-Größe</string>
|
||||
<string name="import_reminder_prompt_body_none">Dieser Termin wurde ohne eine Erinnerung importiert.</string>
|
||||
<string name="import_reminder_prompt_apply">Standart anwenden</string>
|
||||
<string name="settings_widgets_subtitle">Agenda-Widget, Schnelleinstellungen-Kachel</string>
|
||||
<string name="import_reminder_prompt_keep">unverändert lassen</string>
|
||||
<string name="event_access_public_summary">Jeder mit Zugriff sieht jedes Detail</string>
|
||||
<string name="event_access_private_summary">Andere sehen nur das du beschäftigt bist</string>
|
||||
<string name="event_edit_recurrence_next">Als nächstes: %1$s</string>
|
||||
<string name="event_edit_recurrence_incomplete">Gebe eine Zahl zwischen 1 und 999 ein</string>
|
||||
<string name="timeline_scale_custom">Benutzerdefiniert</string>
|
||||
<string name="settings_calendar_duration_inherits">Standard (%1$s)</string>
|
||||
<string name="settings_group_about">Über</string>
|
||||
<string name="event_move_gone">Dieser Termin existiert nicht mehr</string>
|
||||
<string name="duration_custom_max">Maximal %1$s</string>
|
||||
<string name="reminder_day_yesterday">Gestern</string>
|
||||
<string name="agenda_no_more_today">Keine Events mehr heute</string>
|
||||
<string name="agenda_span_starts">Beginnt %1$s</string>
|
||||
<string name="agenda_span_ends">Endet %1$s</string>
|
||||
<string name="today_jump_action">Zu Heute springen</string>
|
||||
<string name="event_move_action">Verschieben…</string>
|
||||
<string name="event_move_recurring_title">Wiederkehrenden Termin verschieben</string>
|
||||
<string name="event_move_occurrence_only">Die ganze Serie zu verschieben würde ändern, auf welche Tage sie fällt, deshalb kann nur dieser Termin verschoben werden.</string>
|
||||
<string name="event_move_done">Verschoben auf %1$s</string>
|
||||
<string name="event_move_undo">Rückgängig</string>
|
||||
<string name="event_move_undone">Verschieben rückgängig gemacht</string>
|
||||
<string name="event_move_failed">Termin konnte nicht verschoben werden</string>
|
||||
<string name="event_move_write_denied">Calendula braucht Schreibzugriff, um Termine zu verschieben</string>
|
||||
<string name="event_move_blocked_series_end">Ein Termin kann nicht über das Ende seiner Serie hinaus verschoben werden</string>
|
||||
<string name="event_edit_recurrence_next_none">Diese Regel wiederholt sich nie</string>
|
||||
<string name="event_access_default_summary">Was dieser Kalender normalerweise macht</string>
|
||||
<string name="event_access_confidential_summary">Als vertraulich markiert; was das bedeutet, entscheidet das Kalenderkonto</string>
|
||||
<string name="onboarding_step_counter">Schritt %1$d von %2$d</string>
|
||||
<string name="onboarding_backup_title">Deine Termine liegen nur hier</string>
|
||||
<string name="onboarding_backup_body">Nichts auf diesem Telefon wird mit einem Konto synchronisiert – geht es verloren, ist auch dein Kalender weg. Calendula kann eine Sicherung für dich schreiben.</string>
|
||||
<string name="onboarding_backup_benefit_folder_title">Ein Ordner deiner Wahl</string>
|
||||
<string name="onboarding_backup_benefit_folder_body">Sicherungen sind einfache .ics-Dateien – lege sie irgendwohin, wo synchronisiert wird, oder auf eine SD-Karte.</string>
|
||||
<string name="onboarding_backup_benefit_daily_title">Einmal am Tag, von selbst</string>
|
||||
<string name="onboarding_backup_benefit_daily_body">Calendula exportiert deine lokalen Kalender im Hintergrund. Wie oft, änderst du in den Einstellungen.</string>
|
||||
<string name="onboarding_backup_enable_button">Ordner wählen und sichern</string>
|
||||
<string name="onboarding_view_title">Was soll zuerst geöffnet werden?</string>
|
||||
<string name="onboarding_month_style_title">Wie soll Monat aussehen?</string>
|
||||
<string name="onboarding_view_continue_button">Weiter</string>
|
||||
<string name="onboarding_visibility_button">Alles klar</string>
|
||||
<string name="onboarding_done_title">Alles bereit</string>
|
||||
<string name="onboarding_done_body">Deine Kalender sind eingerichtet. Alles, was du gerade gewählt hast, lässt sich später in den Einstellungen ändern.</string>
|
||||
<string name="onboarding_done_button">Meinen Kalender öffnen</string>
|
||||
<plurals name="search_selected_count">
|
||||
<item quantity="one">%d ausgewählt</item>
|
||||
<item quantity="other">%d ausgewählt</item>
|
||||
</plurals>
|
||||
<string name="search_in_descriptions">In Beschreibungen gefunden</string>
|
||||
<string name="search_selection_close">Auswahl abbrechen</string>
|
||||
<string name="search_select_all">Alle auswählen</string>
|
||||
<string name="search_delete_selected">Ausgewählte löschen</string>
|
||||
<string name="search_delete_partial">%1$d gelöscht, %2$d nicht</string>
|
||||
<string name="search_delete_denied_partial">%1$d gelöscht, dann wurde der Schreibzugriff entzogen</string>
|
||||
<string name="settings_theme_hint">Ob die App hell oder dunkel ist. Die Auswahl gilt sofort.</string>
|
||||
<string name="settings_theme_system_summary">Aktuell %1$s</string>
|
||||
<string name="settings_soften_colors">Kalenderfarben harmonisieren</string>
|
||||
<string name="settings_soften_colors_summary">Behält den Farbton jedes Kalenders, gleicht aber seine Helligkeit an, damit jeder Termin lesbar bleibt und die Farben zusammenpassen. Ausschalten, um die Originalfarben aus der Kalenderquelle zu zeigen.</string>
|
||||
<string name="settings_font_specimen_heading">Donnerstag, 14. Mai</string>
|
||||
<string name="settings_font_specimen_body">Team-Besprechung um 10:00, danach Mittagessen mit Robin im Café am Marktplatz.</string>
|
||||
<string name="settings_week_start_auto_summary">Aktuell %1$s</string>
|
||||
<string name="settings_today_toolbar">Heute-Schaltfläche in der Symbolleiste</string>
|
||||
<string name="settings_today_toolbar_summary">Zeigt die Schaltfläche für den Sprung zu heute in der Symbolleiste statt als schwebende Schaltfläche</string>
|
||||
<string name="settings_app_name">App-Name</string>
|
||||
<string name="settings_app_name_summary">Zeigt Calendula in deinem Launcher als „Kalender“. Nur der Name im Launcher ändert sich; das Symbol kann nach dem Wechsel an einer neuen Stelle liegen.</string>
|
||||
<string name="settings_time_format_auto_summary">Folgt dem System: %1$s</string>
|
||||
<string name="settings_timeline_scale">Stundenhöhe</string>
|
||||
<string name="settings_timeline_scale_hint">Wie viel senkrechten Platz eine Stunde in der Wochen- und Tagesansicht einnimmt. Beide Ansichten teilen sich diese Einstellung. Du kannst die Zeitleiste auch mit zwei Fingern aufziehen, um jede Höhe dazwischen einzustellen.</string>
|
||||
<string name="timeline_scale_fit_day">Ganzer Tag</string>
|
||||
<string name="timeline_scale_fit_day_summary">Alle 24 Stunden auf einem Bildschirm, ohne Scrollen</string>
|
||||
<string name="timeline_scale_compact">Kompakt</string>
|
||||
<string name="timeline_scale_compact_summary">Mehr Stunden pro Bildschirm, kleinere Blöcke</string>
|
||||
<string name="timeline_scale_regular">Normal</string>
|
||||
<string name="timeline_scale_regular_summary">Der Standardabstand</string>
|
||||
<string name="timeline_scale_comfortable">Komfortabel</string>
|
||||
<string name="timeline_scale_comfortable_summary">Größere Blöcke, mehr Scrollen</string>
|
||||
<string name="timeline_scale_custom_summary">Die Höhe, auf die du die Zeitleiste gezogen hast</string>
|
||||
<string name="settings_past_events_sample_morning">Team-Besprechung</string>
|
||||
<string name="settings_past_events_sample_midday">Mittagessen mit Robin</string>
|
||||
<string name="settings_past_events_sample_evening">Chorprobe</string>
|
||||
<string name="settings_widget_size_hint">Wie groß das Agenda-Widget auf dem Startbildschirm seinen Text zeichnet. Für das Monats-Widget gibt es keine Einstellung – sein Raster passt sich immer dem Platz an, den du ihm gibst.</string>
|
||||
<string name="settings_widget_size_small">Klein</string>
|
||||
<string name="settings_widget_size_medium">Mittel</string>
|
||||
<string name="settings_widget_size_large">Groß</string>
|
||||
<string name="settings_widget_size_extra_large">Sehr groß</string>
|
||||
<string name="settings_widget_size_summary">Termintext %1$d%%</string>
|
||||
<string name="settings_agenda_show_today">Heute immer zeigen</string>
|
||||
<string name="settings_agenda_show_today_hint">Behält Heute oben in der Agenda und ihrem Widget, auch wenn für heute nichts mehr ansteht.</string>
|
||||
<string name="settings_month_header">Monatsansicht</string>
|
||||
<string name="settings_month_view_style">Stil der Monatsansicht</string>
|
||||
<string name="month_style_paged">Seiten</string>
|
||||
<string name="month_style_paged_summary">Ein Monat füllt den Bildschirm. Wische nach links oder rechts, um den Monat zu wechseln.</string>
|
||||
<string name="month_style_continuous">Fortlaufende Monate</string>
|
||||
<string name="month_style_continuous_summary">Jeder Monat steht unter seiner eigenen Überschrift, mit etwas Abstand zum nächsten.</string>
|
||||
<string name="month_style_dense">Nahtlose Wochen</string>
|
||||
<string name="month_style_dense_summary">Die Wochen laufen ohne Unterbrechung weiter, jeder Monat geht ohne Lücke direkt in den nächsten über.</string>
|
||||
<string name="month_style_split">Geteilt</string>
|
||||
<string name="month_style_split_summary">Ein kompaktes Raster markiert die Tage mit Terminen, und der angetippte Tag wird darunter aufgelistet.</string>
|
||||
<string name="month_split_no_events">Nichts geplant</string>
|
||||
<string name="month_split_expand">Ganzen Monat zeigen</string>
|
||||
<string name="month_split_collapse">Termine des Tages zeigen</string>
|
||||
<string name="settings_event_duration">Standarddauer</string>
|
||||
<string name="settings_event_duration_hint">Wie lange ein neuer Termin dauert, bis du seine Endzeit änderst. Ganztägige Termine sind nicht betroffen.</string>
|
||||
<string name="settings_calendar_durations_title">Dauer pro Kalender</string>
|
||||
<string name="settings_calendar_durations_hint">Gib einem Kalender seine eigene Standarddauer – z. B. 8 Stunden für Arbeitsschichten.</string>
|
||||
<string name="settings_calendar_duration_use_default">Standarddauer verwenden (%1$s)</string>
|
||||
<string name="settings_allday_reminder_fires_at">Benachrichtigt um %1$s</string>
|
||||
<string name="settings_group_look">Aussehen & Verhalten</string>
|
||||
<string name="settings_group_data">Daten</string>
|
||||
<string name="settings_group_app">App</string>
|
||||
<string name="settings_backup_subtitle">Export, Import, automatische Sicherung</string>
|
||||
<string name="settings_section_widgets">Widgets & Kacheln</string>
|
||||
<string name="settings_widgets_hint">Einstellungen für die Widgets auf dem Startbildschirm und die Schnelleinstellungen-Kachel. Füge ein Widget hinzu, indem du lange auf den Startbildschirm drückst.</string>
|
||||
<string name="settings_section_backup">Sicherung & Wiederherstellung</string>
|
||||
<string name="settings_views_all_header">Alle Ansichten</string>
|
||||
<string name="settings_week_day_header">Woche & Tag</string>
|
||||
<string name="settings_default_view_hint">Die Ansicht, mit der Calendula startet.</string>
|
||||
<string name="settings_week_start_hint">Der Tag, mit dem jede Woche beginnt – in allen Ansichten und Widgets.</string>
|
||||
<string name="settings_time_format_hint">Wie Zeiten in der App geschrieben werden. Automatisch folgt deiner Systemeinstellung.</string>
|
||||
<string name="settings_past_events_hint">Was die Agenda mit Terminen macht, die bereits vorbei sind.</string>
|
||||
<string name="settings_dynamic_color_summary">Nimmt die Farben der App von deinem Hintergrundbild.</string>
|
||||
<string name="settings_about_privacy">Datenschutzerklärung</string>
|
||||
<string name="calendars_visibility_hint">Schalte einen Kalender aus, um ihn auf diesem Gerät zu verbergen – seine Termine verschwinden aus der App und er erinnert dich nicht mehr. Es ist derselbe Schalter, den deine anderen Kalender-Apps verwenden, sie verbergen ihn also ebenfalls. Nichts wird gelöscht, kein anderes Gerät ist betroffen, und du kannst ihn hier jederzeit wieder einschalten.</string>
|
||||
<string name="calendars_visibility_a11y">„%1$s“ anzeigen</string>
|
||||
<string name="calendars_visibility_notice_title">Einige Kalender sind ausgeschaltet</string>
|
||||
<string name="calendars_visibility_notice_message">Calendula zeigt die Kalender, die für dieses Gerät eingeschaltet sind, und einige von deinen sind aus. Schalte sie unter Einstellungen → Kalender ein, um ihre Termine zu sehen.</string>
|
||||
<string name="calendar_picker_missing_title">Fehlt ein Kalender?</string>
|
||||
<string name="calendar_picker_missing_summary">Er kann ausgeschaltet, schreibgeschützt oder aus deinen Kontakten gefüllt sein – verwalte deine Kalender hier.</string>
|
||||
<string name="calendars_state_read_only">Schreibgeschützt</string>
|
||||
<string name="calendars_state_not_synced">Nicht auf dieses Gerät synchronisiert</string>
|
||||
<string name="calendars_state_managed">Aus deinen Kontakten gefüllt</string>
|
||||
<string name="calendars_managed_delete_locked">Dieser Kalender wird aus deinen Kontakten gefüllt, Calendula würde ihn bei der nächsten Synchronisierung neu anlegen. Schalte die besonderen Termine unter Einstellungen → Besondere Termine aus, um ihn zu löschen.</string>
|
||||
<string name="calendars_account_from_source">%1$s (%2$s)</string>
|
||||
<string name="duration_hours_minutes">%1$s %2$s</string>
|
||||
<string name="onboarding_backup_skip_button">Nicht jetzt</string>
|
||||
<string name="onboarding_back">Zurück</string>
|
||||
<plurals name="search_delete_title">
|
||||
<item quantity="one">%d Termin löschen?</item>
|
||||
<item quantity="other">%d Termine löschen?</item>
|
||||
</plurals>
|
||||
<plurals name="search_delete_done">
|
||||
<item quantity="one">%d Termin gelöscht</item>
|
||||
<item quantity="other">%d Termine gelöscht</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -6,4 +6,205 @@
|
||||
<item>Days</item>
|
||||
<item>Weeks</item>
|
||||
</string-array>
|
||||
<string name="state_loading">Carregando…</string>
|
||||
<string name="app_name">Calendula</string>
|
||||
<string name="state_failure_permission">O acesso ao calendário é necessário.</string>
|
||||
<string name="state_failure_no_calendars">Nenhum calendário configurado.</string>
|
||||
<string name="state_failure_no_calendars_action">Abrir configurações do calendário do sistema</string>
|
||||
<string name="state_retry">Tentar novamente</string>
|
||||
<string name="state_failure_permission_action">Conceder acesso</string>
|
||||
<string name="app_tagline">Um calendário moderno.</string>
|
||||
<string name="state_failure_unknown">Algo deu errado.</string>
|
||||
<string name="state_failure_provider">Não foi possível ler o calendário.</string>
|
||||
<string name="permission_rationale_title">Veja todos os seus eventos, lindamente</string>
|
||||
<string name="permission_rationale_body">Calendula precisa acessar seu calendário para mostrar e gerenciar eventos. É tudo que ele pede — e nada sai do seu dispositivo.</string>
|
||||
<string name="permission_request_button">Conceder acesso ao calendário</string>
|
||||
<string name="permission_denied_title">Acesso ao calendário negado</string>
|
||||
<string name="permission_denied_body">Calendula não consegue mostrar eventos sem acesso ao calendário. Você pode conceder novamente nas configurações do sistema.</string>
|
||||
<string name="permission_open_settings_button">Abrir configurações do sistema</string>
|
||||
<string name="permission_retry_button">Tentar novamente</string>
|
||||
<string name="permission_benefit_private_title">Fica no seu dispositivo</string>
|
||||
<string name="permission_benefit_private_body">Seus calendários são lidos localmente e nunca saem do celular.</string>
|
||||
<string name="permission_benefit_sync_title">Todos os seus calendários, juntos</string>
|
||||
<string name="permission_benefit_sync_body">Google, CalDAV, local — tudo sincronizado ao dispositivo apenas aparece.</string>
|
||||
<string name="permission_benefit_privacy_title">Nenhum rastreamento, jamais</string>
|
||||
<string name="permission_benefit_privacy_body">Zero telemetria, zero dados para análise, sem anúncios.</string>
|
||||
<string name="permission_privacy_footnote">Fica no seu dispositivo · sem permissão de internet</string>
|
||||
<string name="month_prev">Mês anterior</string>
|
||||
<string name="month_next">Próximo mês</string>
|
||||
<string name="month_today_action">Hoje</string>
|
||||
<string name="month_more_actions">Mais ações</string>
|
||||
<string name="month_open_menu">Abrir menu</string>
|
||||
<string name="month_action_settings">Configurações</string>
|
||||
<string name="month_a11y_today_prefix">Hoje</string>
|
||||
<string name="week_today_action">Esta semana</string>
|
||||
<string name="week_number_label">Sem</string>
|
||||
<string name="day_today_action">Hoje</string>
|
||||
<string name="event_detail_back">Voltar</string>
|
||||
<string name="event_detail_edit">Editar</string>
|
||||
<string name="event_detail_delete">Excluir</string>
|
||||
<string name="event_detail_share">Compartilhar</string>
|
||||
<string name="event_detail_duplicate">Duplicar</string>
|
||||
<string name="event_share_chooser_title">Compartilhar evento</string>
|
||||
<string name="event_share_failed">Não foi possível compartilhar este evento.</string>
|
||||
<string name="event_delete_title">Excluir evento?</string>
|
||||
<string name="event_delete_body">O evento sairá do calendário e de todos os aparelhos que o sincronizam.</string>
|
||||
<string name="event_delete_recurring_title">Excluir evento recorrente</string>
|
||||
<string name="event_delete_option_occurrence">Apenas este evento</string>
|
||||
<string name="event_delete_option_following">Este evento e todos os próximos</string>
|
||||
<string name="event_delete_option_series">Todos os eventos nesta série</string>
|
||||
<string name="event_edit_recurring_title">Editar evento recorrente</string>
|
||||
<string name="event_delete_failed">Não foi possível excluir o evento</string>
|
||||
<string name="event_delete_write_denied">Calendula requer permissão de escrita para excluir eventos</string>
|
||||
<string name="dialog_cancel">Cancelar</string>
|
||||
<string name="dialog_ok">OK</string>
|
||||
<string name="event_edit_new_title">Novo evento</string>
|
||||
<string name="event_edit_close">Fechar</string>
|
||||
<string name="event_edit_save">Salvar</string>
|
||||
<string name="event_edit_title_hint">Adicionar título</string>
|
||||
<string name="event_edit_managed_hint">Gerenciado por “%1$s” — o título, data e frequência são sincronizados dos seus contatos. Lembretes, locais e notas são seus para editar.</string>
|
||||
<string name="event_edit_starts">Inicia</string>
|
||||
<string name="event_edit_ends">Termina</string>
|
||||
<string name="event_edit_error_end_before_start">Termina antes de iniciar</string>
|
||||
<string name="event_edit_error_no_calendar">Nenhum calendário disponível para escrita</string>
|
||||
<string name="event_edit_save_failed">Não foi possível salvar o evento</string>
|
||||
<string name="event_edit_write_denied">Calendula requer permissão de escrita para criar eventos</string>
|
||||
<string name="event_edit_more_fields">Mais campos</string>
|
||||
<string name="event_edit_add">Adicionar</string>
|
||||
<string name="event_edit_add_reminder">Adicionar lembrete</string>
|
||||
<string name="event_edit_remove_reminder">Remover lembrete</string>
|
||||
<string name="event_edit_attendees">Convidados</string>
|
||||
<string name="event_edit_add_guest">Adicionar convidado</string>
|
||||
<string name="event_edit_add_guest_hint">Adicionar convidado via e-mail…</string>
|
||||
<string name="event_edit_add_guest_from_contacts">Adicionar dos contatos</string>
|
||||
<string name="event_edit_location_from_contacts">Escolher endereço dos contatos</string>
|
||||
<string name="event_edit_remove_guest">Remover convidado</string>
|
||||
<string name="event_edit_attendee_required">Obrigatório</string>
|
||||
<string name="event_edit_attendee_optional">Opcional</string>
|
||||
<string name="event_edit_attendees_note_synced">Calendula não manda convites. Sua conta de calendário poderá enviar e-mails para os convidados quando sincronizar.</string>
|
||||
<string name="event_edit_attendees_note_local">Armazenado neste dispositivo. Ninguém é notificado.</string>
|
||||
<string name="event_edit_reminder_custom">Personalizado</string>
|
||||
<string name="reminder_unit_minutes">minutos</string>
|
||||
<string name="reminder_unit_hours">horas</string>
|
||||
<string name="reminder_unit_days">dias</string>
|
||||
<string name="reminder_unit_weeks">semanas</string>
|
||||
<string name="event_edit_availability">Disponibilidade</string>
|
||||
<string name="event_edit_visibility">Visibilidade</string>
|
||||
<string name="event_edit_timezone_device">Fuso horário do dispositivo</string>
|
||||
<string name="event_edit_timezone_device_summary">Segue onde você estiver</string>
|
||||
<string name="event_edit_timezone_search">Buscar fusos</string>
|
||||
<string name="event_edit_timezone_recent">Recente</string>
|
||||
<string name="event_edit_timezone_all">Todos os fusos</string>
|
||||
<string name="event_edit_timezone_none">Nenhum fuso horário encontrado com “%1$s”</string>
|
||||
<string name="event_edit_timezone_local_time">%1$s seu horário</string>
|
||||
<string name="event_edit_color">Cor</string>
|
||||
<string name="event_edit_color_default">Cor do calendário</string>
|
||||
<string name="event_edit_color_custom">Cor personalizada</string>
|
||||
<string name="event_edit_color_reset">Redefinir</string>
|
||||
<string name="event_edit_color_unsupported">Não disponível neste calendário</string>
|
||||
<string name="event_edit_color_unsupported_hint">Este calendário não publica um esquema de cores. Você pode permitir cores personalizadas nas Configurações.</string>
|
||||
<string name="event_edit_color_sync_warning">Este calendário pode remover ou sobrescrever a cor na próxima sincronização.</string>
|
||||
<string name="event_edit_conflict_title">Evento alterado em outro lugar</string>
|
||||
<string name="event_edit_conflict_body">Enquanto estava editando, este evento foi alterado — por sincronia ou por outro app. O que deve acontecer com suas mudanças?</string>
|
||||
<string name="event_edit_conflict_overwrite">Salvar mudanças</string>
|
||||
<string name="event_edit_conflict_overwrite_hint">Apenas campos editados sobrescrevem a mudança externa</string>
|
||||
<string name="event_edit_conflict_discard">Descartar mudanças</string>
|
||||
<string name="event_edit_conflict_discard_hint">Evento fica como está agora</string>
|
||||
<string name="event_edit_gone_title">Evento excluído</string>
|
||||
<string name="event_edit_gone_body">Este evento foi excluído enquanto isso, por exemplo, em outro aparelho. Suas mudanças não podem mais ser salvas.</string>
|
||||
<string name="import_reminder_prompt_title">Aplicar seu lembrete padrão?</string>
|
||||
<string name="import_reminder_prompt_body_none">Este evento foi importado sem lembretes.</string>
|
||||
<plurals name="import_reminder_prompt_body_existing">
|
||||
<item quantity="one">Este evento foi importado com %1$d lembrete.</item>
|
||||
<item quantity="many">Este evento foi importado com %1$d lembretes.</item>
|
||||
<item quantity="other">Este evento foi importado com %1$d lembretes.</item>
|
||||
</plurals>
|
||||
<string name="import_reminder_prompt_apply">Aplicar padrão</string>
|
||||
<string name="import_reminder_prompt_keep">Manter assim</string>
|
||||
<string name="event_edit_recurrence_none">Não se repete</string>
|
||||
<string name="event_edit_recurrence_custom">Personalizado</string>
|
||||
<string name="event_edit_recurrence_every">A cada</string>
|
||||
<string name="recurrence_unit_days">dias</string>
|
||||
<string name="recurrence_unit_weeks">semanas</string>
|
||||
<string name="recurrence_unit_months">meses</string>
|
||||
<string name="recurrence_unit_years">anos</string>
|
||||
<string name="event_edit_recurrence_incomplete">Insira um número de 1 a 999</string>
|
||||
<string name="event_edit_recurrence_next">Próximo: %1$s</string>
|
||||
<string name="event_edit_recurrence_next_none">Esta regra nunca se repete</string>
|
||||
<string name="event_edit_recurrence_ends">Termina</string>
|
||||
<string name="event_edit_recurrence_end_never">Nunca</string>
|
||||
<string name="event_edit_recurrence_end_until">Em uma data</string>
|
||||
<string name="event_edit_recurrence_end_count">Após um número de vezes</string>
|
||||
<string name="event_edit_recurrence_times">vezes</string>
|
||||
<string name="event_edit_error_recurrence_ends_before_start">Repetições terminam antes do evento começar</string>
|
||||
<string name="event_availability_busy">Ocupado</string>
|
||||
<string name="event_access_default">Padrão</string>
|
||||
<string name="event_access_public">Público</string>
|
||||
<string name="event_detail_all_day">O dia todo</string>
|
||||
<string name="event_detail_calendar">Calendário</string>
|
||||
<string name="event_detail_calendar_unknown">Calendário desconhecido</string>
|
||||
<string name="event_detail_location">Local</string>
|
||||
<string name="event_detail_description">Descrição</string>
|
||||
<string name="event_detail_attendees">Participantes</string>
|
||||
<string name="event_detail_recurrence">Recorrência</string>
|
||||
<string name="event_detail_recurring">Evento recorrente</string>
|
||||
<string name="recurrence_daily">Todo dia</string>
|
||||
<string name="recurrence_weekly">Toda semana</string>
|
||||
<string name="recurrence_monthly">Todo mês</string>
|
||||
<string name="recurrence_yearly">Todo ano</string>
|
||||
<string name="recurrence_every_n_days">A cada %1$d dias</string>
|
||||
<string name="recurrence_every_n_weeks">A cada %1$d semanas</string>
|
||||
<string name="recurrence_every_n_months">A cada %1$d meses</string>
|
||||
<string name="recurrence_every_n_years">A cada %1$d anos</string>
|
||||
<string name="recurrence_on_days">%1$s em %2$s</string>
|
||||
<string name="recurrence_with_until">%1$s até %2$s</string>
|
||||
<string name="recurrence_with_count">%1$s, %2$d vezes</string>
|
||||
<string name="event_detail_not_found">Este evento não existe mais.</string>
|
||||
<string name="event_attendee_accepted">Aceitou</string>
|
||||
<string name="event_attendee_declined">Rejeitou</string>
|
||||
<string name="event_attendee_tentative">Não decidiu</string>
|
||||
<string name="event_attendee_needs_action">Sem resposta</string>
|
||||
<string name="event_attendee_unknown">—</string>
|
||||
<string name="event_detail_reminders">Lembretes</string>
|
||||
<string name="event_detail_timezone">Fuso horário</string>
|
||||
<string name="event_status_cancelled">Cancelado</string>
|
||||
<string name="event_availability_free">Disponível</string>
|
||||
<string name="event_access_private">Privado</string>
|
||||
<string name="event_access_confidential">Confidencial</string>
|
||||
<string name="event_access_default_summary">O que este calendário normalmente faz</string>
|
||||
<string name="event_access_public_summary">Todos com acesso veem tudo</string>
|
||||
<string name="event_access_private_summary">Outros veem apenas que está ocupado</string>
|
||||
<string name="event_access_confidential_summary">Marcado como confidencial; o que significa depende da conta</string>
|
||||
<string name="event_attendee_organizer">Organizador</string>
|
||||
<string name="event_attendee_optional">Opcional</string>
|
||||
<string name="event_attendee_resource">Recurso</string>
|
||||
<string name="event_detail_self_response">Sua resposta: %1$s</string>
|
||||
<string name="reminder_at_time">Na hora do evento</string>
|
||||
<string name="reminder_default">Lembrete padrão</string>
|
||||
<plurals name="reminder_minutes">
|
||||
<item quantity="one">%d minuto antes</item>
|
||||
<item quantity="many">%d minutos antes</item>
|
||||
<item quantity="other">%d minutos antes</item>
|
||||
</plurals>
|
||||
<plurals name="reminder_hours">
|
||||
<item quantity="one">%d hora antes</item>
|
||||
<item quantity="many">%d horas antes</item>
|
||||
<item quantity="other">%d horas antes</item>
|
||||
</plurals>
|
||||
<plurals name="reminder_days">
|
||||
<item quantity="one">%d dia antes</item>
|
||||
<item quantity="many">%d dias antes</item>
|
||||
<item quantity="other">%d dias antes</item>
|
||||
</plurals>
|
||||
<plurals name="reminder_weeks">
|
||||
<item quantity="one">%d semana antes</item>
|
||||
<item quantity="many">%d semanas antes</item>
|
||||
<item quantity="other">%d semanas antes</item>
|
||||
</plurals>
|
||||
<plurals name="duration_minutes">
|
||||
<item quantity="one">%d minuto</item>
|
||||
<item quantity="many">%d minutos</item>
|
||||
<item quantity="other">%d minutos</item>
|
||||
</plurals>
|
||||
<string name="settings_special_dates_grant">Conceder acesso</string>
|
||||
</resources>
|
||||
|
||||
@@ -439,6 +439,8 @@
|
||||
<string name="timeline_scale_custom_summary">The height you pinched the timeline to</string>
|
||||
<string name="settings_dim_completed">Dim completed events</string>
|
||||
<string name="settings_dim_completed_summary">Fade events that have already ended in month and week view</string>
|
||||
<string name="settings_drag_to_reschedule">Drag to reschedule</string>
|
||||
<string name="settings_drag_to_reschedule_summary">Move an event by dragging it to another day or time</string>
|
||||
<string name="settings_past_events">Past events</string>
|
||||
<string name="settings_past_events_show">Show</string>
|
||||
<string name="settings_past_events_dim">Dim</string>
|
||||
@@ -494,7 +496,7 @@
|
||||
<string name="month_split_expand">Show the whole month</string>
|
||||
<string name="month_split_collapse">Show the day\'s events</string>
|
||||
<string name="settings_quick_switch_header">Quick-switch button</string>
|
||||
<string name="settings_quick_switch_hint">Choose which views the top-right button cycles through, and drag to reorder them. Turned-off views stay reachable from the navigation menu.</string>
|
||||
<string name="settings_quick_switch_hint">Choose which views the top-right button cycles through, and drag to reorder them. With fewer than two views turned on the button is hidden. Turned-off views stay reachable from the navigation menu.</string>
|
||||
<string name="settings_drawer_order_header">Navigation menu</string>
|
||||
<string name="settings_drawer_order_hint">Drag to reorder the views listed in the navigation menu.</string>
|
||||
<string name="reorder_drag_handle">Drag to reorder</string>
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
<locale android:name="fr" />
|
||||
<locale android:name="it" />
|
||||
<locale android:name="pl" />
|
||||
<locale android:name="pt-BR" />
|
||||
<locale android:name="zh-CN" />
|
||||
</locale-config>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class EventTitleOverflowTest {
|
||||
|
||||
@Test
|
||||
fun `a single LTR line clips, so the title runs to the chip's edge`() {
|
||||
val result = eventTitleOverflowFor(rtl = false, singleLine = true)
|
||||
assertThat(result.overflow).isEqualTo(TextOverflow.Clip)
|
||||
// Load-bearing: with softWrap on, a clipped line breaks at the last
|
||||
// whole word and shows less title than the ellipsis did (#164).
|
||||
assertThat(result.softWrap).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RTL keeps the ellipsis, which truncates at the logical end`() {
|
||||
// Clipping with softWrap off cuts at the node's left edge, which in RTL
|
||||
// is the end of the string — the title would lose its beginning.
|
||||
val result = eventTitleOverflowFor(rtl = true, singleLine = true)
|
||||
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
|
||||
assertThat(result.softWrap).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a multi-line block keeps the ellipsis, since wrapping needs softWrap`() {
|
||||
val result = eventTitleOverflowFor(rtl = false, singleLine = false)
|
||||
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
|
||||
assertThat(result.softWrap).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `multi-line in RTL keeps the ellipsis too`() {
|
||||
val result = eventTitleOverflowFor(rtl = true, singleLine = false)
|
||||
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
|
||||
assertThat(result.softWrap).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -120,4 +120,30 @@ class ViewBackStackTest {
|
||||
)
|
||||
assertThat(config.cycle).containsExactly(CalendarView.Agenda, CalendarView.Month).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a cycle can be emptied down to no views at all`() {
|
||||
// #150: the settings screen no longer holds a floor, so every view may go.
|
||||
val config = QuickSwitchConfig(order = IMPLEMENTED_VIEWS, enabled = emptySet())
|
||||
assertThat(config.cycle).isEmpty()
|
||||
assertThat(config.cycle.size).isLessThan(QuickSwitchConfig.MIN_CYCLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `one enabled view is below the cycle minimum, so the pill hides`() {
|
||||
// A single target is not a switch — it hides rather than becoming a
|
||||
// jump-to-one-view button, which would be dead once you were there.
|
||||
val config = QuickSwitchConfig(order = IMPLEMENTED_VIEWS, enabled = setOf(CalendarView.Day))
|
||||
assertThat(config.cycle).containsExactly(CalendarView.Day)
|
||||
assertThat(config.cycle.size).isLessThan(QuickSwitchConfig.MIN_CYCLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two enabled views are enough to show the pill`() {
|
||||
val config = QuickSwitchConfig(
|
||||
order = IMPLEMENTED_VIEWS,
|
||||
enabled = setOf(CalendarView.Day, CalendarView.Month),
|
||||
)
|
||||
assertThat(config.cycle.size).isAtLeast(QuickSwitchConfig.MIN_CYCLE)
|
||||
}
|
||||
}
|
||||
|
||||
3
fastlane/metadata/android/ar/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/ar/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
جديد
|
||||
• صار بالإمكان إيقاف السحب لتغيير الموعد: الإعدادات ← العروض ← «السحب لتغيير الموعد». يبقى مفعّلاً افتراضياً؛ وعند إيقافه لا يمكن سحب الأحداث في عرض الشهر أو الأسبوع أو اليوم.
|
||||
• صار Calendula يتحدّث البرتغالية البرازيلية بفضل ترجمة من المجتمع. اخترها من الإعدادات ← اللغة.
|
||||
3
fastlane/metadata/android/de-DE/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/de-DE/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Neu
|
||||
• Verschieben per Drag & Drop lässt sich abschalten: Einstellungen → Ansichten → „Per Ziehen verschieben“. Standardmäßig bleibt es an; abgeschaltet lassen sich Termine in der Monats-, Wochen- und Tagesansicht nicht mehr aufnehmen.
|
||||
• Calendula spricht jetzt brasilianisches Portugiesisch – dank einer Community-Übersetzung. Auswählbar unter Einstellungen → Sprache.
|
||||
3
fastlane/metadata/android/en-GB/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/en-GB/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Added
|
||||
• Drag to reschedule can now be turned off: Settings → Views → “Drag to reschedule”. It stays on by default; switched off, events can no longer be picked up in the month, week or day view.
|
||||
• Calendula now speaks Brazilian Portuguese, thanks to a community translation. Pick it under Settings → Language.
|
||||
3
fastlane/metadata/android/en-US/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Added
|
||||
• Drag to reschedule can now be turned off: Settings → Views → “Drag to reschedule”. It stays on by default; switched off, events can no longer be picked up in the month, week or day view.
|
||||
• Calendula now speaks Brazilian Portuguese, thanks to a community translation. Pick it under Settings → Language.
|
||||
3
fastlane/metadata/android/es-ES/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/es-ES/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Novedades
|
||||
• Ahora se puede desactivar el mover eventos arrastrando: Ajustes → Vistas → «Arrastrar para reprogramar». Sigue activado por defecto; desactivado, los eventos ya no se pueden arrastrar en las vistas de mes, semana y día.
|
||||
• Calendula ya habla portugués de Brasil, gracias a una traducción de la comunidad. Elígelo en Ajustes → Idioma.
|
||||
3
fastlane/metadata/android/fr-FR/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/fr-FR/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Nouveautés
|
||||
• Le déplacement par glisser-déposer peut désormais être désactivé : Réglages → Vues → « Glisser pour déplacer ». Il reste activé par défaut ; désactivé, les événements ne peuvent plus être saisis en vue mois, semaine ou jour.
|
||||
• Calendula parle désormais le portugais brésilien, grâce à une traduction de la communauté. À choisir dans Réglages → Langue.
|
||||
3
fastlane/metadata/android/it-IT/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/it-IT/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Novità
|
||||
• Ora lo spostamento con il trascinamento si può disattivare: Impostazioni → Viste → «Trascina per spostare». Resta attivo di default; disattivato, gli eventi non si possono più trascinare nelle viste mese, settimana e giorno.
|
||||
• Calendula ora parla portoghese brasiliano, grazie a una traduzione della comunità. Si sceglie in Impostazioni → Lingua.
|
||||
3
fastlane/metadata/android/pl-PL/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/pl-PL/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Nowości
|
||||
• Przenoszenie wydarzeń przeciąganiem można teraz wyłączyć: Ustawienia → Widoki → „Przeciągnij, aby przenieść”. Domyślnie pozostaje włączone; po wyłączeniu wydarzeń nie da się już chwycić w widoku miesiąca, tygodnia i dnia.
|
||||
• Calendula mówi teraz po portugalsku (Brazylia) dzięki tłumaczeniu społeczności. Wybierzesz je w Ustawienia → Język.
|
||||
3
fastlane/metadata/android/pt-BR/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/pt-BR/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Novidades
|
||||
• Agora é possível desativar o mover eventos arrastando: Configurações → Visualizações → “Arrastar para reagendar”. Continua ativo por padrão; desativado, os eventos não podem mais ser arrastados nas visualizações de mês, semana e dia.
|
||||
• O Calendula agora fala português do Brasil, graças a uma tradução da comunidade. Escolha em Configurações → Idioma.
|
||||
3
fastlane/metadata/android/pt-PT/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/pt-PT/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Novidades
|
||||
• Já é possível desativar o mover eventos arrastando: Definições → Vistas → «Arrastar para reagendar». Continua ativo por predefinição; desativado, os eventos deixam de poder ser arrastados nas vistas de mês, semana e dia.
|
||||
• O Calendula fala agora português do Brasil, graças a uma tradução da comunidade. Escolha em Definições → Idioma.
|
||||
3
fastlane/metadata/android/ru-RU/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/ru-RU/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Новое
|
||||
• Перенос событий перетаскиванием теперь можно отключить: Настройки → Виды → «Перетаскивание для переноса». По умолчанию включён; при отключении события больше нельзя подхватить в видах месяца, недели и дня.
|
||||
• Calendula теперь говорит на бразильском португальском — благодаря переводу сообщества. Выбрать можно в Настройки → Язык.
|
||||
3
fastlane/metadata/android/zh-CN/changelogs/21901.txt
Normal file
3
fastlane/metadata/android/zh-CN/changelogs/21901.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
新增
|
||||
• 现在可以关闭拖动改期:设置 → 视图 → “拖动改期”。默认保持开启;关闭后,月、周、日视图中的事件将无法再被拖起。
|
||||
• Calendula 现已支持巴西葡萄牙语,感谢社区翻译。可在设置 → 语言中选择。
|
||||
Submodule floret-kit updated: 05c79f2afc...ad440e8cac
Reference in New Issue
Block a user