Compare commits
7 Commits
fix/214-wi
...
96e3bff877
| Author | SHA1 | Date | |
|---|---|---|---|
| 96e3bff877 | |||
| 2f3980f3c4 | |||
| a1582d5a91 | |||
|
|
9dc83ce15b | ||
|
|
342af07e09 | ||
|
|
007e8ab8f6 | ||
|
|
4e38866dfd |
31
CHANGELOG.md
31
CHANGELOG.md
@@ -7,29 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [2.19.1] — 2026-08-11
|
### Changed
|
||||||
|
- Month-view event titles no longer end in an ellipsis. The "…" took the width
|
||||||
### Added
|
of a couple of characters and told you nothing you couldn't already see, so
|
||||||
- **Drag to reschedule can be turned off.** Settings → Views → *Drag to
|
the title now simply runs to the edge of its chip — a few more letters per
|
||||||
reschedule*, on by default. Off, no event can be picked up in the month, week
|
cell, which is often the difference between two events you can tell apart and
|
||||||
or day view — for anyone who kept moving appointments by accident while
|
two you can't ([#164]).
|
||||||
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
|
## [2.19.0] — 2026-08-10
|
||||||
|
|
||||||
@@ -1413,4 +1396,4 @@ automatically, with zero telemetry and no internet permission.
|
|||||||
[#80]: https://codeberg.org/jlmakiola/calendula/issues/80
|
[#80]: https://codeberg.org/jlmakiola/calendula/issues/80
|
||||||
[#123]: https://codeberg.org/jlmakiola/calendula/issues/123
|
[#123]: https://codeberg.org/jlmakiola/calendula/issues/123
|
||||||
[#163]: https://codeberg.org/jlmakiola/calendula/issues/163
|
[#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 +
|
// which builds this version and then creates the matching vX.Y.Z tag +
|
||||||
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
||||||
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
||||||
versionCode = 21902
|
versionCode = 21900
|
||||||
versionName = "2.19.2"
|
versionName = "2.19.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package de.jeanlucmakiola.calendula.data.calendar
|
package de.jeanlucmakiola.calendula.data.calendar
|
||||||
|
|
||||||
import de.jeanlucmakiola.floret.time.toKotlinInstantFromEpochMillis
|
import de.jeanlucmakiola.floret.time.toKotlinInstantFromEpochMillis
|
||||||
import android.provider.CalendarContract
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
|
|
||||||
@@ -39,7 +38,5 @@ internal fun ColumnReader.toEventInstance(): EventInstance? {
|
|||||||
isAllDay = getInt(InstanceProjection.IDX_ALL_DAY) != 0,
|
isAllDay = getInt(InstanceProjection.IDX_ALL_DAY) != 0,
|
||||||
color = color,
|
color = color,
|
||||||
location = getString(InstanceProjection.IDX_LOCATION),
|
location = getString(InstanceProjection.IDX_LOCATION),
|
||||||
isDeclined = getInt(InstanceProjection.IDX_SELF_ATTENDEE_STATUS) ==
|
|
||||||
CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ internal object InstanceProjection {
|
|||||||
CalendarContract.Instances.EVENT_COLOR,
|
CalendarContract.Instances.EVENT_COLOR,
|
||||||
CalendarContract.Instances.CALENDAR_COLOR,
|
CalendarContract.Instances.CALENDAR_COLOR,
|
||||||
CalendarContract.Instances.EVENT_LOCATION,
|
CalendarContract.Instances.EVENT_LOCATION,
|
||||||
CalendarContract.Instances.SELF_ATTENDEE_STATUS,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const val IDX_INSTANCE_ID = 0
|
const val IDX_INSTANCE_ID = 0
|
||||||
@@ -65,7 +64,6 @@ internal object InstanceProjection {
|
|||||||
const val IDX_EVENT_COLOR = 7
|
const val IDX_EVENT_COLOR = 7
|
||||||
const val IDX_CALENDAR_COLOR = 8
|
const val IDX_CALENDAR_COLOR = 8
|
||||||
const val IDX_LOCATION = 9
|
const val IDX_LOCATION = 9
|
||||||
const val IDX_SELF_ATTENDEE_STATUS = 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object EventDetailProjection {
|
internal object EventDetailProjection {
|
||||||
@@ -184,7 +182,6 @@ internal object SearchProjection {
|
|||||||
CalendarContract.Events.RDATE,
|
CalendarContract.Events.RDATE,
|
||||||
// Excerpted, not just filtered on: a hit has to show what it matched.
|
// Excerpted, not just filtered on: a hit has to show what it matched.
|
||||||
CalendarContract.Events.DESCRIPTION,
|
CalendarContract.Events.DESCRIPTION,
|
||||||
CalendarContract.Events.SELF_ATTENDEE_STATUS,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const val IDX_ID = 0
|
const val IDX_ID = 0
|
||||||
@@ -200,7 +197,6 @@ internal object SearchProjection {
|
|||||||
const val IDX_RRULE = 10
|
const val IDX_RRULE = 10
|
||||||
const val IDX_RDATE = 11
|
const val IDX_RDATE = 11
|
||||||
const val IDX_DESCRIPTION = 12
|
const val IDX_DESCRIPTION = 12
|
||||||
const val IDX_SELF_ATTENDEE_STATUS = 13
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.jeanlucmakiola.calendula.data.calendar
|
package de.jeanlucmakiola.calendula.data.calendar
|
||||||
|
|
||||||
import android.provider.CalendarContract
|
|
||||||
import de.jeanlucmakiola.floret.time.toKotlinInstantFromEpochMillis
|
import de.jeanlucmakiola.floret.time.toKotlinInstantFromEpochMillis
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.domain.ics.parseRfc2445DurationMillis
|
import de.jeanlucmakiola.calendula.domain.ics.parseRfc2445DurationMillis
|
||||||
@@ -43,7 +42,5 @@ internal fun ColumnReader.toSearchResult(): EventInstance? {
|
|||||||
location = getString(SearchProjection.IDX_LOCATION),
|
location = getString(SearchProjection.IDX_LOCATION),
|
||||||
isRecurring = !getString(SearchProjection.IDX_RRULE).isNullOrEmpty() ||
|
isRecurring = !getString(SearchProjection.IDX_RRULE).isNullOrEmpty() ||
|
||||||
!getString(SearchProjection.IDX_RDATE).isNullOrEmpty(),
|
!getString(SearchProjection.IDX_RDATE).isNullOrEmpty(),
|
||||||
isDeclined = getInt(SearchProjection.IDX_SELF_ATTENDEE_STATUS) ==
|
|
||||||
CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,19 +301,6 @@ class SettingsPrefs @Inject constructor(
|
|||||||
store.edit { it[TODAY_BUTTON_IN_TOOLBAR_KEY] = enabled }
|
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
|
* How far ahead the in-app Agenda screen shows events (v2.11). Defaults to
|
||||||
* [AgendaRange.Month] — a month of upcoming events. Independent of the
|
* [AgendaRange.Month] — a month of upcoming events. Independent of the
|
||||||
@@ -958,7 +945,6 @@ class SettingsPrefs @Inject constructor(
|
|||||||
internal val MONTH_VIEW_STYLE_KEY = stringPreferencesKey("month_view_style")
|
internal val MONTH_VIEW_STYLE_KEY = stringPreferencesKey("month_view_style")
|
||||||
internal val TIMELINE_SCALE_KEY = stringPreferencesKey("timeline_scale")
|
internal val TIMELINE_SCALE_KEY = stringPreferencesKey("timeline_scale")
|
||||||
internal val TODAY_BUTTON_IN_TOOLBAR_KEY = booleanPreferencesKey("today_button_in_toolbar")
|
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 DEFAULT_VIEW_KEY = stringPreferencesKey("default_view")
|
||||||
internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views")
|
internal val QUICK_SWITCH_VIEWS_KEY = stringPreferencesKey("quick_switch_views")
|
||||||
internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order")
|
internal val DRAWER_VIEW_ORDER_KEY = stringPreferencesKey("drawer_view_order")
|
||||||
|
|||||||
@@ -44,16 +44,9 @@ class ProviderReminderInstanceSource @Inject constructor(
|
|||||||
// `visible` is the flag the app's one visibility model writes (#75).
|
// `visible` is the flag the app's one visibility model writes (#75).
|
||||||
// The status clause mirrors CalendarDataSource.instances: NULL means
|
// The status clause mirrors CalendarDataSource.instances: NULL means
|
||||||
// "normal", so a bare `!= CANCELED` would drop every ordinary event.
|
// "normal", so a bare `!= CANCELED` would drop every ordinary event.
|
||||||
// An invitation the user declined is answered — it stays on the calendar
|
|
||||||
// struck through, but it plans nothing (#180). NULL again means "no
|
|
||||||
// answer recorded", which is not a "no".
|
|
||||||
val selection = "${CalendarContract.Calendars.VISIBLE} = 1 AND " +
|
val selection = "${CalendarContract.Calendars.VISIBLE} = 1 AND " +
|
||||||
"(${CalendarContract.Instances.STATUS} IS NULL OR " +
|
"(${CalendarContract.Instances.STATUS} IS NULL OR " +
|
||||||
"${CalendarContract.Instances.STATUS} != " +
|
"${CalendarContract.Instances.STATUS} != ${CalendarContract.Events.STATUS_CANCELED})"
|
||||||
"${CalendarContract.Events.STATUS_CANCELED}) AND " +
|
|
||||||
"(${CalendarContract.Instances.SELF_ATTENDEE_STATUS} IS NULL OR " +
|
|
||||||
"${CalendarContract.Instances.SELF_ATTENDEE_STATUS} != " +
|
|
||||||
"${CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED})"
|
|
||||||
return context.contentResolver.query(
|
return context.contentResolver.query(
|
||||||
uri, OCCURRENCE_PROJECTION, selection, null, null,
|
uri, OCCURRENCE_PROJECTION, selection, null, null,
|
||||||
)?.use { c ->
|
)?.use { c ->
|
||||||
|
|||||||
@@ -67,13 +67,6 @@ data class EventInstance(
|
|||||||
* Instances query already yields one row per occurrence.
|
* Instances query already yields one row per occurrence.
|
||||||
*/
|
*/
|
||||||
val isRecurring: Boolean = false,
|
val isRecurring: Boolean = false,
|
||||||
/**
|
|
||||||
* This device user answered "no" to the invitation
|
|
||||||
* (`Events.SELF_ATTENDEE_STATUS`). The event stays on the calendar — it is
|
|
||||||
* still an appointment someone expects an answer about — but every surface
|
|
||||||
* strikes it through, and it plans no reminders (#180).
|
|
||||||
*/
|
|
||||||
val isDeclined: Boolean = false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -312,13 +312,9 @@ fun CalendarHost(
|
|||||||
heldEditKey = key
|
heldEditKey = key
|
||||||
editKey = key
|
editKey = key
|
||||||
}
|
}
|
||||||
// Off by preference (#173) switches off the gesture, not the scope: blocks
|
val moveScope = remember(movableCalendarIds, reschedule) {
|
||||||
// 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(
|
EventMoveScope(
|
||||||
movableCalendarIds = movableCalendarIds,
|
movableCalendarIds = movableCalendarIds,
|
||||||
dragEnabled = dragToReschedule,
|
|
||||||
move = reschedule::move,
|
move = reschedule::move,
|
||||||
inFlight = reschedule.inFlight,
|
inFlight = reschedule.inFlight,
|
||||||
undoStarted = reschedule.undoStarted,
|
undoStarted = reschedule.undoStarted,
|
||||||
|
|||||||
@@ -53,18 +53,4 @@ class CalendarHostViewModel @Inject constructor(
|
|||||||
started = SharingStarted.WhileSubscribed(5_000L),
|
started = SharingStarted.WhileSubscribed(5_000L),
|
||||||
initialValue = false,
|
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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,11 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.declinedTitle
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventAccent
|
import de.jeanlucmakiola.calendula.ui.common.eventAccent
|
||||||
@@ -131,8 +129,8 @@ internal fun AgendaEventRow(
|
|||||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
modifier = if (dimmed) modifier.alpha(EventDimAlpha) else modifier,
|
modifier = if (dimmed) modifier.alpha(EventDimAlpha) else modifier,
|
||||||
title = declinedTitle(title, event.isDeclined),
|
title = title,
|
||||||
summary = AnnotatedString(agendaTimeSummary(event, day, zone)),
|
summary = agendaTimeSummary(event, day, zone),
|
||||||
position = position,
|
position = position,
|
||||||
minHeight = 64.dp,
|
minHeight = 64.dp,
|
||||||
leading = {
|
leading = {
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ class EventMoveScope(
|
|||||||
* the repository writes whatever it is handed — so this gate is load-bearing.
|
* the repository writes whatever it is handed — so this gate is load-bearing.
|
||||||
*/
|
*/
|
||||||
val movableCalendarIds: Set<Long>,
|
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. */
|
/** False when the drop was refused outright, so nothing will be written. */
|
||||||
val move: (MoveRequest) -> Boolean,
|
val move: (MoveRequest) -> Boolean,
|
||||||
/**
|
/**
|
||||||
@@ -97,17 +91,6 @@ fun ghostAlpha(lifted: Boolean): Float = animateFloatAsState(
|
|||||||
label = "ghost-alpha",
|
label = "ghost-alpha",
|
||||||
).value
|
).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
|
* A TalkBack action that opens [event] in the edit form, so rescheduling isn't
|
||||||
* pointer-only. Null when this event can't be moved.
|
* pointer-only. Null when this event can't be moved.
|
||||||
|
|||||||
@@ -36,14 +36,6 @@ val GUTTER_WIDTH = 48.dp
|
|||||||
*/
|
*/
|
||||||
val GUTTER_CONTENT_START_INSET = 8.dp
|
val GUTTER_CONTENT_START_INSET = 8.dp
|
||||||
|
|
||||||
/**
|
|
||||||
* End inset for everything that lines up with the day columns — the week's day
|
|
||||||
* header, the all-day strip and the timeline itself. Without it the last column
|
|
||||||
* runs flush into the screen edge while the gutter gives the other side room
|
|
||||||
* (#192); 8dp mirrors [GUTTER_CONTENT_START_INSET].
|
|
||||||
*/
|
|
||||||
val TIMELINE_CONTENT_END_INSET = 8.dp
|
|
||||||
|
|
||||||
private val BADGE_HEIGHT = 20.dp
|
private val BADGE_HEIGHT = 20.dp
|
||||||
|
|
||||||
/** How far the fixed hour labels recede while a block is being dragged. */
|
/** How far the fixed hour labels recede while a block is being dragged. */
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.common
|
package de.jeanlucmakiola.calendula.ui.common
|
||||||
|
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,30 +14,3 @@ val LocalDimCutoff = compositionLocalOf<Instant?> { null }
|
|||||||
|
|
||||||
/** Opacity applied to a completed/past event chip when it is dimmed. */
|
/** Opacity applied to a completed/past event chip when it is dimmed. */
|
||||||
const val EventDimAlpha = 0.4f
|
const val EventDimAlpha = 0.4f
|
||||||
|
|
||||||
/**
|
|
||||||
* How a declined invitation's title is struck through (#180) — the mark every
|
|
||||||
* surface uses to say "you answered no", chosen over hiding the event because it
|
|
||||||
* is still something the organiser expects you at. Null for everything else, so
|
|
||||||
* it drops straight into a `Text`'s `textDecoration`.
|
|
||||||
*/
|
|
||||||
fun declinedDecoration(isDeclined: Boolean): TextDecoration? =
|
|
||||||
if (isDeclined) TextDecoration.LineThrough else null
|
|
||||||
|
|
||||||
/** [declinedDecoration] for the rows that take styled text rather than a `String`. */
|
|
||||||
fun declinedTitle(title: String, isDeclined: Boolean): AnnotatedString = if (isDeclined) {
|
|
||||||
AnnotatedString(title, SpanStyle(textDecoration = TextDecoration.LineThrough))
|
|
||||||
} else {
|
|
||||||
AnnotatedString(title)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [declinedTitle] over already-styled text, e.g. a search hit's marked runs. */
|
|
||||||
fun declinedTitle(title: AnnotatedString, isDeclined: Boolean): AnnotatedString =
|
|
||||||
if (!isDeclined) {
|
|
||||||
title
|
|
||||||
} else {
|
|
||||||
buildAnnotatedString {
|
|
||||||
append(title)
|
|
||||||
addStyle(SpanStyle(textDecoration = TextDecoration.LineThrough), 0, title.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ import de.jeanlucmakiola.calendula.ui.common.TimelineDragController
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDragOverlay
|
import de.jeanlucmakiola.calendula.ui.common.TimelineDragOverlay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
||||||
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
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.eventMoveAction
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
||||||
@@ -106,7 +105,6 @@ import de.jeanlucmakiola.calendula.ui.common.next
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventFill
|
import de.jeanlucmakiola.calendula.ui.common.eventFill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventInk
|
import de.jeanlucmakiola.calendula.ui.common.eventInk
|
||||||
import de.jeanlucmakiola.calendula.ui.common.declinedDecoration
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
||||||
@@ -119,7 +117,6 @@ import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.GUTTER_WIDTH
|
import de.jeanlucmakiola.calendula.ui.common.GUTTER_WIDTH
|
||||||
import de.jeanlucmakiola.calendula.ui.common.HourGutter
|
import de.jeanlucmakiola.calendula.ui.common.HourGutter
|
||||||
import de.jeanlucmakiola.calendula.ui.common.TIMELINE_CONTENT_END_INSET
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
||||||
import de.jeanlucmakiola.calendula.ui.common.tappedMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.tappedMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
||||||
@@ -473,11 +470,7 @@ private fun AllDayStrip(
|
|||||||
// Height is hoisted + animated so it resizes smoothly; padding sits
|
// Height is hoisted + animated so it resizes smoothly; padding sits
|
||||||
// inside it so the content area is lanes * row height.
|
// inside it so the content area is lanes * row height.
|
||||||
.height(height)
|
.height(height)
|
||||||
.padding(
|
.padding(vertical = ALL_DAY_VERTICAL_PADDING),
|
||||||
top = ALL_DAY_VERTICAL_PADDING,
|
|
||||||
bottom = ALL_DAY_VERTICAL_PADDING,
|
|
||||||
end = TIMELINE_CONTENT_END_INSET,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
// Keep the gutter-width offset so the bars line up with the day column.
|
// Keep the gutter-width offset so the bars line up with the day column.
|
||||||
Spacer(Modifier.width(GUTTER_WIDTH))
|
Spacer(Modifier.width(GUTTER_WIDTH))
|
||||||
@@ -531,7 +524,6 @@ private fun AllDayBar(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = eventInk(fill),
|
color = eventInk(fill),
|
||||||
textDecoration = declinedDecoration(event.isDeclined),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,7 +569,6 @@ private fun Timeline(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(end = TIMELINE_CONTENT_END_INSET)
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
.onGloballyPositioned { dragController.geometry.viewport = it },
|
.onGloballyPositioned { dragController.geometry.viewport = it },
|
||||||
@@ -746,7 +737,7 @@ private fun EventBlock(
|
|||||||
val moveAction = eventMoveAction(block.event)
|
val moveAction = eventMoveAction(block.event)
|
||||||
// A block clipped at the top continues from the previous day: its top edge
|
// 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.
|
// is midnight, not the event's start, so dragging it would invent a time.
|
||||||
val draggable = eventDragAllowed(block.event) && block.beginsOn(date, zone)
|
val draggable = moveAction != null && block.beginsOn(date, zone)
|
||||||
val dragModifier = rememberEventDragSource(
|
val dragModifier = rememberEventDragSource(
|
||||||
enabled = draggable,
|
enabled = draggable,
|
||||||
key = block.event.instanceId,
|
key = block.event.instanceId,
|
||||||
@@ -780,7 +771,6 @@ private fun EventBlock(
|
|||||||
maxLines = if (showTime) 1 else 2,
|
maxLines = if (showTime) 1 else 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = eventInk(fill, alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
textDecoration = declinedDecoration(block.event.isDeclined),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (showTime) {
|
if (showTime) {
|
||||||
@@ -801,12 +791,7 @@ private fun DayLoading() {
|
|||||||
// Same scale resolution as the loaded timeline, so the skeleton's column
|
// Same scale resolution as the loaded timeline, so the skeleton's column
|
||||||
// doesn't resize the moment the real day arrives.
|
// doesn't resize the moment the real day arrives.
|
||||||
val totalHeight = scale.hourHeight(maxHeight) * 24
|
val totalHeight = scale.hourHeight(maxHeight) * 24
|
||||||
Row(
|
Row(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(end = TIMELINE_CONTENT_END_INSET)
|
|
||||||
.verticalScroll(scrollState),
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.width(GUTTER_WIDTH))
|
Spacer(Modifier.width(GUTTER_WIDTH))
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -78,15 +78,9 @@ class EventDetailViewModel @Inject constructor(
|
|||||||
* the occurrence's own times (from `CalendarContract.Instances`); they
|
* the occurrence's own times (from `CalendarContract.Instances`); they
|
||||||
* override the series DTSTART/DTEND so recurring events show the correct
|
* override the series DTSTART/DTEND so recurring events show the correct
|
||||||
* date instead of the first occurrence.
|
* date instead of the first occurrence.
|
||||||
*
|
|
||||||
* Re-opening the *same* occurrence always re-reads it. The view model
|
|
||||||
* outlives the sheet, and a `StateFlow` conflates an identical value away,
|
|
||||||
* so assigning the target alone would leave an edit that changed no time —
|
|
||||||
* adding a description, say (#196) — showing the state from before the save.
|
|
||||||
*/
|
*/
|
||||||
fun open(eventId: Long, beginMillis: Long, endMillis: Long) {
|
fun open(eventId: Long, beginMillis: Long, endMillis: Long) {
|
||||||
val target = Target(eventId, beginMillis, endMillis)
|
_target.value = Target(eventId, beginMillis, endMillis)
|
||||||
if (_target.value == target) _reload.value += 1 else _target.value = target
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Re-run the current load after a failure. */
|
/** Re-run the current load after a failure. */
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import androidx.compose.animation.togetherWith
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
|
||||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
|
||||||
import androidx.compose.foundation.gestures.detectDragGestures
|
import androidx.compose.foundation.gestures.detectDragGestures
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -113,7 +111,6 @@ import androidx.compose.ui.geometry.Size
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -141,7 +138,6 @@ import de.jeanlucmakiola.calendula.ui.common.CALENDAR_SWIPE_THRESHOLD
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||||
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.declinedDecoration
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
|
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventAccent
|
import de.jeanlucmakiola.calendula.ui.common.eventAccent
|
||||||
@@ -443,7 +439,6 @@ fun MonthScreen(
|
|||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onRetry = jumpToToday,
|
onRetry = jumpToToday,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
)
|
)
|
||||||
} else if (viewStyle == MonthViewStyle.Split) {
|
} else if (viewStyle == MonthViewStyle.Split) {
|
||||||
SplitMonthContent(
|
SplitMonthContent(
|
||||||
@@ -468,7 +463,6 @@ fun MonthScreen(
|
|||||||
onSwipePrev = goPrev,
|
onSwipePrev = goPrev,
|
||||||
onRetry = jumpToToday,
|
onRetry = jumpToToday,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,7 +573,6 @@ private fun MonthContent(
|
|||||||
onSwipePrev: () -> Unit,
|
onSwipePrev: () -> Unit,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val slideSpec = rememberCalendarSlideSpec()
|
val slideSpec = rememberCalendarSlideSpec()
|
||||||
val fadeSpec = rememberCalendarFadeSpec()
|
val fadeSpec = rememberCalendarFadeSpec()
|
||||||
@@ -606,7 +599,6 @@ private fun MonthContent(
|
|||||||
state = s,
|
state = s,
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -625,7 +617,6 @@ private fun ContinuousMonthContent(
|
|||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
onRetry: () -> Unit,
|
onRetry: () -> Unit,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
when (state) {
|
when (state) {
|
||||||
// The scrolling styles get their own skeleton rather than the paged
|
// The scrolling styles get their own skeleton rather than the paged
|
||||||
@@ -640,7 +631,6 @@ private fun ContinuousMonthContent(
|
|||||||
listState = listState,
|
listState = listState,
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ContinuousMonthGrid(
|
ContinuousMonthGrid(
|
||||||
@@ -648,7 +638,6 @@ private fun ContinuousMonthContent(
|
|||||||
listState = listState,
|
listState = listState,
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,8 +736,7 @@ private val CELL_SHAPE = RoundedCornerShape(CELL_CORNER)
|
|||||||
|
|
||||||
/** Width of the split style's selected-day outline. */
|
/** Width of the split style's selected-day outline. */
|
||||||
private val SPLIT_SELECTION_STROKE = 1.5.dp
|
private val SPLIT_SELECTION_STROKE = 1.5.dp
|
||||||
/** Lanes of bars/pills a day cell draws before the rest become overflow dots. */
|
private const val MAX_EVENT_ROWS = 3
|
||||||
internal const val MAX_EVENT_ROWS = 3
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Row height in the continuous grid. The paged grid divides the viewport between
|
* Row height in the continuous grid. The paged grid divides the viewport between
|
||||||
@@ -766,15 +754,11 @@ private val CONTINUOUS_ROW_HEIGHT = 112.dp
|
|||||||
*/
|
*/
|
||||||
private val CONTINUOUS_MONTH_GAP = 20.dp
|
private val CONTINUOUS_MONTH_GAP = 20.dp
|
||||||
|
|
||||||
/** Gap between the weekday header and the seamless stream's first week row. */
|
|
||||||
private val DENSE_HEADER_GAP = 4.dp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MonthGrid(
|
internal fun MonthGrid(
|
||||||
state: MonthUiState.Success,
|
state: MonthUiState.Success,
|
||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
/** See [MonthWeekRow]'s `selected`: an anchor for the morph, never a mark. */
|
/** See [MonthWeekRow]'s `selected`: an anchor for the morph, never a mark. */
|
||||||
selected: LocalDate? = null,
|
selected: LocalDate? = null,
|
||||||
) {
|
) {
|
||||||
@@ -795,7 +779,6 @@ internal fun MonthGrid(
|
|||||||
inMonth = { it.month == month.month && it.year == month.year },
|
inMonth = { it.month == month.month && it.year == month.year },
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
selected = selected,
|
selected = selected,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -826,7 +809,6 @@ internal fun ContinuousMonthGrid(
|
|||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val monthCount = remember { continuousMonthCount() }
|
val monthCount = remember { continuousMonthCount() }
|
||||||
@@ -854,7 +836,6 @@ internal fun ContinuousMonthGrid(
|
|||||||
today = state.today,
|
today = state.today,
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,7 +855,6 @@ private fun ContinuousMonthBlock(
|
|||||||
today: LocalDate,
|
today: LocalDate,
|
||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val rowCount = remember(month, weekStart) { weekRowsInMonth(month, weekStart) }
|
val rowCount = remember(month, weekStart) { weekRowsInMonth(month, weekStart) }
|
||||||
Column(
|
Column(
|
||||||
@@ -897,7 +877,6 @@ private fun ContinuousMonthBlock(
|
|||||||
blankOutside = true,
|
blankOutside = true,
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(CONTINUOUS_ROW_HEIGHT),
|
.height(CONTINUOUS_ROW_HEIGHT),
|
||||||
@@ -956,7 +935,6 @@ internal fun DenseMonthGrid(
|
|||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val weekCount = remember(state.weekStart) { continuousWeekCount(state.weekStart) }
|
val weekCount = remember(state.weekStart) { continuousWeekCount(state.weekStart) }
|
||||||
@@ -964,15 +942,10 @@ internal fun DenseMonthGrid(
|
|||||||
state = listState,
|
state = listState,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
// The gap under the weekday header is a real margin, not content
|
.padding(horizontal = 8.dp),
|
||||||
// padding: a lazy list scrolls its rows *through* the before-content
|
|
||||||
// padding, so scrolling to a week landed its row that far down with
|
|
||||||
// the tail of the previous one showing above it (#191).
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.padding(top = DENSE_HEADER_GAP),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
// Bottom inset clears the FAB stack so the last row stays tappable.
|
// Bottom inset clears the FAB stack so the last row stays tappable.
|
||||||
contentPadding = PaddingValues(bottom = 96.dp),
|
contentPadding = PaddingValues(top = 4.dp, bottom = 96.dp),
|
||||||
) {
|
) {
|
||||||
items(count = weekCount, key = { it }) { index ->
|
items(count = weekCount, key = { it }) { index ->
|
||||||
val week = state.weeksByIndex[index]
|
val week = state.weeksByIndex[index]
|
||||||
@@ -987,7 +960,6 @@ internal fun DenseMonthGrid(
|
|||||||
inMonth = { true },
|
inMonth = { true },
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onOpenDay,
|
onOpenDay = onOpenDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
labelMonthOnFirst = true,
|
labelMonthOnFirst = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -1215,9 +1187,6 @@ private fun SplitMonthBody(
|
|||||||
onSelectDay(it)
|
onSelectDay(it)
|
||||||
onSetExpanded(false)
|
onSetExpanded(false)
|
||||||
},
|
},
|
||||||
// A tapped chip is asking for that event, not for its day, so it
|
|
||||||
// opens the detail from here too rather than collapsing (#187).
|
|
||||||
onEventClick = onEventClick,
|
|
||||||
onCollapse = { onSetExpanded(false) },
|
onCollapse = { onSetExpanded(false) },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -1318,7 +1287,6 @@ private fun SplitMonthExpanded(
|
|||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
swipeModifier: Modifier,
|
swipeModifier: Modifier,
|
||||||
onPickDay: (LocalDate) -> Unit,
|
onPickDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
onCollapse: () -> Unit,
|
onCollapse: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val slideSpec = rememberCalendarSlideSpec()
|
val slideSpec = rememberCalendarSlideSpec()
|
||||||
@@ -1341,7 +1309,6 @@ private fun SplitMonthExpanded(
|
|||||||
state = s,
|
state = s,
|
||||||
showWeekNumbers = showWeekNumbers,
|
showWeekNumbers = showWeekNumbers,
|
||||||
onOpenDay = onPickDay,
|
onOpenDay = onPickDay,
|
||||||
onEventClick = onEventClick,
|
|
||||||
selected = sel,
|
selected = sel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1822,7 +1789,7 @@ private fun ContinuousMonthSkeleton(dense: Boolean) {
|
|||||||
modifier = Modifier.padding(bottom = 8.dp),
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Spacer(Modifier.height(DENSE_HEADER_GAP))
|
Spacer(Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
// More rows than a viewport holds; the clip takes the overflow.
|
// More rows than a viewport holds; the clip takes the overflow.
|
||||||
repeat(6) {
|
repeat(6) {
|
||||||
@@ -1870,7 +1837,6 @@ private fun MonthWeekRow(
|
|||||||
inMonth: (LocalDate) -> Boolean,
|
inMonth: (LocalDate) -> Boolean,
|
||||||
showWeekNumbers: Boolean,
|
showWeekNumbers: Boolean,
|
||||||
onOpenDay: (LocalDate) -> Unit,
|
onOpenDay: (LocalDate) -> Unit,
|
||||||
onEventClick: (EventInstance) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
blankOutside: Boolean = false,
|
blankOutside: Boolean = false,
|
||||||
labelMonthOnFirst: Boolean = false,
|
labelMonthOnFirst: Boolean = false,
|
||||||
@@ -2169,17 +2135,11 @@ private fun MonthWeekRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tap layer: a tap on a chip opens that event, anything else opens the
|
// Tap layer: in month view a tap on any day opens that day. Padded and
|
||||||
// day (#187). The chips take no pointer input of their own — this
|
|
||||||
// layer covers them — so the lane under the finger is resolved
|
|
||||||
// geometrically, exactly as the drag pickup above does it; the down
|
|
||||||
// position is read on the initial pass, which consumes nothing and so
|
|
||||||
// leaves both the click and a pickup in flight untouched. Padded and
|
|
||||||
// clipped to the background pill so the ripple matches it. A blanked
|
// clipped to the background pill so the ripple matches it. A blanked
|
||||||
// cell isn't part of this month, so it takes no taps either.
|
// cell isn't part of this month, so it takes no taps either.
|
||||||
val downY = remember(week.days.size) { FloatArray(week.days.size) }
|
|
||||||
Row(Modifier.matchParentSize()) {
|
Row(Modifier.matchParentSize()) {
|
||||||
week.days.forEachIndexed { col, d ->
|
week.days.forEach { d ->
|
||||||
if (blankOutside && !inMonth(d)) {
|
if (blankOutside && !inMonth(d)) {
|
||||||
Spacer(Modifier.weight(1f).fillMaxHeight())
|
Spacer(Modifier.weight(1f).fillMaxHeight())
|
||||||
} else {
|
} else {
|
||||||
@@ -2187,28 +2147,9 @@ private fun MonthWeekRow(
|
|||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.pointerInput(col) {
|
|
||||||
awaitEachGesture {
|
|
||||||
downY[col] = awaitFirstDown(
|
|
||||||
requireUnconsumed = false,
|
|
||||||
pass = PointerEventPass.Initial,
|
|
||||||
).position.y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(horizontal = CELL_GAP, vertical = 1.dp)
|
.padding(horizontal = CELL_GAP, vertical = 1.dp)
|
||||||
.clip(CELL_SHAPE)
|
.clip(CELL_SHAPE)
|
||||||
.clickable {
|
.clickable { onOpenDay(d) },
|
||||||
val chip = week.chipAtCellY(
|
|
||||||
col = col,
|
|
||||||
cellY = downY[col],
|
|
||||||
bandTopInCell = bandTopInCell(
|
|
||||||
cellCoordinates,
|
|
||||||
bandCoordinates,
|
|
||||||
),
|
|
||||||
rowHeightPx = rowHeightPx,
|
|
||||||
)
|
|
||||||
if (chip != null) onEventClick(chip) else onOpenDay(d)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2217,38 +2158,6 @@ private fun MonthWeekRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* How far the event band sits below the top of the row's day-column box, or null
|
|
||||||
* while either is unmeasured. Read off the live coordinates rather than summed
|
|
||||||
* from the padding constants, so it can't drift from what the row actually drew.
|
|
||||||
*/
|
|
||||||
private fun bandTopInCell(
|
|
||||||
cell: Array<LayoutCoordinates?>,
|
|
||||||
band: Array<LayoutCoordinates?>,
|
|
||||||
): Float? {
|
|
||||||
val cellTop = cell[0]?.takeIf { it.isAttached }?.positionInRoot()?.y ?: return null
|
|
||||||
val bandTop = band[0]?.takeIf { it.isAttached }?.positionInRoot()?.y ?: return null
|
|
||||||
return bandTop - cellTop
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The chip at [cellY] in column [col], where [cellY] is measured from the top of
|
|
||||||
* the row's day-column box. Null for a tap above the band (the day number), on an
|
|
||||||
* empty lane, or on the overflow dots — all of which mean "open the day", the
|
|
||||||
* dots included: their point is that the day holds more than fits.
|
|
||||||
*/
|
|
||||||
internal fun MonthWeek.chipAtCellY(
|
|
||||||
col: Int,
|
|
||||||
cellY: Float,
|
|
||||||
bandTopInCell: Float?,
|
|
||||||
rowHeightPx: Float,
|
|
||||||
): EventInstance? {
|
|
||||||
if (bandTopInCell == null || rowHeightPx <= 0f) return null
|
|
||||||
val bandY = cellY - bandTopInCell
|
|
||||||
if (bandY < 0f) return null
|
|
||||||
return chipAt(col, (bandY / rowHeightPx).toInt(), MAX_EVENT_ROWS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The row-level pickup for month chips: resolves which chip the press landed on
|
* The row-level pickup for month chips: resolves which chip the press landed on
|
||||||
* from the geometry the row just laid out, and abandons the gesture on empty
|
* from the geometry the row just laid out, and abandons the gesture on empty
|
||||||
@@ -2263,7 +2172,7 @@ private fun monthChipDragModifier(
|
|||||||
rowHeightPx: Float,
|
rowHeightPx: Float,
|
||||||
isRtl: Boolean,
|
isRtl: Boolean,
|
||||||
): Modifier = rememberDragSurface(
|
): Modifier = rememberDragSurface(
|
||||||
enabled = moveScope?.dragEnabled == true && controller != null,
|
enabled = moveScope != null && controller != null,
|
||||||
key = week.days.first(),
|
key = week.days.first(),
|
||||||
onPickUp = { local, pointerInRoot, nodeInRoot, size ->
|
onPickUp = { local, pointerInRoot, nodeInRoot, size ->
|
||||||
val bandTop = band[0]?.takeIf { it.isAttached }?.positionInRoot()?.y
|
val bandTop = band[0]?.takeIf { it.isAttached }?.positionInRoot()?.y
|
||||||
@@ -2447,9 +2356,13 @@ private fun MonthBar(
|
|||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
// No "…" (#164): the chip is narrow enough that the ellipsis costs a
|
||||||
|
// couple of readable characters. softWrap is off with it — left on,
|
||||||
|
// a clipped line breaks at the last whole word instead of running to
|
||||||
|
// the chip's edge, which reads as *less* title, not more.
|
||||||
|
overflow = TextOverflow.Clip,
|
||||||
|
softWrap = false,
|
||||||
color = eventInk(fill),
|
color = eventInk(fill),
|
||||||
textDecoration = declinedDecoration(event.isDeclined),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ internal fun MonthStylePreview(
|
|||||||
state = sample.month,
|
state = sample.month,
|
||||||
showWeekNumbers = false,
|
showWeekNumbers = false,
|
||||||
onOpenDay = {},
|
onOpenDay = {},
|
||||||
onEventClick = {},
|
|
||||||
)
|
)
|
||||||
MonthViewStyle.Continuous -> ContinuousMonthGrid(
|
MonthViewStyle.Continuous -> ContinuousMonthGrid(
|
||||||
state = sample.continuous,
|
state = sample.continuous,
|
||||||
@@ -56,7 +55,6 @@ internal fun MonthStylePreview(
|
|||||||
),
|
),
|
||||||
showWeekNumbers = false,
|
showWeekNumbers = false,
|
||||||
onOpenDay = {},
|
onOpenDay = {},
|
||||||
onEventClick = {},
|
|
||||||
)
|
)
|
||||||
MonthViewStyle.Dense -> DenseMonthGrid(
|
MonthViewStyle.Dense -> DenseMonthGrid(
|
||||||
state = sample.continuous,
|
state = sample.continuous,
|
||||||
@@ -68,7 +66,6 @@ internal fun MonthStylePreview(
|
|||||||
),
|
),
|
||||||
showWeekNumbers = false,
|
showWeekNumbers = false,
|
||||||
onOpenDay = {},
|
onOpenDay = {},
|
||||||
onEventClick = {},
|
|
||||||
)
|
)
|
||||||
MonthViewStyle.Split -> {
|
MonthViewStyle.Split -> {
|
||||||
SplitMonthGrid(
|
SplitMonthGrid(
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ import de.jeanlucmakiola.calendula.domain.SearchMonth
|
|||||||
import de.jeanlucmakiola.floret.identity.animateItemMotion
|
import de.jeanlucmakiola.floret.identity.animateItemMotion
|
||||||
import de.jeanlucmakiola.floret.identity.fadeThrough
|
import de.jeanlucmakiola.floret.identity.fadeThrough
|
||||||
import de.jeanlucmakiola.floret.identity.predictiveBack
|
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||||
import de.jeanlucmakiola.calendula.ui.common.declinedTitle
|
|
||||||
import de.jeanlucmakiola.floret.components.GroupedRow
|
import de.jeanlucmakiola.floret.components.GroupedRow
|
||||||
import de.jeanlucmakiola.floret.components.InlineTextField
|
import de.jeanlucmakiola.floret.components.InlineTextField
|
||||||
import de.jeanlucmakiola.floret.components.Position
|
import de.jeanlucmakiola.floret.components.Position
|
||||||
@@ -520,7 +519,7 @@ private fun SearchResultRow(
|
|||||||
// Faded like a past event anywhere else in the app — search reaches back
|
// Faded like a past event anywhere else in the app — search reaches back
|
||||||
// through the whole history.
|
// through the whole history.
|
||||||
modifier = if (hit.isPast) modifier.alpha(EventDimAlpha) else modifier,
|
modifier = if (hit.isPast) modifier.alpha(EventDimAlpha) else modifier,
|
||||||
title = declinedTitle(marked(event.title, hit.titleSpans, highlight), event.isDeclined),
|
title = marked(event.title, hit.titleSpans, highlight),
|
||||||
summary = searchSummary(hit, highlight),
|
summary = searchSummary(hit, highlight),
|
||||||
position = position,
|
position = position,
|
||||||
minHeight = 64.dp,
|
minHeight = 64.dp,
|
||||||
|
|||||||
@@ -302,14 +302,13 @@ private fun ReportProblemRow(position: Position) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun LanguageRow(position: Position) {
|
private fun LanguageRow(position: Position) {
|
||||||
val context = LocalContext.current
|
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
|
// Setting a locale recreates the activity; mirror the choice locally so the
|
||||||
// row updates instantly even before the recreation lands.
|
// row updates instantly even before the recreation lands.
|
||||||
var current by remember { mutableStateOf(AppLanguage.currentTag(supported)) }
|
var current by remember { mutableStateOf(AppLanguage.currentTag()) }
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// null = follow the system; the rest are BCP-47 tags from locales_config.xml.
|
// null = follow the system; the rest are BCP-47 tags from locales_config.xml.
|
||||||
val options = remember(supported) { listOf<String?>(null) + supported }
|
val options = remember { listOf<String?>(null) + AppLanguage.supportedTags(context, R.xml.locales_config) }
|
||||||
|
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = stringResource(R.string.settings_language),
|
title = stringResource(R.string.settings_language),
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ data class SettingsUiState(
|
|||||||
val showWeekNumbers: Boolean = false,
|
val showWeekNumbers: Boolean = false,
|
||||||
/** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */
|
/** Whether the jump-to-today control sits in the top bar instead of the FAB (#60). */
|
||||||
val todayButtonInToolbar: Boolean = false,
|
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). */
|
/** How far ahead the in-app Agenda screen shows events (v2.11). */
|
||||||
val agendaScreenRange: AgendaRange = AgendaRange.Month,
|
val agendaScreenRange: AgendaRange = AgendaRange.Month,
|
||||||
/** How far ahead the agenda widget shows events (v2.11). */
|
/** How far ahead the agenda widget shows events (v2.11). */
|
||||||
|
|||||||
@@ -145,15 +145,9 @@ class SettingsViewModel @Inject constructor(
|
|||||||
prefs.showWeekNumbers,
|
prefs.showWeekNumbers,
|
||||||
prefs.agendaShowToday,
|
prefs.agendaShowToday,
|
||||||
prefs.softenCalendarColors,
|
prefs.softenCalendarColors,
|
||||||
combine(
|
|
||||||
prefs.todayButtonInToolbar,
|
prefs.todayButtonInToolbar,
|
||||||
prefs.dragToReschedule,
|
) { hourLines, weekNumbers, showToday, soften, todayInToolbar ->
|
||||||
::Pair,
|
DisplayToggles(hourLines, weekNumbers, showToday, soften, todayInToolbar)
|
||||||
),
|
|
||||||
) { hourLines, weekNumbers, showToday, soften, (todayInToolbar, dragToMove) ->
|
|
||||||
DisplayToggles(
|
|
||||||
hourLines, weekNumbers, showToday, soften, todayInToolbar, dragToMove,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
) { view, screenRange, widgetRange, timeFormat, toggles ->
|
) { view, screenRange, widgetRange, timeFormat, toggles ->
|
||||||
ViewSettings(
|
ViewSettings(
|
||||||
@@ -163,7 +157,6 @@ class SettingsViewModel @Inject constructor(
|
|||||||
agendaShowToday = toggles.agendaShowToday,
|
agendaShowToday = toggles.agendaShowToday,
|
||||||
softenColors = toggles.softenColors,
|
softenColors = toggles.softenColors,
|
||||||
todayButtonInToolbar = toggles.todayButtonInToolbar,
|
todayButtonInToolbar = toggles.todayButtonInToolbar,
|
||||||
dragToReschedule = toggles.dragToReschedule,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
combine(
|
combine(
|
||||||
@@ -196,7 +189,6 @@ class SettingsViewModel @Inject constructor(
|
|||||||
agendaShowToday = views.agendaShowToday,
|
agendaShowToday = views.agendaShowToday,
|
||||||
softenColors = views.softenColors,
|
softenColors = views.softenColors,
|
||||||
todayButtonInToolbar = views.todayButtonInToolbar,
|
todayButtonInToolbar = views.todayButtonInToolbar,
|
||||||
dragToReschedule = views.dragToReschedule,
|
|
||||||
agendaShowRangeBar = misc.showRangeBar,
|
agendaShowRangeBar = misc.showRangeBar,
|
||||||
autofocusEventTitle = misc.autofocusEventTitle,
|
autofocusEventTitle = misc.autofocusEventTitle,
|
||||||
pastEventDisplay = misc.pastEventDisplay,
|
pastEventDisplay = misc.pastEventDisplay,
|
||||||
@@ -297,7 +289,6 @@ class SettingsViewModel @Inject constructor(
|
|||||||
val agendaShowToday: Boolean,
|
val agendaShowToday: Boolean,
|
||||||
val softenColors: Boolean,
|
val softenColors: Boolean,
|
||||||
val todayButtonInToolbar: Boolean,
|
val todayButtonInToolbar: Boolean,
|
||||||
val dragToReschedule: Boolean,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class DisplayToggles(
|
private data class DisplayToggles(
|
||||||
@@ -306,7 +297,6 @@ class SettingsViewModel @Inject constructor(
|
|||||||
val agendaShowToday: Boolean,
|
val agendaShowToday: Boolean,
|
||||||
val softenColors: Boolean,
|
val softenColors: Boolean,
|
||||||
val todayButtonInToolbar: Boolean,
|
val todayButtonInToolbar: Boolean,
|
||||||
val dragToReschedule: Boolean,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private data class MiscSettings(
|
private data class MiscSettings(
|
||||||
@@ -544,10 +534,6 @@ class SettingsViewModel @Inject constructor(
|
|||||||
viewModelScope.launch { prefs.setTodayButtonInToolbar(enabled) }
|
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
|
* 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
|
* on what the component state reports; the writes are binder round-trips and
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ internal fun ViewsScreen(
|
|||||||
GroupedRow(
|
GroupedRow(
|
||||||
title = stringResource(R.string.settings_dim_completed),
|
title = stringResource(R.string.settings_dim_completed),
|
||||||
summary = stringResource(R.string.settings_dim_completed_summary),
|
summary = stringResource(R.string.settings_dim_completed_summary),
|
||||||
position = Position.Middle,
|
position = Position.Bottom,
|
||||||
trailing = {
|
trailing = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = state.dimCompletedEvents,
|
checked = state.dimCompletedEvents,
|
||||||
@@ -123,18 +123,6 @@ internal fun ViewsScreen(
|
|||||||
},
|
},
|
||||||
onClick = { viewModel.setDimCompletedEvents(!state.dimCompletedEvents) },
|
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))
|
Spacer(Modifier.height(8.dp))
|
||||||
SectionHeader(stringResource(R.string.settings_month_header))
|
SectionHeader(stringResource(R.string.settings_month_header))
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||||
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.declinedDecoration
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
|
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
|
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
||||||
@@ -101,7 +100,6 @@ import de.jeanlucmakiola.calendula.ui.common.TimelineDragController
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDragOverlay
|
import de.jeanlucmakiola.calendula.ui.common.TimelineDragOverlay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
||||||
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
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.eventMoveAction
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
||||||
@@ -130,7 +128,6 @@ import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.GUTTER_CONTENT_START_INSET
|
import de.jeanlucmakiola.calendula.ui.common.GUTTER_CONTENT_START_INSET
|
||||||
import de.jeanlucmakiola.calendula.ui.common.GUTTER_WIDTH
|
import de.jeanlucmakiola.calendula.ui.common.GUTTER_WIDTH
|
||||||
import de.jeanlucmakiola.calendula.ui.common.HourGutter
|
import de.jeanlucmakiola.calendula.ui.common.HourGutter
|
||||||
import de.jeanlucmakiola.calendula.ui.common.TIMELINE_CONTENT_END_INSET
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines
|
||||||
import de.jeanlucmakiola.calendula.ui.common.tappedMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.tappedMinuteOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
@@ -508,7 +505,7 @@ private fun WeekDayHeader(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 4.dp, bottom = 8.dp, end = TIMELINE_CONTENT_END_INSET),
|
.padding(top = 4.dp, bottom = 8.dp),
|
||||||
) {
|
) {
|
||||||
// Mirror the day-column layout (empty weekday line + spacer) so the
|
// Mirror the day-column layout (empty weekday line + spacer) so the
|
||||||
// badge lines up vertically with the date numbers. The start inset centres
|
// badge lines up vertically with the date numbers. The start inset centres
|
||||||
@@ -604,11 +601,7 @@ private fun AllDayStrip(
|
|||||||
// Height is hoisted + animated so it slides and resizes smoothly;
|
// Height is hoisted + animated so it slides and resizes smoothly;
|
||||||
// padding sits inside it so the content area is lanes * row height.
|
// padding sits inside it so the content area is lanes * row height.
|
||||||
.height(height)
|
.height(height)
|
||||||
.padding(
|
.padding(vertical = ALL_DAY_VERTICAL_PADDING),
|
||||||
top = ALL_DAY_VERTICAL_PADDING,
|
|
||||||
bottom = ALL_DAY_VERTICAL_PADDING,
|
|
||||||
end = TIMELINE_CONTENT_END_INSET,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
// Keep the gutter-width offset so the bars line up with the day columns.
|
// Keep the gutter-width offset so the bars line up with the day columns.
|
||||||
Spacer(Modifier.width(GUTTER_WIDTH))
|
Spacer(Modifier.width(GUTTER_WIDTH))
|
||||||
@@ -668,7 +661,6 @@ private fun AllDayBar(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = eventInk(fill),
|
color = eventInk(fill),
|
||||||
textDecoration = declinedDecoration(event.isDeclined),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,7 +707,6 @@ private fun Timeline(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.padding(end = TIMELINE_CONTENT_END_INSET)
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
.onGloballyPositioned { dragController.geometry.viewport = it },
|
.onGloballyPositioned { dragController.geometry.viewport = it },
|
||||||
@@ -914,7 +905,7 @@ private fun EventBlock(
|
|||||||
val moveAction = eventMoveAction(block.event)
|
val moveAction = eventMoveAction(block.event)
|
||||||
// A block clipped at the top continues from the previous day: its top edge
|
// 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.
|
// is midnight, not the event's start, so dragging it would invent a time.
|
||||||
val draggable = eventDragAllowed(block.event) && block.beginsOn(date, zone)
|
val draggable = moveAction != null && block.beginsOn(date, zone)
|
||||||
val dragModifier = rememberEventDragSource(
|
val dragModifier = rememberEventDragSource(
|
||||||
enabled = draggable,
|
enabled = draggable,
|
||||||
key = block.event.instanceId,
|
key = block.event.instanceId,
|
||||||
@@ -948,7 +939,6 @@ private fun EventBlock(
|
|||||||
maxLines = titleMaxLines,
|
maxLines = titleMaxLines,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = eventInk(fill, alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
textDecoration = declinedDecoration(block.event.isDeclined),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (showTime) {
|
if (showTime) {
|
||||||
@@ -986,12 +976,7 @@ private fun WeekLoading() {
|
|||||||
// Same scale resolution as the loaded timeline, so the skeleton's
|
// Same scale resolution as the loaded timeline, so the skeleton's
|
||||||
// columns don't resize the moment the real week arrives.
|
// columns don't resize the moment the real week arrives.
|
||||||
val totalHeight = scale.hourHeight(maxHeight) * 24
|
val totalHeight = scale.hourHeight(maxHeight) * 24
|
||||||
Row(
|
Row(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(end = TIMELINE_CONTENT_END_INSET)
|
|
||||||
.verticalScroll(scrollState),
|
|
||||||
) {
|
|
||||||
Spacer(Modifier.width(GUTTER_WIDTH))
|
Spacer(Modifier.width(GUTTER_WIDTH))
|
||||||
repeat(7) {
|
repeat(7) {
|
||||||
Box(
|
Box(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.os.Build
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.glance.GlanceTheme
|
import androidx.glance.GlanceTheme
|
||||||
import androidx.glance.material3.ColorProviders
|
import androidx.glance.material3.ColorProviders
|
||||||
import androidx.glance.text.TextDecoration
|
|
||||||
import de.jeanlucmakiola.calendula.ui.theme.CalendulaDarkFallback
|
import de.jeanlucmakiola.calendula.ui.theme.CalendulaDarkFallback
|
||||||
import de.jeanlucmakiola.calendula.ui.theme.CalendulaLightFallback
|
import de.jeanlucmakiola.calendula.ui.theme.CalendulaLightFallback
|
||||||
|
|
||||||
@@ -35,10 +34,3 @@ fun CalendulaGlanceTheme(content: @Composable () -> Unit) {
|
|||||||
}
|
}
|
||||||
GlanceTheme(colors = colors, content = content)
|
GlanceTheme(colors = colors, content = content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Glance's counterpart to the app's `declinedDecoration`: a declined invitation
|
|
||||||
* reads the same on the home screen as it does inside the app (#180).
|
|
||||||
*/
|
|
||||||
fun glanceDeclinedDecoration(isDeclined: Boolean): TextDecoration =
|
|
||||||
if (isDeclined) TextDecoration.LineThrough else TextDecoration.None
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.jeanlucmakiola.calendula.widget.agenda
|
package de.jeanlucmakiola.calendula.widget.agenda
|
||||||
|
|
||||||
import de.jeanlucmakiola.calendula.widget.glanceDeclinedDecoration
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -25,6 +24,7 @@ import androidx.glance.appwidget.cornerRadius
|
|||||||
import androidx.glance.appwidget.lazy.LazyColumn
|
import androidx.glance.appwidget.lazy.LazyColumn
|
||||||
import androidx.glance.appwidget.lazy.items
|
import androidx.glance.appwidget.lazy.items
|
||||||
import androidx.glance.appwidget.provideContent
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.appwidget.updateAll
|
||||||
import androidx.glance.background
|
import androidx.glance.background
|
||||||
import androidx.glance.currentState
|
import androidx.glance.currentState
|
||||||
import androidx.glance.state.PreferencesGlanceStateDefinition
|
import androidx.glance.state.PreferencesGlanceStateDefinition
|
||||||
@@ -135,14 +135,10 @@ class AgendaWidget : GlanceAppWidget() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Re-reads the calendar and redraws the widget (header refresh button). */
|
||||||
* Re-reads the calendar and redraws the widget (header refresh button). Targets
|
|
||||||
* the tapped widget's own id rather than `updateAll`, whose provider-name lookup
|
|
||||||
* is empty in a process a tap woke from cold — see `ShiftMonthAction` (#18).
|
|
||||||
*/
|
|
||||||
class RefreshAgendaAction : ActionCallback {
|
class RefreshAgendaAction : ActionCallback {
|
||||||
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
||||||
AgendaWidget().update(context.applicationContext, glanceId)
|
AgendaWidget().updateAll(context.applicationContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,11 +416,7 @@ private fun EventRow(
|
|||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = TextStyle(
|
style = TextStyle(color = titleColor, fontSize = metrics.eventTitle),
|
||||||
color = titleColor,
|
|
||||||
fontSize = metrics.eventTitle,
|
|
||||||
textDecoration = glanceDeclinedDecoration(event.isDeclined),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = eventTimeSummary(context, event, day, is24Hour),
|
text = eventTimeSummary(context, event, day, is24Hour),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.jeanlucmakiola.calendula.widget.month
|
package de.jeanlucmakiola.calendula.widget.month
|
||||||
|
|
||||||
import de.jeanlucmakiola.calendula.widget.glanceDeclinedDecoration
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -26,6 +25,7 @@ import androidx.glance.appwidget.action.actionStartActivity
|
|||||||
import androidx.glance.appwidget.cornerRadius
|
import androidx.glance.appwidget.cornerRadius
|
||||||
import androidx.glance.appwidget.provideContent
|
import androidx.glance.appwidget.provideContent
|
||||||
import androidx.glance.appwidget.state.updateAppWidgetState
|
import androidx.glance.appwidget.state.updateAppWidgetState
|
||||||
|
import androidx.glance.appwidget.updateAll
|
||||||
import androidx.glance.background
|
import androidx.glance.background
|
||||||
import androidx.glance.currentState
|
import androidx.glance.currentState
|
||||||
import androidx.glance.layout.Alignment
|
import androidx.glance.layout.Alignment
|
||||||
@@ -33,7 +33,6 @@ import androidx.glance.layout.Box
|
|||||||
import androidx.glance.layout.Column
|
import androidx.glance.layout.Column
|
||||||
import androidx.glance.layout.Row
|
import androidx.glance.layout.Row
|
||||||
import androidx.glance.layout.Spacer
|
import androidx.glance.layout.Spacer
|
||||||
import androidx.glance.layout.fillMaxHeight
|
|
||||||
import androidx.glance.layout.fillMaxSize
|
import androidx.glance.layout.fillMaxSize
|
||||||
import androidx.glance.layout.fillMaxWidth
|
import androidx.glance.layout.fillMaxWidth
|
||||||
import androidx.glance.layout.height
|
import androidx.glance.layout.height
|
||||||
@@ -98,27 +97,10 @@ private fun yearMonthOf(index: Int): YearMonth =
|
|||||||
* inter-cell seam, with rounded end caps. The displayed month lives in Glance
|
* inter-cell seam, with rounded end caps. The displayed month lives in Glance
|
||||||
* state and is read reactively in the composition ([currentState]) so the arrows
|
* state and is read reactively in the composition ([currentState]) so the arrows
|
||||||
* move it via plain recomposition, not a (here-unreliable) widget session reload.
|
* move it via plain recomposition, not a (here-unreliable) widget session reload.
|
||||||
*
|
|
||||||
* Everything here is written to keep the published `RemoteViews` small. A widget
|
|
||||||
* update reaches the launcher as a *oneway* binder call, and the host's async
|
|
||||||
* buffer is 1 MB for all of it; overrun throws `TransactionTooLargeException` and
|
|
||||||
* the system drops the whole host, freezing every widget on the home screen until
|
|
||||||
* the launcher rebinds. [SizeMode.Exact] serialises the grid once per size the
|
|
||||||
* host reports, so the ceiling arrives at half the view count you would expect: a
|
|
||||||
* grid that emitted a view per cell blew past it after three taps (#214). Every
|
|
||||||
* row here emits only what it fills.
|
|
||||||
*/
|
*/
|
||||||
class MonthWidget : GlanceAppWidget() {
|
class MonthWidget : GlanceAppWidget() {
|
||||||
|
|
||||||
override val stateDefinition = PreferencesGlanceStateDefinition
|
override val stateDefinition = PreferencesGlanceStateDefinition
|
||||||
|
|
||||||
/**
|
|
||||||
* Exact rather than [SizeMode.Single] or [SizeMode.Responsive], both of which
|
|
||||||
* return early from `GlanceAppWidget.resize` — under those the grid is never
|
|
||||||
* recomposed for a new size and a resized widget clips instead of reflowing.
|
|
||||||
* Exact costs one serialised copy per host size, which is affordable now that
|
|
||||||
* a rendering is ~190 views rather than ~740.
|
|
||||||
*/
|
|
||||||
override val sizeMode = SizeMode.Exact
|
override val sizeMode = SizeMode.Exact
|
||||||
|
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
@@ -136,17 +118,7 @@ class MonthWidget : GlanceAppWidget() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Step the displayed month by the `delta` action parameter (±1). */
|
||||||
* Step the displayed month by the `delta` action parameter (±1).
|
|
||||||
*
|
|
||||||
* Redrawn through [GlanceAppWidget.update] with the id the callback was handed,
|
|
||||||
* never `updateAll`. `updateAll` resolves its targets through the provider-name
|
|
||||||
* -> app-widget-id map Glance persists for the *receiver*, and in a process that
|
|
||||||
* has done nothing else yet — the one a tap wakes after a reboot — that lookup
|
|
||||||
* comes back empty and the redraw is dropped: the state write lands, nothing
|
|
||||||
* recomposes, and the arrows read as dead until something else opens the app and
|
|
||||||
* starts a session (#18). The tapped widget's own id needs no lookup.
|
|
||||||
*/
|
|
||||||
class ShiftMonthAction : ActionCallback {
|
class ShiftMonthAction : ActionCallback {
|
||||||
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
||||||
val delta = parameters[deltaKey] ?: 0
|
val delta = parameters[deltaKey] ?: 0
|
||||||
@@ -154,7 +126,7 @@ class ShiftMonthAction : ActionCallback {
|
|||||||
val cur = prefs[MONTH_INDEX_KEY] ?: currentMonthIndex(systemZone())
|
val cur = prefs[MONTH_INDEX_KEY] ?: currentMonthIndex(systemZone())
|
||||||
prefs[MONTH_INDEX_KEY] = cur + delta
|
prefs[MONTH_INDEX_KEY] = cur + delta
|
||||||
}
|
}
|
||||||
MonthWidget().update(context.applicationContext, glanceId)
|
MonthWidget().updateAll(context.applicationContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -162,11 +134,11 @@ class ShiftMonthAction : ActionCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Jump the displayed month back to the current month. See [ShiftMonthAction]. */
|
/** Jump the displayed month back to the current month. */
|
||||||
class ResetMonthAction : ActionCallback {
|
class ResetMonthAction : ActionCallback {
|
||||||
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
|
||||||
updateAppWidgetState(context, glanceId) { prefs -> prefs.remove(MONTH_INDEX_KEY) }
|
updateAppWidgetState(context, glanceId) { prefs -> prefs.remove(MONTH_INDEX_KEY) }
|
||||||
MonthWidget().update(context.applicationContext, glanceId)
|
MonthWidget().updateAll(context.applicationContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,31 +286,9 @@ private fun WeekRow(
|
|||||||
colW: Dp,
|
colW: Dp,
|
||||||
modifier: GlanceModifier,
|
modifier: GlanceModifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
val hidden = week.hiddenPerDay()
|
// Day numbers.
|
||||||
Box(modifier = modifier.fillMaxWidth()) {
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
// Day-open targets, one full-height strip per column, sitting *under* the
|
|
||||||
// content: a tap anywhere in a day still opens it, as in the app, without
|
|
||||||
// every cell in the column carrying a click target of its own. The content
|
|
||||||
// above is not clickable, so touches fall through to this layer; only an
|
|
||||||
// event bar opts out to open its own detail.
|
|
||||||
Row(modifier = GlanceModifier.fillMaxSize()) {
|
|
||||||
week.days.forEach { date ->
|
|
||||||
Box(
|
|
||||||
modifier = GlanceModifier
|
|
||||||
.width(colW)
|
|
||||||
.fillMaxHeight()
|
|
||||||
.clickable(openDayAction(context, date)),
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(modifier = GlanceModifier.fillMaxWidth()) {
|
|
||||||
// Day numbers. Fixed height so every week's number row lines up whether
|
|
||||||
// or not it holds today's larger pill.
|
|
||||||
Row(
|
|
||||||
modifier = GlanceModifier.fillMaxWidth().height(DAY_NUMBER_HEIGHT),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
week.days.forEach { date ->
|
week.days.forEach { date ->
|
||||||
DayNumber(
|
DayNumber(
|
||||||
date = date,
|
date = date,
|
||||||
@@ -351,37 +301,13 @@ private fun WeekRow(
|
|||||||
Spacer(GlanceModifier.height(2.dp))
|
Spacer(GlanceModifier.height(2.dp))
|
||||||
// One lane row per event row. A multi-day span is a single Box spanning
|
// One lane row per event row. A multi-day span is a single Box spanning
|
||||||
// its columns (colW * n) so it's connected with no seam and rounded ends.
|
// its columns (colW * n) so it's connected with no seam and rounded ends.
|
||||||
// Lanes past the last one this week fills emit nothing at all.
|
repeat(MAX_LANES) { lane ->
|
||||||
repeat(week.usedLanes()) { lane ->
|
|
||||||
LaneRow(week = week, lane = lane, dark = dark, soften = soften, colW = colW)
|
LaneRow(week = week, lane = lane, dark = dark, soften = soften, colW = colW)
|
||||||
Spacer(GlanceModifier.height(1.dp))
|
Spacer(GlanceModifier.height(1.dp))
|
||||||
}
|
}
|
||||||
if (hidden.any { it > 0 }) OverflowRow(hidden = hidden, colW = colW)
|
OverflowRow(week = week, colW = colW)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How many lanes this week actually fills, counting from the top. The grid draws
|
|
||||||
* that many rows instead of a fixed [MAX_LANES]; an empty lane costs three views
|
|
||||||
* per day and most weeks leave the last two empty.
|
|
||||||
*/
|
|
||||||
private fun MonthWeek.usedLanes(): Int {
|
|
||||||
for (lane in MAX_LANES - 1 downTo 0) {
|
|
||||||
val filled = spans.any { it.lane == lane } ||
|
|
||||||
days.indices.any { col -> timedEventAt(this, lane, col, days[col]) != null }
|
|
||||||
if (filled) return lane + 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Events per day that no lane had room for — the "+N" counts, 0 where all fit. */
|
|
||||||
private fun MonthWeek.hiddenPerDay(): List<Int> = days.mapIndexed { col, date ->
|
|
||||||
val shownSpans = spans.count { col in it.startCol..it.endCol && it.lane < MAX_LANES }
|
|
||||||
val freeSlots = (MAX_LANES - shownSpans).coerceAtLeast(0)
|
|
||||||
val timedShown = minOf(freeSlots, timedByDay[date].orEmpty().size)
|
|
||||||
((countByDay[date] ?: 0) - shownSpans - timedShown).coerceAtLeast(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open [date]'s day view rooted in the month view (so back returns to the month
|
* Open [date]'s day view rooted in the month view (so back returns to the month
|
||||||
@@ -392,14 +318,25 @@ private fun MonthWeek.hiddenPerDay(): List<Int> = days.mapIndexed { col, date ->
|
|||||||
private fun openDayAction(context: Context, date: LocalDate) =
|
private fun openDayAction(context: Context, date: LocalDate) =
|
||||||
actionStartActivity(MainActivity.openDateIntent(context, date, CalendarView.Month))
|
actionStartActivity(MainActivity.openDateIntent(context, date, CalendarView.Month))
|
||||||
|
|
||||||
/**
|
|
||||||
* A day's number. Every day but today is a bare centred [Text] carrying the column
|
|
||||||
* width itself — a wrapping Box costs three views, and there are 42 of these.
|
|
||||||
* Today keeps its filled circle, which does need the Box.
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DayNumber(date: LocalDate, isToday: Boolean, inMonth: Boolean, colW: Dp) {
|
private fun DayNumber(date: LocalDate, isToday: Boolean, inMonth: Boolean, colW: Dp) {
|
||||||
val style = TextStyle(
|
val context = LocalContext.current
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.width(colW)
|
||||||
|
.height(DAY_NUMBER_HEIGHT)
|
||||||
|
.clickable(openDayAction(context, date)),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.size(DAY_NUMBER_HEIGHT)
|
||||||
|
.then(if (isToday) GlanceModifier.cornerRadius(DAY_NUMBER_HEIGHT / 2).background(GlanceTheme.colors.primary) else GlanceModifier),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = date.day.toString(),
|
||||||
|
style = TextStyle(
|
||||||
color = when {
|
color = when {
|
||||||
isToday -> GlanceTheme.colors.onPrimary
|
isToday -> GlanceTheme.colors.onPrimary
|
||||||
inMonth -> GlanceTheme.colors.onSurface
|
inMonth -> GlanceTheme.colors.onSurface
|
||||||
@@ -407,78 +344,38 @@ private fun DayNumber(date: LocalDate, isToday: Boolean, inMonth: Boolean, colW:
|
|||||||
},
|
},
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal,
|
fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal,
|
||||||
textAlign = TextAlign.Center,
|
),
|
||||||
)
|
)
|
||||||
if (!isToday) {
|
|
||||||
Text(text = date.day.toString(), style = style, modifier = GlanceModifier.width(colW))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Box(modifier = GlanceModifier.width(colW), contentAlignment = Alignment.Center) {
|
|
||||||
Box(
|
|
||||||
modifier = GlanceModifier
|
|
||||||
.size(DAY_NUMBER_HEIGHT)
|
|
||||||
.cornerRadius(DAY_NUMBER_HEIGHT / 2)
|
|
||||||
.background(GlanceTheme.colors.primary),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Text(text = date.day.toString(), style = style)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A bar occupying [cols] columns, or — with a null event — that many blank ones. */
|
|
||||||
private data class LaneCell(val event: EventInstance?, val cols: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A lane split into bars and the gaps between them, with consecutive blank columns
|
|
||||||
* merged into one gap: the week's day-open taps come from the strip underneath, so
|
|
||||||
* a gap needs no per-column view and a mostly-empty lane collapses to a spacer.
|
|
||||||
*/
|
|
||||||
private fun MonthWeek.laneCells(lane: Int): List<LaneCell> {
|
|
||||||
val cells = mutableListOf<LaneCell>()
|
|
||||||
var gap = 0
|
|
||||||
var col = 0
|
|
||||||
fun closeGap() {
|
|
||||||
if (gap > 0) cells += LaneCell(null, gap)
|
|
||||||
gap = 0
|
|
||||||
}
|
|
||||||
while (col < 7) {
|
|
||||||
val span = spans.firstOrNull { it.lane == lane && col in it.startCol..it.endCol }
|
|
||||||
val timed = if (span == null) timedEventAt(this, lane, col, days[col]) else null
|
|
||||||
when {
|
|
||||||
span != null -> {
|
|
||||||
closeGap()
|
|
||||||
cells += LaneCell(span.event, span.endCol - col + 1)
|
|
||||||
col = span.endCol + 1
|
|
||||||
}
|
|
||||||
timed != null -> {
|
|
||||||
closeGap()
|
|
||||||
cells += LaneCell(timed, 1)
|
|
||||||
col += 1
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
gap += 1
|
|
||||||
col += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeGap()
|
|
||||||
return cells
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, soften: Boolean, colW: Dp) {
|
private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, soften: Boolean, colW: Dp) {
|
||||||
|
val context = LocalContext.current
|
||||||
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
week.laneCells(lane).forEach { cell ->
|
var col = 0
|
||||||
if (cell.event == null) {
|
while (col < 7) {
|
||||||
Spacer(GlanceModifier.width(colW * cell.cols).height(LANE_HEIGHT))
|
val span = week.spans.firstOrNull { it.lane == lane && col in it.startCol..it.endCol }
|
||||||
|
if (span != null) {
|
||||||
|
val cols = span.endCol - col + 1
|
||||||
|
SpanBar(event = span.event, dark = dark, soften = soften, width = colW * cols)
|
||||||
|
col = span.endCol + 1
|
||||||
} else {
|
} else {
|
||||||
SpanBar(
|
val timed = timedEventAt(week, lane, col, week.days[col])
|
||||||
event = cell.event,
|
if (timed != null) {
|
||||||
dark = dark,
|
SpanBar(event = timed, dark = dark, soften = soften, width = colW)
|
||||||
soften = soften,
|
} else {
|
||||||
width = colW * cell.cols,
|
// Empty lane cell: a tap opens that day, so blank space in a
|
||||||
)
|
// day column is a day-open target just like the number is.
|
||||||
|
Box(
|
||||||
|
GlanceModifier
|
||||||
|
.width(colW)
|
||||||
|
.height(LANE_HEIGHT)
|
||||||
|
.clickable(openDayAction(context, week.days[col])),
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
col += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -517,39 +414,41 @@ private fun SpanBar(event: EventInstance, dark: Boolean, soften: Boolean, width:
|
|||||||
Text(
|
Text(
|
||||||
text = event.title.ifBlank { context.getString(R.string.event_untitled) },
|
text = event.title.ifBlank { context.getString(R.string.event_untitled) },
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = TextStyle(
|
style = TextStyle(color = ColorProvider(eventInk(fill)), fontSize = 9.sp),
|
||||||
color = ColorProvider(eventInk(fill)),
|
|
||||||
fontSize = 9.sp,
|
|
||||||
textDecoration = glanceDeclinedDecoration(event.isDeclined),
|
|
||||||
),
|
|
||||||
modifier = GlanceModifier.padding(horizontal = 3.dp),
|
modifier = GlanceModifier.padding(horizontal = 3.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The "+N" row, drawn only for weeks that hide something, gaps merged as in a lane. */
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun OverflowRow(hidden: List<Int>, colW: Dp) {
|
private fun OverflowRow(week: MonthWeek, colW: Dp) {
|
||||||
|
val context = LocalContext.current
|
||||||
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
var gap = 0
|
week.days.forEachIndexed { col, date ->
|
||||||
hidden.forEach { count ->
|
val shownSpans = week.spans.count { col in it.startCol..it.endCol && it.lane < MAX_LANES }
|
||||||
if (count == 0) {
|
val freeSlots = (MAX_LANES - shownSpans).coerceAtLeast(0)
|
||||||
gap += 1
|
val timedShown = minOf(freeSlots, week.timedByDay[date].orEmpty().size)
|
||||||
return@forEach
|
val hidden = (week.countByDay[date] ?: 0) - shownSpans - timedShown
|
||||||
}
|
// The overflow row is part of the day column too: tapping it (whether
|
||||||
if (gap > 0) {
|
// it shows "+N" or is blank) opens that day, same as the app.
|
||||||
Spacer(GlanceModifier.width(colW * gap).height(LANE_HEIGHT))
|
Box(
|
||||||
gap = 0
|
modifier = GlanceModifier
|
||||||
}
|
.width(colW)
|
||||||
|
.height(LANE_HEIGHT)
|
||||||
|
.clickable(openDayAction(context, date)),
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
|
if (hidden > 0) {
|
||||||
Text(
|
Text(
|
||||||
text = "+$count",
|
text = "+$hidden",
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 9.sp),
|
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 9.sp),
|
||||||
modifier = GlanceModifier.width(colW).padding(start = 3.dp),
|
modifier = GlanceModifier.padding(start = 3.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (gap > 0) Spacer(GlanceModifier.width(colW * gap).height(LANE_HEIGHT))
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
<string name="state_failure_no_calendars_action">فتح إعدادات تقويم النظام</string>
|
<string name="state_failure_no_calendars_action">فتح إعدادات تقويم النظام</string>
|
||||||
<string name="state_failure_provider">تعذّر قراءة التقويم.</string>
|
<string name="state_failure_provider">تعذّر قراءة التقويم.</string>
|
||||||
<string name="permission_rationale_title">شاهد جميع أحداثك، بشكل جميل</string>
|
<string name="permission_rationale_title">شاهد جميع أحداثك، بشكل جميل</string>
|
||||||
<string name="permission_rationale_body">Calendula يحتاج للوصول إلى تقويمك لعرض و إدارة أحداثك.</string>
|
<string name="permission_rationale_body">Calendula يحتاج للوصول إلى تقويمك لعرض و إدارة أحداثك. هذا كل ما يطلبه مقدمًا — ولا يخرج أي شيء من جهازك على الإطلاق.</string>
|
||||||
<string name="permission_request_button">منح الوصول إلى التقويم</string>
|
<string name="permission_request_button">منح الوصول إلى التقويم</string>
|
||||||
<string name="permission_denied_title">رُفِض الوصول إلى التقويم</string>
|
<string name="permission_denied_title">رُفِض الوصول إلى التقويم</string>
|
||||||
<string name="permission_denied_body">Calendula لا يمكنه عرض الأحداث بدون الوصول إلى التقويم. يمكنك منحه مُجددًا في إعدادات النظام.</string>
|
<string name="permission_denied_body">Calendula لا يمكنه عرض الأحداث بدون الوصول إلى التقويم. يمكنك منحه مُجددًا في إعدادات النظام.</string>
|
||||||
<string name="permission_retry_button">أعِد المحاولة</string>
|
<string name="permission_retry_button">أعِد المحاولة</string>
|
||||||
<string name="permission_open_settings_button">فتح إعدادات النظام</string>
|
<string name="permission_open_settings_button">فتح إعدادات النظام</string>
|
||||||
<string name="permission_benefit_private_title">الخصوصية حسب التصميم</string>
|
<string name="permission_benefit_private_title">يبقى على جهازك</string>
|
||||||
<string name="permission_benefit_private_body">لا يوجد إذن بالإنترنت، ولا تتبع عن بُعد — لا شيء يغادر هاتفك أبدًا.</string>
|
<string name="permission_benefit_private_body">تقويماتك تتِم قراءتها محليًا ولا تُغادر الهاتف أبدًا.</string>
|
||||||
<string name="permission_benefit_sync_title">جميع تقويماتك، مع بعض</string>
|
<string name="permission_benefit_sync_title">جميع تقويماتك، مع بعض</string>
|
||||||
<string name="permission_benefit_sync_body">جوجل، CalDAV، محلي — أي شيء تمت مزامنته مع الجهاز يظهر فورًا.</string>
|
<string name="permission_benefit_sync_body">جوجل، CalDAV، محلي — أي شيء تمت مزامنته مع الجهاز يظهر فورًا.</string>
|
||||||
<string name="permission_benefit_privacy_title">لا تتبع، أبدًا</string>
|
<string name="permission_benefit_privacy_title">لا تتبع، أبدًا</string>
|
||||||
@@ -126,7 +126,6 @@
|
|||||||
<string name="event_edit_recurrence_none">لا يتكرر</string>
|
<string name="event_edit_recurrence_none">لا يتكرر</string>
|
||||||
<string name="import_reminder_prompt_body_none">تم استيراد هذا الحدث بدون أي تذكير.</string>
|
<string name="import_reminder_prompt_body_none">تم استيراد هذا الحدث بدون أي تذكير.</string>
|
||||||
<plurals name="import_reminder_prompt_body_existing">
|
<plurals name="import_reminder_prompt_body_existing">
|
||||||
<item quantity="zero">تم استيراد هذا الحدث مع (%1$d) لا تذكيرات.</item>
|
|
||||||
<item quantity="one">تم استيراد هذا الحدث مع %1$d تذكير.</item>
|
<item quantity="one">تم استيراد هذا الحدث مع %1$d تذكير.</item>
|
||||||
<item quantity="two">تم استيراد هذا الحدث مع %1$d تذكيران.</item>
|
<item quantity="two">تم استيراد هذا الحدث مع %1$d تذكيران.</item>
|
||||||
<item quantity="few">تم استيراد هذا الحدث مع %1$d تذكيرات.</item>
|
<item quantity="few">تم استيراد هذا الحدث مع %1$d تذكيرات.</item>
|
||||||
@@ -178,7 +177,7 @@
|
|||||||
<string name="reminder_channel_name">تذكيرات الحدث</string>
|
<string name="reminder_channel_name">تذكيرات الحدث</string>
|
||||||
<string name="reminder_channel_description">الإشعارات في أوقات التذكير الخاصه بأحداثك</string>
|
<string name="reminder_channel_description">الإشعارات في أوقات التذكير الخاصه بأحداثك</string>
|
||||||
<string name="reminder_onboarding_title">لا تفوّت أي حدث أبدًا</string>
|
<string name="reminder_onboarding_title">لا تفوّت أي حدث أبدًا</string>
|
||||||
<string name="reminder_onboarding_body">أندرويد لا يعرض تذكيرات الأحداث من نفسه — بل يجب أن يقوم تطبيق تقويم بذلك. دع Calendula يتولى هذه المهمة.</string>
|
<string name="reminder_onboarding_body">Android لا يعرض تذكيرات الأحداث من نفسه — بل يجب أن يقوم تطبيق تقويم بذلك. دع Calendula يتولى هذه المهمة.</string>
|
||||||
<string name="reminder_benefit_delivery_body">كل تذكير لأحداثك يصِل كإشعار، في الوقت المحدد تمامًا.</string>
|
<string name="reminder_benefit_delivery_body">كل تذكير لأحداثك يصِل كإشعار، في الوقت المحدد تمامًا.</string>
|
||||||
<string name="reminder_benefit_duplicates_title">هل تستخدم تطبيق تقويم ثانٍ؟</string>
|
<string name="reminder_benefit_duplicates_title">هل تستخدم تطبيق تقويم ثانٍ؟</string>
|
||||||
<string name="reminder_benefit_duplicates_body">إذا تطبيق آخر أيضًا ينشر تذكيرات، فستراهم مرتين — قم بإيقاف تشغيلهم هناك أو هنا.</string>
|
<string name="reminder_benefit_duplicates_body">إذا تطبيق آخر أيضًا ينشر تذكيرات، فستراهم مرتين — قم بإيقاف تشغيلهم هناك أو هنا.</string>
|
||||||
@@ -243,17 +242,17 @@
|
|||||||
<string name="agenda_range_this_week">هذا الأسبوع</string>
|
<string name="agenda_range_this_week">هذا الأسبوع</string>
|
||||||
<string name="settings_past_events_show">إظهار</string>
|
<string name="settings_past_events_show">إظهار</string>
|
||||||
<string name="settings_past_events_hide">إخفاء</string>
|
<string name="settings_past_events_hide">إخفاء</string>
|
||||||
<string name="settings_agenda_header">الجدول</string>
|
<string name="settings_agenda_header">جدول</string>
|
||||||
<string name="event_edit_timezone_device">المنطقة الزمنية للجهاز</string>
|
<string name="event_edit_timezone_device">المنطقة الزمنية للجهاز</string>
|
||||||
<string name="event_edit_timezone_search">البحث عن المناطق الزمنية</string>
|
<string name="event_edit_timezone_search">البحث عن المناطق الزمنية</string>
|
||||||
<string name="event_edit_timezone_all">جميع المناطق الزمنية</string>
|
<string name="event_edit_timezone_all">جميع المناطق الزمنية</string>
|
||||||
<string name="event_edit_timezone_none">لا منطقة زمنية تطابق ”%1$s“</string>
|
<string name="event_edit_timezone_none">لا منطقة زمنية تطابق \"%1$s\"</string>
|
||||||
<string name="event_edit_timezone_local_time">%1$s توقيتك</string>
|
<string name="event_edit_timezone_local_time">%1$s توقيتك</string>
|
||||||
<string name="event_edit_timezone_recent">الأحدث</string>
|
<string name="event_edit_timezone_recent">الأحدث</string>
|
||||||
<string name="event_edit_recurrence_incomplete">أدخل رقمًا من ١ إلى ٩٩٩</string>
|
<string name="event_edit_recurrence_incomplete">أدخل رقمًا من ١ إلى ٩٩٩</string>
|
||||||
<plurals name="duration_minutes">
|
<plurals name="duration_minutes">
|
||||||
<item quantity="zero">(%d) لا دقائق</item>
|
<item quantity="zero">(%d) لا دقائق</item>
|
||||||
<item quantity="one">%d دقيقة واحده</item>
|
<item quantity="one">(%d) دقيقة واحده</item>
|
||||||
<item quantity="two">%d دقيقتان</item>
|
<item quantity="two">%d دقيقتان</item>
|
||||||
<item quantity="few">%d دقائق</item>
|
<item quantity="few">%d دقائق</item>
|
||||||
<item quantity="many">%d دقيقة</item>
|
<item quantity="many">%d دقيقة</item>
|
||||||
@@ -261,7 +260,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="duration_hours">
|
<plurals name="duration_hours">
|
||||||
<item quantity="zero">(%d) لا ساعات</item>
|
<item quantity="zero">(%d) لا ساعات</item>
|
||||||
<item quantity="one">%d ساعة واحده</item>
|
<item quantity="one">(%d) ساعة واحده</item>
|
||||||
<item quantity="two">%d ساعتان</item>
|
<item quantity="two">%d ساعتان</item>
|
||||||
<item quantity="few">%d ساعات</item>
|
<item quantity="few">%d ساعات</item>
|
||||||
<item quantity="many">%d ساعة</item>
|
<item quantity="many">%d ساعة</item>
|
||||||
@@ -269,7 +268,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="duration_days">
|
<plurals name="duration_days">
|
||||||
<item quantity="zero">(%d) لا أيام</item>
|
<item quantity="zero">(%d) لا أيام</item>
|
||||||
<item quantity="one">%d يوم واحد</item>
|
<item quantity="one">(%d) يوم واحد</item>
|
||||||
<item quantity="two">%d يومان</item>
|
<item quantity="two">%d يومان</item>
|
||||||
<item quantity="few">%d أيام</item>
|
<item quantity="few">%d أيام</item>
|
||||||
<item quantity="many">%d يوم</item>
|
<item quantity="many">%d يوم</item>
|
||||||
@@ -277,7 +276,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="duration_weeks">
|
<plurals name="duration_weeks">
|
||||||
<item quantity="zero">(%d) لا أسابيع</item>
|
<item quantity="zero">(%d) لا أسابيع</item>
|
||||||
<item quantity="one">%d أسبوع واحد</item>
|
<item quantity="one">(%d) أسبوع واحد</item>
|
||||||
<item quantity="two">%d أسبوعان</item>
|
<item quantity="two">%d أسبوعان</item>
|
||||||
<item quantity="few">%d أسابيع</item>
|
<item quantity="few">%d أسابيع</item>
|
||||||
<item quantity="many">%d أسبوع</item>
|
<item quantity="many">%d أسبوع</item>
|
||||||
@@ -292,7 +291,7 @@
|
|||||||
<string name="settings_dim_completed_summary">تعتيم الأحداث التي انتهت بالفعل في عرض الشهر والأسبوع</string>
|
<string name="settings_dim_completed_summary">تعتيم الأحداث التي انتهت بالفعل في عرض الشهر والأسبوع</string>
|
||||||
<string name="settings_today_toolbar">زر اليوم في شريط الأدوات</string>
|
<string name="settings_today_toolbar">زر اليوم في شريط الأدوات</string>
|
||||||
<string name="settings_today_toolbar_summary">إظهار زر الانتقال إلى اليوم في شريط الأدوات بدلاً من زر عائم</string>
|
<string name="settings_today_toolbar_summary">إظهار زر الانتقال إلى اليوم في شريط الأدوات بدلاً من زر عائم</string>
|
||||||
<string name="settings_app_name_summary">اعرض Calendula كـ ”Calendar“ في مشغّل التطبيقات الخاص بك. فقط الاسم في المشغّل يتغير؛ وقد ينتقل الرمز إلى مكان جديد بعد التبديل.</string>
|
<string name="settings_app_name_summary">اعرض Calendula كـ \"Calendar\" في مشغّل التطبيقات الخاص بك. فقط الاسم في المشغّل يتغير؛ وقد ينتقل الرمز إلى مكان جديد بعد التبديل.</string>
|
||||||
<string name="settings_past_events_dim">تعتيم</string>
|
<string name="settings_past_events_dim">تعتيم</string>
|
||||||
<string name="settings_past_events">الأحداث السابقة</string>
|
<string name="settings_past_events">الأحداث السابقة</string>
|
||||||
<string name="settings_agenda_range">مدى الجدول</string>
|
<string name="settings_agenda_range">مدى الجدول</string>
|
||||||
@@ -315,6 +314,7 @@
|
|||||||
<string name="settings_form_fields_hint">الخانات المعروضة افتراضيًا — كل شيء آخر موجود ضمن \"المزيد من الخانات\"</string>
|
<string name="settings_form_fields_hint">الخانات المعروضة افتراضيًا — كل شيء آخر موجود ضمن \"المزيد من الخانات\"</string>
|
||||||
<string name="settings_section_event_form">نموذج حدث جديد</string>
|
<string name="settings_section_event_form">نموذج حدث جديد</string>
|
||||||
<string name="settings_quick_switch_header">زر التبديل السريع</string>
|
<string name="settings_quick_switch_header">زر التبديل السريع</string>
|
||||||
|
<string name="app_name">التقويم</string>
|
||||||
<string name="settings_theme_system_summary">الحالي %1$s</string>
|
<string name="settings_theme_system_summary">الحالي %1$s</string>
|
||||||
<string name="settings_theme_hint">سواء التطبيق فاتحًا أو داكنًا. الاختيار يُطبق على الفور.</string>
|
<string name="settings_theme_hint">سواء التطبيق فاتحًا أو داكنًا. الاختيار يُطبق على الفور.</string>
|
||||||
<string name="settings_week_start_auto_summary">الحالي %1$s</string>
|
<string name="settings_week_start_auto_summary">الحالي %1$s</string>
|
||||||
@@ -329,7 +329,7 @@
|
|||||||
<string name="settings_widget_size_extra_large">كبير جدًا</string>
|
<string name="settings_widget_size_extra_large">كبير جدًا</string>
|
||||||
<plurals name="agenda_range_days">
|
<plurals name="agenda_range_days">
|
||||||
<item quantity="zero">(%d) لا أيام</item>
|
<item quantity="zero">(%d) لا أيام</item>
|
||||||
<item quantity="one">%d يوم واحد</item>
|
<item quantity="one">(%d) يوم واحد</item>
|
||||||
<item quantity="two">%d يومان</item>
|
<item quantity="two">%d يومان</item>
|
||||||
<item quantity="few">%d أيام</item>
|
<item quantity="few">%d أيام</item>
|
||||||
<item quantity="many">%d يوم</item>
|
<item quantity="many">%d يوم</item>
|
||||||
@@ -383,7 +383,7 @@
|
|||||||
<string name="event_edit_recurrence_next">التالي: %1$s</string>
|
<string name="event_edit_recurrence_next">التالي: %1$s</string>
|
||||||
<plurals name="reminder_minutes">
|
<plurals name="reminder_minutes">
|
||||||
<item quantity="zero">(%d) لا دقائق قبل</item>
|
<item quantity="zero">(%d) لا دقائق قبل</item>
|
||||||
<item quantity="one">%d دقيقة واحده قبل</item>
|
<item quantity="one">(%d) دقيقة واحده قبل</item>
|
||||||
<item quantity="two">%d دقيقتان قبل</item>
|
<item quantity="two">%d دقيقتان قبل</item>
|
||||||
<item quantity="few">%d دقائق قبل</item>
|
<item quantity="few">%d دقائق قبل</item>
|
||||||
<item quantity="many">%d دقيقة قبل</item>
|
<item quantity="many">%d دقيقة قبل</item>
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="reminder_hours">
|
<plurals name="reminder_hours">
|
||||||
<item quantity="zero">(%d) لا ساعات قبل</item>
|
<item quantity="zero">(%d) لا ساعات قبل</item>
|
||||||
<item quantity="one">%d ساعة واحده قبل</item>
|
<item quantity="one">(%d) ساعة واحده قبل</item>
|
||||||
<item quantity="two">%d ساعتان قبل</item>
|
<item quantity="two">%d ساعتان قبل</item>
|
||||||
<item quantity="few">%d ساعات قبل</item>
|
<item quantity="few">%d ساعات قبل</item>
|
||||||
<item quantity="many">%d ساعة قبل</item>
|
<item quantity="many">%d ساعة قبل</item>
|
||||||
@@ -399,7 +399,7 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="reminder_days">
|
<plurals name="reminder_days">
|
||||||
<item quantity="zero">(%d) لا أيام قبل</item>
|
<item quantity="zero">(%d) لا أيام قبل</item>
|
||||||
<item quantity="one">%d يوم واحد قبل</item>
|
<item quantity="one">(%d) يوم واحد قبل</item>
|
||||||
<item quantity="two">%d يومان قبل</item>
|
<item quantity="two">%d يومان قبل</item>
|
||||||
<item quantity="few">%d أيام قبل</item>
|
<item quantity="few">%d أيام قبل</item>
|
||||||
<item quantity="many">%d يوم قبل</item>
|
<item quantity="many">%d يوم قبل</item>
|
||||||
@@ -407,13 +407,13 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="reminder_weeks">
|
<plurals name="reminder_weeks">
|
||||||
<item quantity="zero">(%d) لا أسابيع قبل</item>
|
<item quantity="zero">(%d) لا أسابيع قبل</item>
|
||||||
<item quantity="one">%d أسبوع واحد قبل</item>
|
<item quantity="one">(%d) أسبوع واحد قبل</item>
|
||||||
<item quantity="two">%d أسبوعان قبل</item>
|
<item quantity="two">%d أسبوعان قبل</item>
|
||||||
<item quantity="few">%d أسابيع قبل</item>
|
<item quantity="few">%d أسابيع قبل</item>
|
||||||
<item quantity="many">%d أسبوع قبل</item>
|
<item quantity="many">%d أسبوع قبل</item>
|
||||||
<item quantity="other">%d أسبوع قبل</item>
|
<item quantity="other">%d أسبوع قبل</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="reminder_benefit_reversible_body">يوجد مفتاح التبديل في الإعدادات، ضمن الإشعارات.</string>
|
<string name="reminder_benefit_reversible_body">يوجد المفتاح في الإعدادات، ضمن الإشعارات.</string>
|
||||||
<string name="reminder_custom_amount">القيمة</string>
|
<string name="reminder_custom_amount">القيمة</string>
|
||||||
<string name="settings_manage_calendars_hint">إنشاء تقويمات محلية؛ إدارة التقويمات المتزامنة</string>
|
<string name="settings_manage_calendars_hint">إنشاء تقويمات محلية؛ إدارة التقويمات المتزامنة</string>
|
||||||
<string name="settings_snooze_duration">مدة التأجيل</string>
|
<string name="settings_snooze_duration">مدة التأجيل</string>
|
||||||
@@ -451,13 +451,13 @@
|
|||||||
<string name="settings_special_dates_grant">منح الوصول</string>
|
<string name="settings_special_dates_grant">منح الوصول</string>
|
||||||
<string name="calendars_backup_action">تصدير كملف .ics</string>
|
<string name="calendars_backup_action">تصدير كملف .ics</string>
|
||||||
<string name="calendars_export_title">تصدير التقويمات</string>
|
<string name="calendars_export_title">تصدير التقويمات</string>
|
||||||
<string name="calendars_export_hint">اختر التقويمات التي تريد تضمينها في ملف .ics.</string>
|
<string name="calendars_export_hint">اختر التقويمات التي تريد تضمينها في .ics الملف.</string>
|
||||||
<string name="calendars_export_action">تصدير</string>
|
<string name="calendars_export_action">تصدير</string>
|
||||||
<string name="calendars_restore_header">استعادة</string>
|
<string name="calendars_restore_header">استعادة</string>
|
||||||
<string name="calendars_restore_action">استعادة من ملف .ics</string>
|
<string name="calendars_restore_action">استعادة من ملف .ics</string>
|
||||||
<string name="calendars_restore_hint">استيراد الأحداث من نسخة احتياطية أو تطبيق تقويم آخر.</string>
|
<string name="calendars_restore_hint">استيراد الأحداث من نسخة احتياطية أو تطبيق تقويم آخر.</string>
|
||||||
<string name="calendars_auto_backup">النسخ الاحتياطي التلقائي</string>
|
<string name="calendars_auto_backup">النسخ الاحتياطي التلقائي</string>
|
||||||
<string name="calendars_auto_backup_hint">قم بتصدير تقويماتك المحلية بشكل دوري إلى مجلد كـ ملف .ics.</string>
|
<string name="calendars_auto_backup_hint">قم بتصدير تقويماتك المحلية بشكل دوري إلى مجلد كـ .ics الملف.</string>
|
||||||
<string name="calendars_auto_backup_folder">مجلد النسخ الاحتياطي</string>
|
<string name="calendars_auto_backup_folder">مجلد النسخ الاحتياطي</string>
|
||||||
<string name="calendars_auto_backup_folder_unset">اضغط لاختيار مجلد</string>
|
<string name="calendars_auto_backup_folder_unset">اضغط لاختيار مجلد</string>
|
||||||
<string name="calendars_auto_backup_every">كل %1$s</string>
|
<string name="calendars_auto_backup_every">كل %1$s</string>
|
||||||
@@ -472,7 +472,7 @@
|
|||||||
<string name="calendars_backup_failed">تعذّر تصدير النسخة الاحتياطية.</string>
|
<string name="calendars_backup_failed">تعذّر تصدير النسخة الاحتياطية.</string>
|
||||||
<plurals name="calendars_backup_done">
|
<plurals name="calendars_backup_done">
|
||||||
<item quantity="zero">تم تصدير (%d) لا أحداث.</item>
|
<item quantity="zero">تم تصدير (%d) لا أحداث.</item>
|
||||||
<item quantity="one">تم تصدير %d حدث واحد.</item>
|
<item quantity="one">تم تصدير (%d) حدث واحد.</item>
|
||||||
<item quantity="two">تم تصدير %d حدثان.</item>
|
<item quantity="two">تم تصدير %d حدثان.</item>
|
||||||
<item quantity="few">تم تصدير %d أحداث.</item>
|
<item quantity="few">تم تصدير %d أحداث.</item>
|
||||||
<item quantity="many">تم تصدير %d حدث.</item>
|
<item quantity="many">تم تصدير %d حدث.</item>
|
||||||
@@ -520,7 +520,7 @@
|
|||||||
<string name="settings_past_events_hint">ما الذي يفعله جدول بالأحداث التي انتهت بالفعل.</string>
|
<string name="settings_past_events_hint">ما الذي يفعله جدول بالأحداث التي انتهت بالفعل.</string>
|
||||||
<string name="calendars_visibility_a11y">إظهار \"%1$s\"</string>
|
<string name="calendars_visibility_a11y">إظهار \"%1$s\"</string>
|
||||||
<string name="calendars_visibility_notice_title">بعض التقويمات متوقفة</string>
|
<string name="calendars_visibility_notice_title">بعض التقويمات متوقفة</string>
|
||||||
<string name="calendars_visibility_notice_message">Calendula يعرض التقويمات التي تم تشغيلها لهذا الجهاز، وبعض من خاصتك متوقفة. لرؤية أحداثهم، قم بتشغيلهم ضمن الإعدادات ← التقويمات.</string>
|
<string name="calendars_visibility_notice_message">Calendula يعرض الآن التقويمات التي تم تشغيلها لهذا الجهاز، لذلك ما تراه وما يذكرك لم يعد بإمكانه أن يختلف. بعض تقويماتك متوقفة حاليًا — تم إيقافها هنا أو في تطبيق تقويم آخر. أعد تشغيل أي منها في الإعدادات ← التقويمات.</string>
|
||||||
<string name="calendar_picker_missing_title">تفتقد تقويمًا؟</string>
|
<string name="calendar_picker_missing_title">تفتقد تقويمًا؟</string>
|
||||||
<string name="calendar_picker_missing_summary">قد يكون متوقفًا عن التشغيل، للقراءة فقط أو مملوءًا من جهات الاتصال الخاصة بك — يمكنك إدارة تقويماتك هنا.</string>
|
<string name="calendar_picker_missing_summary">قد يكون متوقفًا عن التشغيل، للقراءة فقط أو مملوءًا من جهات الاتصال الخاصة بك — يمكنك إدارة تقويماتك هنا.</string>
|
||||||
<string name="calendars_state_read_only">للقراءة فقط</string>
|
<string name="calendars_state_read_only">للقراءة فقط</string>
|
||||||
@@ -542,140 +542,4 @@
|
|||||||
<string name="timeline_scale_regular">العادي</string>
|
<string name="timeline_scale_regular">العادي</string>
|
||||||
<string name="timeline_scale_comfortable_summary">مجموعات أوسع، تمرير أكثر</string>
|
<string name="timeline_scale_comfortable_summary">مجموعات أوسع، تمرير أكثر</string>
|
||||||
<string name="timeline_scale_custom_summary">الارتفاع الذي قمت بضغظت المخطط الزمني إليه</string>
|
<string name="timeline_scale_custom_summary">الارتفاع الذي قمت بضغظت المخطط الزمني إليه</string>
|
||||||
<string name="event_edit_timezone_device_summary">يتابعك أينما كنت</string>
|
|
||||||
<string name="event_access_confidential_summary">مُعَلَّم كـ سري؛ ما يعنيه ذلك متروك لحساب التقويم</string>
|
|
||||||
<string name="settings_widget_size_summary">نص الحدث %1$d%%</string>
|
|
||||||
<string name="settings_calendar_durations_hint">امنح تقويم طوله الافتراضي خاص به — للمثال. ٨ ساعات لمناوبات العمل.</string>
|
|
||||||
<string name="settings_backup_subtitle">تصدير، استيراد، النسخ الاحتياطي التلقائي</string>
|
|
||||||
<string name="settings_views_all_header">كل طُرق العرض</string>
|
|
||||||
<string name="settings_calendar_durations_title">المدة لكل تقويم</string>
|
|
||||||
<string name="event_move_done">تم النقل إلى %1$s</string>
|
|
||||||
<string name="event_move_undo">تراجع</string>
|
|
||||||
<string name="event_move_undone">تم التراجع عن النقل</string>
|
|
||||||
<string name="event_move_failed">تعذّر نقل الحدث</string>
|
|
||||||
<string name="event_move_write_denied">Calendula يحتاج إلى صلاحية للكتابة لنقل الأحداث</string>
|
|
||||||
<string name="event_move_gone">لم يعد هذا الحدث موجودًا</string>
|
|
||||||
<string name="onboarding_step_counter">الخطوة %1$d من %2$d</string>
|
|
||||||
<string name="onboarding_backup_title">أحداثك تعيش فقط هنا</string>
|
|
||||||
<string name="onboarding_backup_body">لا شئ على هذا الهاتف تتم مزامنته لحساب، لذلك فقدانها سيؤدي لفقدان تقويمك. Calendula يمكنه كتابة نسخة احتياطية لك.</string>
|
|
||||||
<string name="onboarding_backup_benefit_folder_body">النسخ الاحتياطية هي ملفات .ics عادية — ضعها في مكان ما يزامن، أو على بطاقة SD.</string>
|
|
||||||
<string name="onboarding_backup_benefit_daily_title">مرة واحدة يوميًا، من تلقاء نفسه</string>
|
|
||||||
<string name="onboarding_backup_benefit_daily_body">Calendula يقم بتصدير تقويماتك المحلية في الخلفية. يمكنك تغيير مدى المرات في الإعدادات.</string>
|
|
||||||
<string name="onboarding_backup_enable_button">اختر مجلد وانسخ احتياطيًا</string>
|
|
||||||
<string name="onboarding_backup_skip_button">ليس الآن</string>
|
|
||||||
<string name="onboarding_view_title">ما الذي يجب أن يفتح أولاً؟</string>
|
|
||||||
<string name="onboarding_month_style_title">كيف يجب أن يبدو الشهر؟</string>
|
|
||||||
<string name="onboarding_view_continue_button">متابعة</string>
|
|
||||||
<string name="onboarding_back">رجوع</string>
|
|
||||||
<string name="onboarding_visibility_button">فهمت</string>
|
|
||||||
<string name="onboarding_done_title">تم إعدادك بالكامل</string>
|
|
||||||
<string name="onboarding_done_body">تقويماتك جاهزه. يمكن تغيير كل ما اخترته للتو لاحقًا في الإعدادات.</string>
|
|
||||||
<string name="onboarding_done_button">افتح تقويمي</string>
|
|
||||||
<plurals name="search_selected_count">
|
|
||||||
<item quantity="zero">تم تحديد (%d)</item>
|
|
||||||
<item quantity="one">تم تحديد %d</item>
|
|
||||||
<item quantity="two">تم تحديد %d</item>
|
|
||||||
<item quantity="few">تم تحديد %d</item>
|
|
||||||
<item quantity="many">تم تحديد %d</item>
|
|
||||||
<item quantity="other">تم تحديد %d</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="search_in_descriptions">عُثِر في الأوصاف</string>
|
|
||||||
<string name="search_selection_close">إلغاء التحديد</string>
|
|
||||||
<string name="search_select_all">تحديد الكل</string>
|
|
||||||
<string name="search_delete_selected">حذف المُحدّد</string>
|
|
||||||
<plurals name="search_delete_title">
|
|
||||||
<item quantity="zero">حذف (%d) حدث؟</item>
|
|
||||||
<item quantity="one">حذف %d حدث واحد؟</item>
|
|
||||||
<item quantity="two">حذف %d حدثان؟</item>
|
|
||||||
<item quantity="few">حذف %d أحداث؟</item>
|
|
||||||
<item quantity="many">حذف %d حدث؟</item>
|
|
||||||
<item quantity="other">حذف %d حدث؟</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="search_delete_done">
|
|
||||||
<item quantity="zero">تم حذف (%d) لا حدث</item>
|
|
||||||
<item quantity="one">تم حذف %d حدث واحد</item>
|
|
||||||
<item quantity="two">تم حذف %d حدثان</item>
|
|
||||||
<item quantity="few">تم حذف %d أحداث</item>
|
|
||||||
<item quantity="many">تم حذف %d حدث</item>
|
|
||||||
<item quantity="other">تم حذف %d حدث</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="search_delete_partial">تم حذف %1$d، وتعذّر حذف %2$d</string>
|
|
||||||
<string name="search_delete_denied_partial">تم حذف %1$d، ثم تم سحب صلاحية للكتابة</string>
|
|
||||||
<plurals name="import_done_skipped">
|
|
||||||
<item quantity="zero">تم تخطي (%d) بالفعل في هذا التقويم.</item>
|
|
||||||
<item quantity="one">تم تخطي %d بالفعل في هذا التقويم.</item>
|
|
||||||
<item quantity="two">تم تخطي %d بالفعل في هذا التقويم.</item>
|
|
||||||
<item quantity="few">تم تخطي %d بالفعل في هذا التقويم.</item>
|
|
||||||
<item quantity="many">تم تخطي %d بالفعل في هذا التقويم.</item>
|
|
||||||
<item quantity="other">تم تخطي %d بالفعل في هذا التقويم.</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="import_done_imported">
|
|
||||||
<item quantity="zero">تم استيراد (%d) لا أحداث.</item>
|
|
||||||
<item quantity="one">تم استيراد %d حدث واحد.</item>
|
|
||||||
<item quantity="two">تم استيراد %d حدثان.</item>
|
|
||||||
<item quantity="few">تم استيراد %d أحداث.</item>
|
|
||||||
<item quantity="many">تم استيراد %d حدث.</item>
|
|
||||||
<item quantity="other">تم استيراد %d حدث.</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="import_action">
|
|
||||||
<item quantity="zero">استيراد (%d) لا أحداث</item>
|
|
||||||
<item quantity="one">استيراد %d حدث واحد</item>
|
|
||||||
<item quantity="two">استيراد %d حدثان</item>
|
|
||||||
<item quantity="few">استيراد %d أحداث</item>
|
|
||||||
<item quantity="many">استيراد %d حدث</item>
|
|
||||||
<item quantity="other">استيراد %d حدث</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="import_event_count">
|
|
||||||
<item quantity="zero">(%d) لا أحداث في هذا الملف.</item>
|
|
||||||
<item quantity="one">%d حدث واحد في هذا الملف.</item>
|
|
||||||
<item quantity="two">%d حدثان في هذا الملف.</item>
|
|
||||||
<item quantity="few">%d أحداث في هذا الملف.</item>
|
|
||||||
<item quantity="many">%d حدث في هذا الملف.</item>
|
|
||||||
<item quantity="other">%d حدث في هذا الملف.</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="import_title_count">
|
|
||||||
<item quantity="zero">يتم استيراد (%d) لا أحداث</item>
|
|
||||||
<item quantity="one">يتم استيراد %d حدث واحد</item>
|
|
||||||
<item quantity="two">يتم استيراد %d حدثان</item>
|
|
||||||
<item quantity="few">يتم استيراد %d أحداث</item>
|
|
||||||
<item quantity="many">يتم استيراد %d حدث</item>
|
|
||||||
<item quantity="other">يتم استيراد %d حدث</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="event_move_recurring_title">نقل الحدث المتكرر</string>
|
|
||||||
<string name="event_edit_conflict_overwrite_hint">فقط الحقول التي عدلتها هي التي ستحل محل التغيير الخارجي</string>
|
|
||||||
<string name="reorder_drag_handle">اسحب لإعادة الترتيب</string>
|
|
||||||
<string name="settings_drawer_order_hint">اسحب لإعادة ترتيب طرق العرض المدرجة في قائمة التنقل.</string>
|
|
||||||
<string name="settings_drawer_order_header">قائمة التنقل</string>
|
|
||||||
<string name="settings_default_reminder_allday">أحداث يوم كامل</string>
|
|
||||||
<string name="settings_allday_reminder_time">وقت تذكير اليوم الكامل</string>
|
|
||||||
<string name="reminder_none">لا شئ</string>
|
|
||||||
<string name="settings_reliable_delivery_hint">أندرويد قد يؤخر التذكيرات لتوفير البطارية. استثنِ Calendula لكي تصل في الوقت المحدد.</string>
|
|
||||||
<string name="settings_calendar_reminders_title">التذكيرات لكل تقويم</string>
|
|
||||||
<string name="settings_license_value">MIT</string>
|
|
||||||
<string name="settings_special_dates_paused_title">متوقف</string>
|
|
||||||
<string name="settings_special_dates_calendar_hint">اضبط لون كل تقويم وإمكانية ظهوره في إعدادات التقويمات.</string>
|
|
||||||
<string name="calendars_visibility_hint">قم بإيقاف تقويم لإخفائه على هذا الجهاز — تختفي أحداثه من التطبيق ويتوقف عن تذكيرك. هذا هو مفتاح التبديل نفسه الذي تستخدمه تطبيقات التقويم الأخرى خاصتك، لذلك تقوم بإخفائه أيضًا. لا شيء يتم حذفه، ولا يتأثر أي جهاز آخر، ويمكنك إعادة تشغيله من هنا في أي وقت.</string>
|
|
||||||
<string name="settings_allday_reminder_fires_at">يتم الإشعار في %1$s</string>
|
|
||||||
<string name="settings_agenda_widget_range">نطاق ودجت الجدول</string>
|
|
||||||
<string name="settings_widget_size">حجم ودجت الجدول</string>
|
|
||||||
<string name="settings_widgets_subtitle">ودجت الجدول، عنصر الإعدادات السريعة</string>
|
|
||||||
<string name="settings_drag_to_reschedule">السحب لتعديل الموعد</string>
|
|
||||||
<string name="settings_drag_to_reschedule_summary">انقل حدثًا عن طريق سحبه إلى يوم أو وقت آخر</string>
|
|
||||||
<string name="event_move_blocked_series_end">لا يمكن نقل حدث بعد نهاية سلسلته</string>
|
|
||||||
<string name="crash_dialog_report">تقرير</string>
|
|
||||||
<string name="settings_quick_switch_hint">اختر أي طُرق العرض التي يتم التنقل بينها باستخدام الزر الموجود في أعلى اليمين، واسحب لإعادة ترتيبها. تظل طرق العرض التي تم إيقاف تشغيلها متاحة من قائمة التنقل.</string>
|
|
||||||
<string name="calendars_account_from_source">%1$s (%2$s)</string>
|
|
||||||
<string name="duration_hours_minutes">%1$s %2$s</string>
|
|
||||||
<string name="settings_widgets_hint">إعدادات لوجدتز الشاشة الرئيسية وعنصر لوحة الإعدادات السريعة. أضف وجدت عن طريق الضغط لفترة طويلة على شاشتك الرئيسية.</string>
|
|
||||||
<string name="import_warning_attendees">لم يتم استيراد قوائم الضيوف.</string>
|
|
||||||
<string name="settings_reliable_delivery_exempt">معفى من تحسين البطارية — تصل التذكيرات في الوقت المحدد.</string>
|
|
||||||
<string name="settings_reliable_delivery">توصيل موثوق</string>
|
|
||||||
<string name="settings_allday_reminder_time_hint">تنطلق التذكيرات لأحداث التي تستمر طوال اليوم عند %1$s</string>
|
|
||||||
<string name="agenda_range_override_hint">فقط للآن — يعيد التعيين إلى المدى المحفوظ الخاص بك عند إعادة فتح Calendula.</string>
|
|
||||||
<string name="settings_past_events_sample_morning">مراجعة الفريق</string>
|
|
||||||
<string name="settings_past_events_sample_midday">غداء مع روبن</string>
|
|
||||||
<string name="settings_past_events_sample_evening">ممارسة الجوقة</string>
|
|
||||||
<string name="settings_special_dates_last_synced">آخر مزامنة %1$s</string>
|
|
||||||
<string name="settings_special_dates_disable_type_message">يؤدي هذا لحذف التقويم ”%1$s“ وأحداثه. سيتم فقدان أي تذكيرات أو ملاحظات أضفتها إليه.</string>
|
|
||||||
<string name="special_dates_default_title_birthday">عيد ميلاد {name} ({year})</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
<string name="state_failure_provider">Kalender konnte nicht gelesen werden.</string>
|
<string name="state_failure_provider">Kalender konnte nicht gelesen werden.</string>
|
||||||
<!-- Permission-Flow (F1) -->
|
<!-- Permission-Flow (F1) -->
|
||||||
<string name="permission_rationale_title">Alle Termine, schön im Blick</string>
|
<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.</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_request_button">Kalender-Zugriff erlauben</string>
|
<string name="permission_request_button">Kalender-Zugriff erlauben</string>
|
||||||
<string name="permission_denied_title">Kalender-Zugriff abgelehnt</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_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_open_settings_button">System-Einstellungen öffnen</string>
|
||||||
<string name="permission_retry_button">Erneut versuchen</string>
|
<string name="permission_retry_button">Erneut versuchen</string>
|
||||||
<string name="permission_benefit_private_title">Datenschutz by Design</string>
|
<string name="permission_benefit_private_title">Bleibt auf deinem Gerät</string>
|
||||||
<string name="permission_benefit_private_body">Keine Internetberechtigung, keine Telemetrie — nichts verlässt dein Telefon.</string>
|
<string name="permission_benefit_private_body">Deine Kalender werden lokal gelesen und verlassen das Telefon nie.</string>
|
||||||
<string name="permission_benefit_sync_title">Alle Kalender vereint</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_sync_body">Google, CalDAV, lokal — alles, was synchronisiert ist, erscheint automatisch.</string>
|
||||||
<string name="permission_benefit_privacy_title">Kein Tracking, niemals</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">App-Sprache</string>
|
||||||
<string name="settings_language_auto">Systemstandard</string>
|
<string name="settings_language_auto">Systemstandard</string>
|
||||||
<!-- Hub category subtitles -->
|
<!-- Hub category subtitles -->
|
||||||
<string name="settings_appearance_subtitle">Theme, Farben, Schriftarten</string>
|
<string name="settings_appearance_subtitle">Design, Standardansicht, Wochenstart</string>
|
||||||
<string name="settings_event_form_subtitle">Standardfelder und Verhalten</string>
|
<string name="settings_event_form_subtitle">Standardfelder für neue Termine</string>
|
||||||
<string name="settings_notifications_subtitle">Erinnerungen und Zustellung</string>
|
<string name="settings_notifications_subtitle">Termin-Erinnerungen</string>
|
||||||
<string name="settings_section_about">Über</string>
|
<string name="settings_section_about">Über</string>
|
||||||
<string name="settings_license">Lizenz</string>
|
<string name="settings_license">Lizenz</string>
|
||||||
<string name="settings_license_value">MIT</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_calendar_reminders_title">Erinnerungen pro Kalender</string>
|
||||||
<string name="settings_translate">Hilf beim Übersetzen</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_translate_hint">Füge eine Sprache auf Weblate hinzu oder verbessere sie</string>
|
||||||
<string name="settings_views_subtitle">Standartansicht, Anordnung, Menüreihenfolge</string>
|
<string name="settings_views_subtitle">Schnellwechsellnopf und Menüreihenfolge</string>
|
||||||
<string name="settings_special_dates_subtitle">Kontakte-Geburtstage und Jubiläen</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_section_special_dates">Besondere Termine von Kontakten</string>
|
||||||
<string name="settings_special_dates_enable">Kontakttermine anzeigen</string>
|
<string name="settings_special_dates_enable">Kontakttermine anzeigen</string>
|
||||||
@@ -498,154 +498,4 @@
|
|||||||
<string name="event_edit_timezone_none">Keine Zeitzone passt zu “%1$s”</string>
|
<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="event_edit_timezone_local_time">%1$s deiner Zeit</string>
|
||||||
<string name="import_reminder_prompt_title">Deine Standarterinnerung anwenden?</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>
|
</resources>
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
<string name="state_failure_no_calendars_action">Apri le impostazioni calendario di sistema</string>
|
<string name="state_failure_no_calendars_action">Apri le impostazioni calendario di sistema</string>
|
||||||
<string name="state_failure_provider">Errore nella lettura del calendario.</string>
|
<string name="state_failure_provider">Errore nella lettura del calendario.</string>
|
||||||
<string name="permission_rationale_title">Gestisci magnificamente tutti i tuoi eventi</string>
|
<string name="permission_rationale_title">Gestisci magnificamente tutti i tuoi eventi</string>
|
||||||
<string name="permission_rationale_body">Calendula ha bisogno di accedere al tuo calendario per mostrare e gestire gli eventi.</string>
|
<string name="permission_rationale_body">Calendula ha bisogno di accedere al tuo calendario per mostrare e gestire gli eventi. È l\'unica cosa di cui ha bisogno: nessuna informazione lascerà mai il tuo dispositivo.</string>
|
||||||
<string name="permission_request_button">Concedi l\'accesso al calendario</string>
|
<string name="permission_request_button">Concedi l\'accesso al calendario</string>
|
||||||
<string name="permission_denied_title">Accesso al calendario negato</string>
|
<string name="permission_denied_title">Accesso al calendario negato</string>
|
||||||
<string name="permission_denied_body">Calendula non può mostrare gli eventi senza accesso al calendario. Puoi concederlo dalle impostazioni di sistema.</string>
|
<string name="permission_denied_body">Calendula non può mostrare gli eventi senza accesso al calendario. Puoi concederlo dalle impostazioni di sistema.</string>
|
||||||
<string name="permission_open_settings_button">Apri le impostazioni di sistema</string>
|
<string name="permission_open_settings_button">Apri le impostazioni di sistema</string>
|
||||||
<string name="permission_retry_button">Riprova</string>
|
<string name="permission_retry_button">Riprova</string>
|
||||||
<string name="permission_benefit_private_title">Privato by design</string>
|
<string name="permission_benefit_private_title">Rimane sul tuo dispositivo</string>
|
||||||
<string name="permission_benefit_private_body">Nessun accesso a internet, nessun tracking: nulla lascia il tuo dispositivo.</string>
|
<string name="permission_benefit_private_body">I tuoi calendari sono letti localmente e non lasciano mai il tuo dispositivo.</string>
|
||||||
<string name="permission_benefit_sync_title">Tutti i tuoi calendari, insieme</string>
|
<string name="permission_benefit_sync_title">Tutti i tuoi calendari, insieme</string>
|
||||||
<string name="permission_benefit_sync_body">Google, CalDAV, locali - qualsiasi calendario sincronizzato sul dispositivo, semplicemente, compare.</string>
|
<string name="permission_benefit_sync_body">Google, CalDAV, locali - qualsiasi calendario sincronizzato sul dispositivo, semplicemente, compare.</string>
|
||||||
<string name="permission_benefit_privacy_title">Nessun tracking, per sempre</string>
|
<string name="permission_benefit_privacy_title">Nessun tracking, per sempre</string>
|
||||||
@@ -301,8 +301,8 @@
|
|||||||
<string name="settings_language_auto">Lingua di sistema</string>
|
<string name="settings_language_auto">Lingua di sistema</string>
|
||||||
<string name="settings_translate">Aiuta a tradurre</string>
|
<string name="settings_translate">Aiuta a tradurre</string>
|
||||||
<string name="settings_translate_hint">Aggiungi o migliora una traduzione su Weblate</string>
|
<string name="settings_translate_hint">Aggiungi o migliora una traduzione su Weblate</string>
|
||||||
<string name="settings_appearance_subtitle">Tema, colori, font</string>
|
<string name="settings_appearance_subtitle">Tema, vista di default, inizio settimana</string>
|
||||||
<string name="settings_event_form_subtitle">Campi di default e comportamento</string>
|
<string name="settings_event_form_subtitle">Campi di default per nuovi eventi</string>
|
||||||
<string name="settings_section_about">A proposito</string>
|
<string name="settings_section_about">A proposito</string>
|
||||||
<string name="settings_license">Licenza</string>
|
<string name="settings_license">Licenza</string>
|
||||||
<string name="settings_license_value">MIT</string>
|
<string name="settings_license_value">MIT</string>
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
<string name="agenda_range_this_week">Questa settimana</string>
|
<string name="agenda_range_this_week">Questa settimana</string>
|
||||||
<string name="settings_reminders">Promemoria per gli eventi</string>
|
<string name="settings_reminders">Promemoria per gli eventi</string>
|
||||||
<string name="settings_default_reminder">Promemoria per eventi standard</string>
|
<string name="settings_default_reminder">Promemoria per eventi standard</string>
|
||||||
<string name="settings_notifications_subtitle">Promemoria</string>
|
<string name="settings_notifications_subtitle">Promemoria evento</string>
|
||||||
<string name="shortcut_new_event_short">Nuovo evento</string>
|
<string name="shortcut_new_event_short">Nuovo evento</string>
|
||||||
<string name="event_edit_managed_hint">Gestito da “%1$s” - titolo, data e ripetizioni sono sincronizzate dai tuoi contatti. Promemoria e note sono modificabili dall\'utente.</string>
|
<string name="event_edit_managed_hint">Gestito da “%1$s” - titolo, data e ripetizioni sono sincronizzate dai tuoi contatti. Promemoria e note sono modificabili dall\'utente.</string>
|
||||||
<string name="settings_font_headings">Carattere del titolo</string>
|
<string name="settings_font_headings">Carattere del titolo</string>
|
||||||
@@ -423,13 +423,13 @@
|
|||||||
<string name="settings_font_choose_file">Scegli file…</string>
|
<string name="settings_font_choose_file">Scegli file…</string>
|
||||||
<string name="settings_font_custom_selected">Font personalizzato</string>
|
<string name="settings_font_custom_selected">Font personalizzato</string>
|
||||||
<string name="settings_font_import_failed">Impossibile leggere il file come font</string>
|
<string name="settings_font_import_failed">Impossibile leggere il file come font</string>
|
||||||
<string name="settings_section_views">Vista</string>
|
<string name="settings_section_views">Viste</string>
|
||||||
<string name="settings_quick_switch_header">Pulsante di cambio rapido</string>
|
<string name="settings_quick_switch_header">Pulsante di cambio rapido</string>
|
||||||
<string name="settings_quick_switch_hint">Scegli attraverso quali viste scorrere col pulsante in alto a destra, trascinale per riordinarle. Le viste disattivate rimangono raggiungibili dal menù di navigazione.</string>
|
<string name="settings_quick_switch_hint">Scegli attraverso quali viste scorrere col pulsante in alto a destra, trascinale per riordinarle. Le viste disattivate rimangono raggiungibili dal menù di navigazione.</string>
|
||||||
<string name="settings_drawer_order_header">Menù di navigazione</string>
|
<string name="settings_drawer_order_header">Menù di navigazione</string>
|
||||||
<string name="settings_drawer_order_hint">Trascina per riordinare le viste elencate nel menù di navigazione.</string>
|
<string name="settings_drawer_order_hint">Trascina per riordinare le viste elencate nel menù di navigazione.</string>
|
||||||
<string name="reorder_drag_handle">Trascina per riordinare</string>
|
<string name="reorder_drag_handle">Trascina per riordinare</string>
|
||||||
<string name="settings_views_subtitle">Vista di default, layout, ordine</string>
|
<string name="settings_views_subtitle">Ordine del pulsante di cambio rapido e del menù</string>
|
||||||
<string name="settings_special_dates_subtitle">Compleanni e anniversari dei contatti</string>
|
<string name="settings_special_dates_subtitle">Compleanni e anniversari dei contatti</string>
|
||||||
<string name="settings_section_special_dates">Date speciali dei contatti</string>
|
<string name="settings_section_special_dates">Date speciali dei contatti</string>
|
||||||
<string name="settings_special_dates_enable">Mostra date dei contatti</string>
|
<string name="settings_special_dates_enable">Mostra date dei contatti</string>
|
||||||
@@ -478,8 +478,8 @@
|
|||||||
<string name="reminder_day_yesterday">Ieri</string>
|
<string name="reminder_day_yesterday">Ieri</string>
|
||||||
<string name="agenda_no_more_today">Nessun altro evento per oggi</string>
|
<string name="agenda_no_more_today">Nessun altro evento per oggi</string>
|
||||||
<string name="back">Indietro</string>
|
<string name="back">Indietro</string>
|
||||||
<string name="settings_soften_colors">Armonizza i colori del calendario</string>
|
<string name="settings_soften_colors">Attenua i colori del calendario</string>
|
||||||
<string name="settings_soften_colors_summary">Mantieni i colori del calendario ma attenua la luminosità, così gli eventi rimangono leggibili e i colori sono armonizzati. Deseleziona per mostrare i colori originali.</string>
|
<string name="settings_soften_colors_summary">I colori dei calendari e degli eventi vengono attenuati per adattarsi al tema. Deseleziona per mostrare i colori naturali.</string>
|
||||||
<string name="settings_week_numbers">Numeri di settimana</string>
|
<string name="settings_week_numbers">Numeri di settimana</string>
|
||||||
<string name="settings_week_numbers_summary">Mostra i numeri di settimana nella vista Mese</string>
|
<string name="settings_week_numbers_summary">Mostra i numeri di settimana nella vista Mese</string>
|
||||||
<string name="settings_agenda_show_today">Mostra sempre Oggi</string>
|
<string name="settings_agenda_show_today">Mostra sempre Oggi</string>
|
||||||
@@ -499,151 +499,4 @@
|
|||||||
<item quantity="many">Importando %d eventi</item>
|
<item quantity="many">Importando %d eventi</item>
|
||||||
<item quantity="other">Importando %d eventi</item>
|
<item quantity="other">Importando %d eventi</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="event_move_action">Sposta…</string>
|
|
||||||
<string name="event_move_recurring_title">Sposta un evento ricorrente</string>
|
|
||||||
<string name="event_move_done">Spostato al %1$s</string>
|
|
||||||
<string name="event_move_undo">Annulla ripianificazione</string>
|
|
||||||
<string name="event_move_undone">Ripianificazione annullata</string>
|
|
||||||
<string name="event_move_failed">Impossibile ripianificare l\'evento</string>
|
|
||||||
<string name="event_move_write_denied">Calendula ha bisogno dei permessi di scrittura per ripianificare gli eventi</string>
|
|
||||||
<string name="event_move_gone">Questo evento è stato eliminato.</string>
|
|
||||||
<string name="event_move_blocked_series_end">Non è possibile ripianificare un evento oltre la data di fine della sua serie</string>
|
|
||||||
<string name="event_edit_timezone_device">Fuso orario del dispositivo</string>
|
|
||||||
<string name="event_edit_timezone_device_summary">Segue il fuso orario in cui ti trovi</string>
|
|
||||||
<string name="event_edit_timezone_search">Seleziona fuso orario</string>
|
|
||||||
<string name="event_edit_timezone_recent">Recenti</string>
|
|
||||||
<string name="event_edit_timezone_all">Tutti i fusi orari</string>
|
|
||||||
<string name="event_edit_timezone_none">Nessun fuso corrisponde a \"%1$s\"</string>
|
|
||||||
<string name="event_edit_timezone_local_time">%1$s nel tuo fuso orario</string>
|
|
||||||
<string name="event_edit_recurrence_incomplete">Inserisci un numero tra 1 e 999</string>
|
|
||||||
<string name="event_edit_recurrence_next">Prossimo evento: %1$s</string>
|
|
||||||
<string name="event_edit_recurrence_next_none">Questa regola non si ripete</string>
|
|
||||||
<string name="event_access_default_summary">In base all\'impostazione del calendario</string>
|
|
||||||
<string name="event_access_public_summary">Chiunque acceda può vedere i dettagli completi</string>
|
|
||||||
<string name="event_access_private_summary">Gli altri vedranno solo che sei impegnato</string>
|
|
||||||
<string name="event_access_confidential_summary">Confidenziale; dipende dalle impostazioni dell\'account</string>
|
|
||||||
<string name="duration_hours_minutes">%1$s %2$s</string>
|
|
||||||
<string name="duration_custom_max">Al più %1$s</string>
|
|
||||||
<string name="onboarding_step_counter">Step %1$d di %2$d</string>
|
|
||||||
<string name="onboarding_backup_title">I tuoi eventi rimangono qui</string>
|
|
||||||
<string name="onboarding_backup_body">Nulla viene sincronizzato, perdere i dati presenti su questo dispositivo significa perdere i tuoi calendari. Calendula può fare un backup per te.</string>
|
|
||||||
<string name="onboarding_backup_benefit_folder_title">Una cartella a scelta</string>
|
|
||||||
<string name="onboarding_backup_benefit_folder_body">I backup sono file .ics. Mettili al sicuro sul cloud o su una scheda SD.</string>
|
|
||||||
<string name="onboarding_backup_benefit_daily_title">Giornaliero, in automatico</string>
|
|
||||||
<string name="onboarding_backup_benefit_daily_body">Calendula esporta i tuoi calendari locali in background. Selezione la frequenza nelle Impostazioni.</string>
|
|
||||||
<string name="onboarding_backup_enable_button">Seleziona la cartella ed esegui il backup</string>
|
|
||||||
<string name="onboarding_backup_skip_button">Non ora</string>
|
|
||||||
<string name="onboarding_view_title">Cosa viene aperto per primo?</string>
|
|
||||||
<string name="onboarding_month_style_title">Seleziona la vista Mese</string>
|
|
||||||
<string name="onboarding_view_continue_button">Continua</string>
|
|
||||||
<string name="onboarding_back">Indietro</string>
|
|
||||||
<string name="onboarding_visibility_button">Capito</string>
|
|
||||||
<string name="onboarding_done_title">Tutto fatto</string>
|
|
||||||
<string name="onboarding_done_body">I tuoi calendari sono pronti. Tutto ciò che hai scelto può essere modificato più tardi nelle Impostazioni.</string>
|
|
||||||
<string name="onboarding_done_button">Apri i miei calendari</string>
|
|
||||||
<string name="agenda_span_starts">Inizia alle %1$s</string>
|
|
||||||
<string name="agenda_span_ends">Finisce alle %1$s</string>
|
|
||||||
<string name="today_jump_action">Vai ad oggi</string>
|
|
||||||
<plurals name="search_selected_count">
|
|
||||||
<item quantity="one">%d selezionato</item>
|
|
||||||
<item quantity="many">%d selezionati</item>
|
|
||||||
<item quantity="other">%d selezionati</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="search_in_descriptions">Trovato nelle descrizioni</string>
|
|
||||||
<string name="search_selection_close">Cancella selezione</string>
|
|
||||||
<string name="search_select_all">Seleziona tutti</string>
|
|
||||||
<string name="search_delete_selected">Elimina i selezionati</string>
|
|
||||||
<plurals name="search_delete_title">
|
|
||||||
<item quantity="one">Eliminare l\'evento?</item>
|
|
||||||
<item quantity="other">Eliminare l\'evento?</item>
|
|
||||||
</plurals>
|
|
||||||
<plurals name="search_delete_done">
|
|
||||||
<item quantity="one">Evento eliminato</item>
|
|
||||||
<item quantity="other">Evento eliminato</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="search_delete_partial">%1$d eliminati, %2$d no</string>
|
|
||||||
<string name="search_delete_denied_partial">%1$d eliminati, poi permessi di scrittura negati</string>
|
|
||||||
<string name="event_move_occurrence_only">Ripianificare l\'intera serie cambia i giorni in cui cade, quindi puoi ripianificare solo questo evento.</string>
|
|
||||||
<string name="settings_theme_hint">Tema chiaro o scuro. La modifica viene applicata immediatamente.</string>
|
|
||||||
<string name="settings_theme_system_summary">Al momento %1$s</string>
|
|
||||||
<string name="settings_font_specimen_heading">Giovedì, 14 Maggio</string>
|
|
||||||
<string name="settings_font_specimen_body">Team review alle 10:00, poi pranzo con Ugo al bar a Piazza di Spagna.</string>
|
|
||||||
<string name="settings_week_start_auto_summary">Al momento %1$s</string>
|
|
||||||
<string name="settings_today_toolbar">Pulsante Oggi nella toolbar</string>
|
|
||||||
<string name="settings_today_toolbar_summary">Mostra il pulsante \"vai alla data\" nella toolbar invece che come pulsante flottante</string>
|
|
||||||
<string name="settings_app_name">Nome app</string>
|
|
||||||
<string name="settings_app_name_summary">Mostra Calendula come \"Calendario\" nel launcher. Il nome cambia solo nel launcher; l\'icona potrebbe spostarsi dopo la modifica.</string>
|
|
||||||
<string name="settings_time_format_auto_summary">Da sistema: %1$s</string>
|
|
||||||
<string name="settings_timeline_scale">Altezza ore</string>
|
|
||||||
<string name="settings_timeline_scale_hint">Spazio occupato verticalmente da un\'ora nella vista Giorno e Settimana. Puoi anche modificare l\'altezza come preverisci pizzicando la timeline.</string>
|
|
||||||
<string name="timeline_scale_fit_day">Occupa la giornata intera</string>
|
|
||||||
<string name="timeline_scale_fit_day_summary">Tutte le 24 ore in una schermata, senza scrolling</string>
|
|
||||||
<string name="timeline_scale_compact">Compatto</string>
|
|
||||||
<string name="timeline_scale_compact_summary">Più ore per schermata, blocchi più piccoli</string>
|
|
||||||
<string name="timeline_scale_regular">Regolare</string>
|
|
||||||
<string name="timeline_scale_regular_summary">La spaziatura standard</string>
|
|
||||||
<string name="timeline_scale_comfortable">Comodo</string>
|
|
||||||
<string name="timeline_scale_comfortable_summary">Blocchi più ampi, più scrolling</string>
|
|
||||||
<string name="timeline_scale_custom">Personalizza</string>
|
|
||||||
<string name="timeline_scale_custom_summary">L\'altezza a cui hai portato la timeline</string>
|
|
||||||
<string name="settings_drag_to_reschedule">Trascina per ripianificare</string>
|
|
||||||
<string name="settings_drag_to_reschedule_summary">Ripianifica un evento trascinandolo in un altro giorno o ora</string>
|
|
||||||
<string name="settings_past_events_sample_morning">Team review</string>
|
|
||||||
<string name="settings_past_events_sample_midday">Pranzo con Ugo</string>
|
|
||||||
<string name="settings_past_events_sample_evening">Allenamento di scacchi</string>
|
|
||||||
<string name="settings_widget_size">Dimensioni del widget Agenda</string>
|
|
||||||
<string name="settings_widget_size_hint">Dimensioni del testo del widget agenda. Il widget mensile non ha impostazioni, si adatta da solo alla dimensione data.</string>
|
|
||||||
<string name="settings_widget_size_small">Piccolo</string>
|
|
||||||
<string name="settings_widget_size_medium">Medio</string>
|
|
||||||
<string name="settings_widget_size_large">Grande</string>
|
|
||||||
<string name="settings_widget_size_extra_large">Molto grande</string>
|
|
||||||
<string name="settings_widget_size_summary">Carattere %1$d%%</string>
|
|
||||||
<string name="settings_month_header">Vista Mese</string>
|
|
||||||
<string name="settings_month_view_style">Stile vista Mese</string>
|
|
||||||
<string name="month_style_paged">Pagine</string>
|
|
||||||
<string name="month_style_paged_summary">Viene mostrato un singolo mese. Scorri a destra o a sinistra per cambiare mese.</string>
|
|
||||||
<string name="month_style_continuous">Scorrimento</string>
|
|
||||||
<string name="settings_past_events_hint">Ciò che fa l\'Agenda con eventi conclusi.</string>
|
|
||||||
<string name="settings_dynamic_color_summary">Deduci il colore dell\'app dallo sfondo.</string>
|
|
||||||
<string name="settings_about_privacy">Politica per la Privacy</string>
|
|
||||||
<string name="calendars_visibility_hint">Deseleziona un calendario per non mostrarlo sul dispositivo: scompariranno gli eventi e non riceverai più promemoria. Questa impostazione agisce anche su altre app calendario. Nulla viene eliminato e il calendario non scompare da nessun altro dispositivo. Puoi riselezionarlo in ogni momento.</string>
|
|
||||||
<string name="calendars_visibility_a11y">Mostra \"%1$s\"</string>
|
|
||||||
<string name="calendars_visibility_notice_title">Alcuni calendari non sono attivi</string>
|
|
||||||
<string name="calendars_visibility_notice_message">Calendula mostra solo i calendari che sono attivi sul dispositivo, e alcuni non lo sono. Per vedere gli eventi di quest\'ultimi, attivali dalle Impostazioni.</string>
|
|
||||||
<string name="calendar_picker_missing_title">Manca un calendario?</string>
|
|
||||||
<string name="calendar_picker_missing_summary">Può essere disattivato, in sola lettura o compilato in base ai tuoi contatti. Gestisci qui i tuoi calendari.</string>
|
|
||||||
<string name="calendars_state_read_only">Sola lettura</string>
|
|
||||||
<string name="calendars_state_not_synced">Non sincronizzato su questo dispositivo</string>
|
|
||||||
<string name="calendars_state_managed">Compilato dai tuoi contatti</string>
|
|
||||||
<string name="calendars_managed_delete_locked">Questo calendario è compilato in base ai tuoi contatti, quindi Calendula lo creerà di nuovo alla prossima sincronizzazione. Per eliminarlo disattiva l\'opzione \"Date Speciali\" nelle Impostazioni.</string>
|
|
||||||
<string name="calendars_account_from_source">%1$s (%2$s)</string>
|
|
||||||
<string name="month_style_continuous_summary">Ogni mese è sotto la sua testata, con un piccolo spazio a separarlo dal mese successivo.</string>
|
|
||||||
<string name="month_style_dense">Settimane continue</string>
|
|
||||||
<string name="month_style_dense_summary">Le settimane scorrono senza interruzione, ogni mese succede a quello precedente.</string>
|
|
||||||
<string name="month_style_split">Diviso</string>
|
|
||||||
<string name="month_style_split_summary">Una griglia compatta indica i giorni con eventi. Gli eventi del giorno selezionato sono elencati sotto.</string>
|
|
||||||
<string name="month_split_no_events">Nulla in programma</string>
|
|
||||||
<string name="month_split_expand">Mostra il mese completo</string>
|
|
||||||
<string name="month_split_collapse">Mostra gli eventi del giorno</string>
|
|
||||||
<string name="settings_event_duration">Durata standard</string>
|
|
||||||
<string name="settings_event_duration_hint">Quanto durano gli eventi se non modifichi l\'orario di fine. Non ha effetto sugli eventi giornalieri.</string>
|
|
||||||
<string name="settings_calendar_durations_title">Durata per calendario</string>
|
|
||||||
<string name="settings_calendar_durations_hint">Assegna a ciascun calendario la sua durata standard (e.g. 8 ore per il calendario dei turni lavorativi).</string>
|
|
||||||
<string name="settings_calendar_duration_inherits">Default (%1$s)</string>
|
|
||||||
<string name="settings_calendar_duration_use_default">Usa la durata standard (%1$s)</string>
|
|
||||||
<string name="settings_allday_reminder_fires_at">Promemoria alle %1$s</string>
|
|
||||||
<string name="settings_group_look">Aspetto e Comportamento</string>
|
|
||||||
<string name="settings_group_data">Dati</string>
|
|
||||||
<string name="settings_group_app">App</string>
|
|
||||||
<string name="settings_group_about">A proposito</string>
|
|
||||||
<string name="settings_widgets_subtitle">Widget Agenda e riquadro Impostazioni rapide</string>
|
|
||||||
<string name="settings_backup_subtitle">Export, Import, backup automatici</string>
|
|
||||||
<string name="settings_section_widgets">Widget & Riquardi</string>
|
|
||||||
<string name="settings_widgets_hint">Impostazioni per i widget e il riquardo delle Impostazioni rapide. Aggiungi i widget tenendo premuto sulla home.</string>
|
|
||||||
<string name="settings_section_backup">Backup e ripristino</string>
|
|
||||||
<string name="settings_views_all_header">Tutte le viste</string>
|
|
||||||
<string name="settings_week_day_header">Giorno e settimana</string>
|
|
||||||
<string name="settings_default_view_hint">La vista di default all\'apertura dell\'app.</string>
|
|
||||||
<string name="settings_week_start_hint">Il giorno in cui inizia ogni settimana, in tutta l\'app e i suoi widget.</string>
|
|
||||||
<string name="settings_time_format_hint">Formato degli orari in tutta l\'app. Di default viene seguito il formato di sistema.</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,205 +6,4 @@
|
|||||||
<item>Days</item>
|
<item>Days</item>
|
||||||
<item>Weeks</item>
|
<item>Weeks</item>
|
||||||
</string-array>
|
</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>
|
</resources>
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
<string name="state_failure_no_calendars_action">Перейти в системные настройки календаря</string>
|
<string name="state_failure_no_calendars_action">Перейти в системные настройки календаря</string>
|
||||||
<string name="state_failure_provider">Не удалось просмотреть календарь.</string>
|
<string name="state_failure_provider">Не удалось просмотреть календарь.</string>
|
||||||
<string name="permission_rationale_title">Следите за всеми своими событиями. С красотой</string>
|
<string name="permission_rationale_title">Следите за всеми своими событиями. С красотой</string>
|
||||||
<string name="permission_rationale_body">Calendula требуется доступ к вашему календарю, чтобы показывать и управлять вашими событиями. Это всё, что приложение требует с самого начала – и никакая информация не покидает ваше устройство.</string>
|
<string name="permission_rationale_body">Calendula требуется доступ к вашему календарю, чтобы показывать и управлять вашими событиями. Это всё, что приложение требует с самого начала – и никакая информация не покидает ваш девайс.</string>
|
||||||
<string name="permission_request_button">Дать доступ к календарю</string>
|
<string name="permission_request_button">Дать доступ к календарю</string>
|
||||||
<string name="permission_denied_title">В доступе к календарю отказано</string>
|
<string name="permission_denied_title">В доступе к календарю отказано</string>
|
||||||
<string name="permission_denied_body">Calendula не может показать события без доступа к календарю. Предоставьте его снова в настройках телефона.</string>
|
<string name="permission_denied_body">Calendula не может показать события без доступа к календарю. Предоставьте его снова в настройках телефона.</string>
|
||||||
<string name="permission_open_settings_button">Открыть настройки телефона</string>
|
<string name="permission_open_settings_button">Открыть настройки телефона</string>
|
||||||
<string name="permission_retry_button">Попытайтесь снова</string>
|
<string name="permission_retry_button">Попытайтесь снова</string>
|
||||||
<string name="permission_benefit_private_title">Приватность по умолчанию</string>
|
<string name="permission_benefit_private_title">Остаётся на вашем устройстве</string>
|
||||||
<string name="permission_benefit_private_body">Нет доступа к интернету, нету телеметрии — ничто не покидает ваше устройство.</string>
|
<string name="permission_benefit_private_body">Ваши календари просматриваются локально и информация никогда не покидает устройство.</string>
|
||||||
<string name="permission_benefit_sync_title">Все ваши календари. Вместе</string>
|
<string name="permission_benefit_sync_title">Все ваши календари. Вместе</string>
|
||||||
<string name="permission_benefit_sync_body">Google, CalDAV, локальный календарь – всё синхронизированное с устройством просто подключается.</string>
|
<string name="permission_benefit_sync_body">Google, CalDAV, локальный календарь – всё синхронизированное с устройством просто подключается.</string>
|
||||||
<string name="permission_benefit_privacy_title">Никакого отслеживания. Никогда</string>
|
<string name="permission_benefit_privacy_title">Никакого отслеживания. Никогда</string>
|
||||||
@@ -62,75 +62,4 @@
|
|||||||
<string name="event_edit_close">Закрыть</string>
|
<string name="event_edit_close">Закрыть</string>
|
||||||
<string name="event_edit_save">Сохранить</string>
|
<string name="event_edit_save">Сохранить</string>
|
||||||
<string name="event_edit_title_hint">Добавить имя</string>
|
<string name="event_edit_title_hint">Добавить имя</string>
|
||||||
<string name="event_move_action">Перенести…</string>
|
|
||||||
<string name="event_move_recurring_title">Перенести повторяющиеся события</string>
|
|
||||||
<string name="event_move_done">Перенесено на %1$s</string>
|
|
||||||
<string name="event_move_undo">Отменить</string>
|
|
||||||
<string name="event_move_occurrence_only">Перемещение всей серии изменит дни на которые она попадёт, поэтому можно переместить только это событие.</string>
|
|
||||||
<string name="event_move_undone">Перемещение отменено</string>
|
|
||||||
<string name="event_move_failed">Не удалось переместить событие</string>
|
|
||||||
<string name="event_move_write_denied">Приложению Calendula требуется доступ на запись, чтобы перемещать события</string>
|
|
||||||
<string name="event_move_gone">Это событие больше не существует</string>
|
|
||||||
<string name="event_move_blocked_series_end">Нельзя переместить событие дальше за пределы его серии</string>
|
|
||||||
<string name="event_edit_managed_hint">Управляется «%1$s» — название, дата и повторение синхронизируются с ваших контактов. Напоминания, местоположения и заметки могут быть редактированы вами.</string>
|
|
||||||
<string name="event_edit_starts">Начинается</string>
|
|
||||||
<string name="event_edit_ends">Заканчивается</string>
|
|
||||||
<string name="event_edit_error_end_before_start">Заканчивается раньше, чем начинается</string>
|
|
||||||
<string name="event_edit_error_no_calendar">Нет календарей с правами на запись</string>
|
|
||||||
<string name="event_edit_save_failed">Не удалось сохранить событие</string>
|
|
||||||
<string name="event_edit_write_denied">Приложению Calendula требуется доступ на запись, чтобы создавать события</string>
|
|
||||||
<string name="event_edit_more_fields">Больше полей</string>
|
|
||||||
<string name="event_edit_add">Добавить</string>
|
|
||||||
<string name="event_edit_add_reminder">Добавить напоминание</string>
|
|
||||||
<string name="event_edit_remove_reminder">Удалить напоминание</string>
|
|
||||||
<string name="event_edit_attendees">Гости</string>
|
|
||||||
<string name="event_edit_add_guest">Добавить гостя</string>
|
|
||||||
<string name="event_edit_add_guest_hint">Добавить гостя по почте…</string>
|
|
||||||
<string name="event_edit_add_guest_from_contacts">Добавить из контактов</string>
|
|
||||||
<string name="event_edit_location_from_contacts">Выбрать адрес из контактов</string>
|
|
||||||
<string name="event_edit_remove_guest">Удалить гостя</string>
|
|
||||||
<string name="event_edit_attendee_required">Обязательный</string>
|
|
||||||
<string name="event_edit_attendee_optional">Необязательный</string>
|
|
||||||
<string name="event_edit_attendees_note_synced">Calendula не отправляет приглашения. Ваша учётная запись календаря может отправить письма гостям при синхронизации.</string>
|
|
||||||
<string name="event_edit_attendees_note_local">Хранится на этом устройстве. Никто не получит уведомление.</string>
|
|
||||||
<string name="event_edit_reminder_custom">Другое</string>
|
|
||||||
<string name="reminder_unit_minutes">минуты</string>
|
|
||||||
<string name="reminder_unit_hours">часы</string>
|
|
||||||
<string name="reminder_unit_days">дни</string>
|
|
||||||
<string name="reminder_unit_weeks">недели</string>
|
|
||||||
<string name="event_edit_availability">Занятость</string>
|
|
||||||
<string name="event_edit_visibility">Видимость</string>
|
|
||||||
<string name="event_edit_timezone_device">Часовой пояс устройства</string>
|
|
||||||
<string name="event_edit_timezone_device_summary">Следует за вами, где бы вы ни были</string>
|
|
||||||
<string name="event_edit_timezone_search">Поиск часовых поясов</string>
|
|
||||||
<string name="event_edit_timezone_recent">Недавние</string>
|
|
||||||
<string name="event_edit_timezone_all">Все часовые пояса</string>
|
|
||||||
<string name="event_edit_timezone_none">Часовые пояса, соответсвующие «%1$s», не найдены</string>
|
|
||||||
<string name="event_edit_timezone_local_time">%1$s по вашему времени</string>
|
|
||||||
<string name="event_edit_color">Цвет</string>
|
|
||||||
<string name="event_edit_color_default">Цвет календаря</string>
|
|
||||||
<string name="event_edit_color_custom">Свой цвет</string>
|
|
||||||
<string name="event_edit_color_reset">Сбросить</string>
|
|
||||||
<string name="event_edit_color_unsupported">Недоступно для этого календаря</string>
|
|
||||||
<string name="event_edit_color_unsupported_hint">Этот календарь не публикует набор цветов. Вы можете разрешить пользовательские цвета для таких календарей в Настройках.</string>
|
|
||||||
<string name="event_edit_color_sync_warning">Этот календарь может потерять или перезаписать цвет при следующей синхронизации.</string>
|
|
||||||
<string name="event_edit_conflict_title">Событие изменено в другом месте</string>
|
|
||||||
<string name="event_edit_conflict_body">Пока вы редактировали, это событие было изменено — синхронизацией или другим приложением. Что вы хотите сделать с вашими изменениями?</string>
|
|
||||||
<string name="event_edit_conflict_overwrite">Сохранить мои изменения</string>
|
|
||||||
<string name="event_edit_conflict_overwrite_hint">Только изменённые вами поля, перезаписывают внешние изменения</string>
|
|
||||||
<string name="event_edit_conflict_discard">Отменить мои изменения</string>
|
|
||||||
<string name="event_edit_conflict_discard_hint">Событие остаётся таким, как сейчас</string>
|
|
||||||
<string name="event_edit_gone_title">Событие удалено</string>
|
|
||||||
<string name="event_edit_gone_body">Это событие было удалено, пока вы его редактировали, например на другом устройстве. Ваши изменения больше не могут быть сохранены.</string>
|
|
||||||
<string name="import_reminder_prompt_title">Применить ваше стандартное напоминание?</string>
|
|
||||||
<string name="import_reminder_prompt_body_none">Это событие было импортировано без каких-либо напоминаний.</string>
|
|
||||||
<plurals name="import_reminder_prompt_body_existing">
|
|
||||||
<item quantity="one">Это событие было импортировано с %1$d напоминанием.</item>
|
|
||||||
<item quantity="few">Это событие было импортировано с %1$d напоминаниями.</item>
|
|
||||||
<item quantity="many">Это событие было импортировано с %1$d напоминаниями.</item>
|
|
||||||
<item quantity="other">Это событие было импортировано с %1$d напоминаниями.</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="import_reminder_prompt_apply">Применить стандартное</string>
|
|
||||||
<string name="import_reminder_prompt_keep">Оставить как есть</string>
|
|
||||||
<string name="event_edit_recurrence_none">Не повторяется</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -439,8 +439,6 @@
|
|||||||
<string name="timeline_scale_custom_summary">The height you pinched the timeline to</string>
|
<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">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_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">Past events</string>
|
||||||
<string name="settings_past_events_show">Show</string>
|
<string name="settings_past_events_show">Show</string>
|
||||||
<string name="settings_past_events_dim">Dim</string>
|
<string name="settings_past_events_dim">Dim</string>
|
||||||
|
|||||||
@@ -15,6 +15,5 @@
|
|||||||
<locale android:name="fr" />
|
<locale android:name="fr" />
|
||||||
<locale android:name="it" />
|
<locale android:name="it" />
|
||||||
<locale android:name="pl" />
|
<locale android:name="pl" />
|
||||||
<locale android:name="pt-BR" />
|
|
||||||
<locale android:name="zh-CN" />
|
<locale android:name="zh-CN" />
|
||||||
</locale-config>
|
</locale-config>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.jeanlucmakiola.calendula.data.calendar
|
package de.jeanlucmakiola.calendula.data.calendar
|
||||||
|
|
||||||
import android.provider.CalendarContract
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
@@ -18,7 +17,6 @@ class InstanceMapperTest {
|
|||||||
eventColor: Any? = null,
|
eventColor: Any? = null,
|
||||||
calendarColor: Int = 0xFFAABBCC.toInt(),
|
calendarColor: Int = 0xFFAABBCC.toInt(),
|
||||||
location: String? = null,
|
location: String? = null,
|
||||||
selfAttendeeStatus: Int = CalendarContract.Attendees.ATTENDEE_STATUS_NONE,
|
|
||||||
): MapColumnReader = MapColumnReader(
|
): MapColumnReader = MapColumnReader(
|
||||||
InstanceProjection.IDX_INSTANCE_ID to instanceId,
|
InstanceProjection.IDX_INSTANCE_ID to instanceId,
|
||||||
InstanceProjection.IDX_EVENT_ID to eventId,
|
InstanceProjection.IDX_EVENT_ID to eventId,
|
||||||
@@ -30,7 +28,6 @@ class InstanceMapperTest {
|
|||||||
InstanceProjection.IDX_EVENT_COLOR to eventColor,
|
InstanceProjection.IDX_EVENT_COLOR to eventColor,
|
||||||
InstanceProjection.IDX_CALENDAR_COLOR to calendarColor,
|
InstanceProjection.IDX_CALENDAR_COLOR to calendarColor,
|
||||||
InstanceProjection.IDX_LOCATION to location,
|
InstanceProjection.IDX_LOCATION to location,
|
||||||
InstanceProjection.IDX_SELF_ATTENDEE_STATUS to selfAttendeeStatus,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -93,20 +90,4 @@ class InstanceMapperTest {
|
|||||||
val inst = reader(location = "Berlin").toEventInstance()
|
val inst = reader(location = "Berlin").toEventInstance()
|
||||||
assertThat(inst!!.location).isEqualTo("Berlin")
|
assertThat(inst!!.location).isEqualTo("Berlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a declined invitation is marked, any other answer is not`() {
|
|
||||||
assertThat(reader().toEventInstance()!!.isDeclined).isFalse()
|
|
||||||
assertThat(
|
|
||||||
reader(selfAttendeeStatus = CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED)
|
|
||||||
.toEventInstance()!!.isDeclined,
|
|
||||||
).isTrue()
|
|
||||||
listOf(
|
|
||||||
CalendarContract.Attendees.ATTENDEE_STATUS_ACCEPTED,
|
|
||||||
CalendarContract.Attendees.ATTENDEE_STATUS_TENTATIVE,
|
|
||||||
CalendarContract.Attendees.ATTENDEE_STATUS_INVITED,
|
|
||||||
).forEach { status ->
|
|
||||||
assertThat(reader(selfAttendeeStatus = status).toEventInstance()!!.isDeclined).isFalse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.detail
|
|
||||||
|
|
||||||
import android.content.ContextWrapper
|
|
||||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import de.jeanlucmakiola.calendula.data.calendar.CalendarRepositoryImpl
|
|
||||||
import de.jeanlucmakiola.calendula.data.calendar.FakeCalendarDataSource
|
|
||||||
import de.jeanlucmakiola.calendula.data.ics.IcsExporter
|
|
||||||
import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs
|
|
||||||
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
|
|
||||||
import de.jeanlucmakiola.calendula.domain.CalendarSource
|
|
||||||
import de.jeanlucmakiola.calendula.domain.EventDetail
|
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.advanceUntilIdle
|
|
||||||
import kotlinx.coroutines.test.resetMain
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import kotlinx.coroutines.test.setMain
|
|
||||||
import org.junit.jupiter.api.AfterEach
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.io.TempDir
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.time.Instant
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-opening an occurrence must re-read it (#196): the view model outlives the
|
|
||||||
* sheet, so an edit that changed no time would otherwise show the pre-save row.
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
class EventDetailViewModelTest {
|
|
||||||
|
|
||||||
private val dispatcher = UnconfinedTestDispatcher()
|
|
||||||
|
|
||||||
@BeforeEach fun setUp() = Dispatchers.setMain(dispatcher)
|
|
||||||
@AfterEach fun tearDown() = Dispatchers.resetMain()
|
|
||||||
|
|
||||||
private val beginMillis = 1_781_164_800_000L
|
|
||||||
private val endMillis = beginMillis + 3_600_000L
|
|
||||||
|
|
||||||
private fun detail(description: String?) = EventDetail(
|
|
||||||
instance = EventInstance(
|
|
||||||
instanceId = 42L, eventId = 42L, calendarId = 1L, title = "Standup",
|
|
||||||
start = Instant.fromEpochMilliseconds(beginMillis),
|
|
||||||
end = Instant.fromEpochMilliseconds(endMillis),
|
|
||||||
isAllDay = false, color = 0xFF000000.toInt(), location = null,
|
|
||||||
),
|
|
||||||
description = description, organizer = null, attendees = emptyList(), rrule = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun viewModel(tempDir: Path, fake: FakeCalendarDataSource): EventDetailViewModel {
|
|
||||||
val prefs = CalendarPrefs(
|
|
||||||
PreferenceDataStoreFactory.create(
|
|
||||||
scope = CoroutineScope(dispatcher),
|
|
||||||
produceFile = { tempDir.resolve("detail_prefs.preferences_pb").toFile() },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
val settings = SettingsPrefs(
|
|
||||||
PreferenceDataStoreFactory.create(
|
|
||||||
scope = CoroutineScope(dispatcher),
|
|
||||||
produceFile = { tempDir.resolve("detail_settings.preferences_pb").toFile() },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
val repo = CalendarRepositoryImpl(fake, prefs, settings, dispatcher as CoroutineDispatcher)
|
|
||||||
// Only `shareUri()` touches the exporter, and nothing here shares.
|
|
||||||
return EventDetailViewModel(repo, IcsExporter(ContextWrapper(null)), dispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `re-opening the same occurrence re-reads it`(@TempDir tempDir: Path) = runTest(dispatcher) {
|
|
||||||
var stored: String? = null
|
|
||||||
val fake = FakeCalendarDataSource().apply {
|
|
||||||
calendarsResult = listOf(
|
|
||||||
CalendarSource(
|
|
||||||
id = 1L, displayName = "Cal", accountName = "acc@local", accountType = "LOCAL",
|
|
||||||
color = 0xFF112233.toInt(), isVisibleInSystem = true, canModifyContents = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
eventDetailResult = { detail(stored) }
|
|
||||||
}
|
|
||||||
val vm = viewModel(tempDir, fake)
|
|
||||||
val collector = launch(Job()) { vm.state.collect {} }
|
|
||||||
|
|
||||||
vm.open(42L, beginMillis, endMillis)
|
|
||||||
advanceUntilIdle()
|
|
||||||
assertThat((vm.state.value as EventDetailUiState.Success).detail.description).isNull()
|
|
||||||
|
|
||||||
// The edit screen saved a description; the tapped occurrence is unchanged.
|
|
||||||
stored = "Bring the roadmap"
|
|
||||||
vm.open(42L, beginMillis, endMillis)
|
|
||||||
advanceUntilIdle()
|
|
||||||
assertThat((vm.state.value as EventDetailUiState.Success).detail.description)
|
|
||||||
.isEqualTo("Bring the roadmap")
|
|
||||||
|
|
||||||
collector.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.month
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
|
||||||
import kotlinx.datetime.DateTimeUnit
|
|
||||||
import kotlinx.datetime.DayOfWeek
|
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.Month
|
|
||||||
import kotlinx.datetime.TimeZone
|
|
||||||
import kotlinx.datetime.YearMonth
|
|
||||||
import kotlinx.datetime.atTime
|
|
||||||
import kotlinx.datetime.plus
|
|
||||||
import kotlinx.datetime.toInstant
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Which chip a tap in a month cell lands on (#187) — the geometry the tap layer
|
|
||||||
* uses to tell "open this event" from "open this day", given that the chips take
|
|
||||||
* no pointer input of their own.
|
|
||||||
*/
|
|
||||||
class ChipAtCellYTest {
|
|
||||||
|
|
||||||
private val zone = TimeZone.UTC
|
|
||||||
private val jul26 = YearMonth(2026, Month.JULY)
|
|
||||||
|
|
||||||
/** Band starts 40px down the cell; each lane is 20px tall. */
|
|
||||||
private val bandTop = 40f
|
|
||||||
private val laneHeight = 20f
|
|
||||||
|
|
||||||
/** July 2026 starts on a Wednesday, so this row — Jul 6–12 — sits wholly inside it. */
|
|
||||||
private fun rowOfJuly6(events: List<EventInstance>) =
|
|
||||||
layoutMonthWeeks(jul26, DayOfWeek.MONDAY, events, zone)[1]
|
|
||||||
|
|
||||||
private fun allDay(from: LocalDate, toInclusive: LocalDate, id: Long) = EventInstance(
|
|
||||||
instanceId = id,
|
|
||||||
eventId = id,
|
|
||||||
calendarId = 1L,
|
|
||||||
title = "A$id",
|
|
||||||
start = from.atTime(0, 0).toInstant(zone),
|
|
||||||
end = toInclusive.plus(1, DateTimeUnit.DAY).atTime(0, 0).toInstant(zone),
|
|
||||||
isAllDay = true,
|
|
||||||
color = 0xFF2196F3.toInt(),
|
|
||||||
location = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun timed(date: LocalDate, hour: Int, id: Long) = EventInstance(
|
|
||||||
instanceId = id,
|
|
||||||
eventId = id,
|
|
||||||
calendarId = 1L,
|
|
||||||
title = "T$id",
|
|
||||||
start = date.atTime(hour, 0).toInstant(zone),
|
|
||||||
end = date.atTime(hour + 1, 0).toInstant(zone),
|
|
||||||
isAllDay = false,
|
|
||||||
color = 0xFFF44336.toInt(),
|
|
||||||
location = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun MonthWeek.chipAt(col: Int, cellY: Float) =
|
|
||||||
chipAtCellY(col = col, cellY = cellY, bandTopInCell = bandTop, rowHeightPx = laneHeight)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a tap on a lane resolves to the chip seated there`() {
|
|
||||||
val bar = allDay(LocalDate(2026, 7, 7), LocalDate(2026, 7, 9), id = 1L)
|
|
||||||
val meeting = timed(LocalDate(2026, 7, 7), hour = 9, id = 2L)
|
|
||||||
val week = rowOfJuly6(listOf(bar, meeting))
|
|
||||||
|
|
||||||
// Jul 7 is column 1 of a Monday-anchored row starting Jul 6.
|
|
||||||
assertThat(week.chipAt(col = 1, cellY = bandTop + 5f)?.eventId).isEqualTo(1L)
|
|
||||||
assertThat(week.chipAt(col = 1, cellY = bandTop + laneHeight + 5f)?.eventId).isEqualTo(2L)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a multi-day bar answers on every column it covers`() {
|
|
||||||
val bar = allDay(LocalDate(2026, 7, 7), LocalDate(2026, 7, 9), id = 1L)
|
|
||||||
val week = rowOfJuly6(listOf(bar))
|
|
||||||
|
|
||||||
(1..3).forEach { col ->
|
|
||||||
assertThat(week.chipAt(col = col, cellY = bandTop + 5f)?.eventId).isEqualTo(1L)
|
|
||||||
}
|
|
||||||
// Jul 10 is past the bar's last day.
|
|
||||||
assertThat(week.chipAt(col = 4, cellY = bandTop + 5f)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a tap above the band is the day number, not a chip`() {
|
|
||||||
val week = rowOfJuly6(listOf(timed(LocalDate(2026, 7, 7), hour = 9, id = 2L)))
|
|
||||||
|
|
||||||
assertThat(week.chipAt(col = 1, cellY = bandTop - 1f)).isNull()
|
|
||||||
assertThat(week.chipAt(col = 1, cellY = 0f)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a tap on an empty lane of a day that has chips falls through to the day`() {
|
|
||||||
val week = rowOfJuly6(listOf(timed(LocalDate(2026, 7, 7), hour = 9, id = 2L)))
|
|
||||||
|
|
||||||
assertThat(week.chipAt(col = 1, cellY = bandTop + laneHeight * 2 + 5f)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a tap on the overflow row opens the day rather than a hidden event`() {
|
|
||||||
val events = (1..MAX_EVENT_ROWS + 2).map {
|
|
||||||
timed(LocalDate(2026, 7, 7), hour = it, id = it.toLong())
|
|
||||||
}
|
|
||||||
val week = rowOfJuly6(events)
|
|
||||||
|
|
||||||
// The dots sit one lane below the last one the row draws.
|
|
||||||
val overflowY = bandTop + laneHeight * MAX_EVENT_ROWS + 2f
|
|
||||||
assertThat(week.chipAt(col = 1, cellY = overflowY)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `unmeasured geometry resolves to no chip`() {
|
|
||||||
val week = rowOfJuly6(listOf(timed(LocalDate(2026, 7, 7), hour = 9, id = 2L)))
|
|
||||||
|
|
||||||
assertThat(
|
|
||||||
week.chipAtCellY(col = 1, cellY = 45f, bandTopInCell = null, rowHeightPx = laneHeight),
|
|
||||||
).isNull()
|
|
||||||
assertThat(
|
|
||||||
week.chipAtCellY(col = 1, cellY = 45f, bandTopInCell = bandTop, rowHeightPx = 0f),
|
|
||||||
).isNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
جديد
|
|
||||||
• صار بالإمكان إيقاف السحب لتغيير الموعد: الإعدادات ← العروض ← «السحب لتغيير الموعد». يبقى مفعّلاً افتراضياً؛ وعند إيقافه لا يمكن سحب الأحداث في عرض الشهر أو الأسبوع أو اليوم.
|
|
||||||
• صار Calendula يتحدّث البرتغالية البرازيلية بفضل ترجمة من المجتمع. اخترها من الإعدادات ← اللغة.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Новое
|
|
||||||
• Перенос событий перетаскиванием теперь можно отключить: Настройки → Виды → «Перетаскивание для переноса». По умолчанию включён; при отключении события больше нельзя подхватить в видах месяца, недели и дня.
|
|
||||||
• Calendula теперь говорит на бразильском португальском — благодаря переводу сообщества. Выбрать можно в Настройки → Язык.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
新增
|
|
||||||
• 现在可以关闭拖动改期:设置 → 视图 → “拖动改期”。默认保持开启;关闭后,月、周、日视图中的事件将无法再被拖起。
|
|
||||||
• Calendula 现已支持巴西葡萄牙语,感谢社区翻译。可在设置 → 语言中选择。
|
|
||||||
Submodule floret-kit updated: ad440e8cac...05c79f2afc
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.2.1"
|
agp = "9.2.1"
|
||||||
kotlin = "2.3.21"
|
kotlin = "2.3.21"
|
||||||
ksp = "2.3.11"
|
ksp = "2.3.10"
|
||||||
hilt = "2.60.1"
|
hilt = "2.59.2"
|
||||||
coreKtx = "1.19.0"
|
coreKtx = "1.19.0"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
lifecycleRuntime = "2.10.0"
|
lifecycleRuntime = "2.10.0"
|
||||||
@@ -11,7 +11,7 @@ composeBom = "2026.06.01"
|
|||||||
# Material 3 Expressive APIs currently live only in the 1.5 alpha line.
|
# Material 3 Expressive APIs currently live only in the 1.5 alpha line.
|
||||||
# Pin explicitly to override the BOM (which ships stable 1.4.0).
|
# Pin explicitly to override the BOM (which ships stable 1.4.0).
|
||||||
# Re-evaluate when 1.5.0 stable lands.
|
# Re-evaluate when 1.5.0 stable lands.
|
||||||
material3 = "1.5.0-alpha25"
|
material3 = "1.5.0-alpha24"
|
||||||
datastore = "1.2.1"
|
datastore = "1.2.1"
|
||||||
junit = "6.1.2"
|
junit = "6.1.2"
|
||||||
junitPlatform = "6.1.2"
|
junitPlatform = "6.1.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user