Compare commits
7 Commits
8945b877b2
...
v2.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 273cfce969 | |||
| b6a45b7264 | |||
| c7ef52e426 | |||
| b1151b6516 | |||
| 1664625bc5 | |||
| e8b87446f7 | |||
| dc478516cd |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.11.1] — 2026-06-28
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Calendula can now be set as your default calendar app. It registers the
|
||||||
|
calendar-app intent filters the system uses, so it appears in the chooser when
|
||||||
|
you tap a date in a launcher or clock — and opening one takes you straight to
|
||||||
|
that day. Android has no way for an app to make itself the default, so you pick
|
||||||
|
it once from the system picker. Thanks to @abrossimow for the report ([#9]).
|
||||||
|
|
||||||
## [2.11.0] — 2026-06-27
|
## [2.11.0] — 2026-06-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -30,10 +39,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
top naming the exact dates in view, with a button to switch the range just for
|
top naming the exact dates in view, with a button to switch the range just for
|
||||||
the current session (it resets when you reopen the app); the bar can be turned
|
the current session (it resets when you reopen the app); the bar can be turned
|
||||||
off. Thanks to @zmaherdev for the suggestion ([#4]).
|
off. Thanks to @zmaherdev for the suggestion ([#4]).
|
||||||
|
- Automatic backup of local calendars. A new **Automatic backup** option
|
||||||
|
(Settings → Calendars → Backup) periodically exports your local calendars to an
|
||||||
|
`.ics` file in a folder you choose, on an interval you set (from 30 minutes up).
|
||||||
|
Useful for file-based syncing such as Syncthing, or simply as a safety net — it
|
||||||
|
is a one-way export and never touches your synced accounts. Thanks to @shield
|
||||||
|
for the discussion that inspired it ([#7], [#8]).
|
||||||
|
- Field icons in the event-form settings. Each optional-field toggle (Settings →
|
||||||
|
Event form) now shows the same icon the field uses in the new-event form, so
|
||||||
|
the list is easier to scan.
|
||||||
|
- Help translate Calendula. A **Help translate** link at the top of **Settings →
|
||||||
|
App language** opens the project's Weblate, where you can add or improve a
|
||||||
|
language in your browser — no coding needed. Contributions in any language are
|
||||||
|
welcome.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Tidied **Settings → Appearance** into clearer groups (theme & colour, calendar,
|
- Tidied **Settings → Appearance** into clearer groups (theme & colour, calendar,
|
||||||
agenda), and the agenda's empty state now reads "You're all caught up".
|
agenda), and the agenda's empty state now reads "You're all caught up".
|
||||||
|
- Reworked **Settings → Calendars**. Local and synced calendars are now grouped
|
||||||
|
into collapsible, source-branded cards — each account shows its app's icon —
|
||||||
|
with a per-account menu to enable or disable all of its calendars at once or
|
||||||
|
open the account in its source app.
|
||||||
|
- Reorganised **Settings → Notifications**: reliable-delivery and snooze settings
|
||||||
|
moved up with the other global reminder options, and the per-calendar reminder
|
||||||
|
overrides now fold into a single expandable section.
|
||||||
|
- Moved **Add Quick Settings tile** out of the event-form section to its own
|
||||||
|
top-level entry in Settings.
|
||||||
|
|
||||||
## [2.10.0] — 2026-06-25
|
## [2.10.0] — 2026-06-25
|
||||||
|
|
||||||
@@ -661,3 +692,6 @@ automatically, with zero telemetry and no internet permission.
|
|||||||
[#4]: https://codeberg.org/jlmakiola/calendula/issues/4
|
[#4]: https://codeberg.org/jlmakiola/calendula/issues/4
|
||||||
[#5]: https://codeberg.org/jlmakiola/calendula/issues/5
|
[#5]: https://codeberg.org/jlmakiola/calendula/issues/5
|
||||||
[#6]: https://codeberg.org/jlmakiola/calendula/issues/6
|
[#6]: https://codeberg.org/jlmakiola/calendula/issues/6
|
||||||
|
[#7]: https://codeberg.org/jlmakiola/calendula/issues/7
|
||||||
|
[#8]: https://codeberg.org/jlmakiola/calendula/issues/8
|
||||||
|
[#9]: https://codeberg.org/jlmakiola/calendula/issues/9
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -68,7 +68,8 @@ database, no sync stack reinvented.
|
|||||||
|
|
||||||
- Real Material 3 Expressive throughout — dynamic color (Android 12+),
|
- Real Material 3 Expressive throughout — dynamic color (Android 12+),
|
||||||
expressive motion and shapes, light/dark theme
|
expressive motion and shapes, light/dark theme
|
||||||
- German and English UI, per-app language setting
|
- German and English UI, per-app language setting — and [open to community
|
||||||
|
translations](#-translations)
|
||||||
- **Zero telemetry, zero analytics, no internet permission** — your data
|
- **Zero telemetry, zero analytics, no internet permission** — your data
|
||||||
never leaves the device
|
never leaves the device
|
||||||
|
|
||||||
@@ -108,6 +109,17 @@ without reinstalling. Or build from source — see below.
|
|||||||
- **[Architecture](docs/ARCHITECTURE.md)** — the layered design and key pipelines
|
- **[Architecture](docs/ARCHITECTURE.md)** — the layered design and key pipelines
|
||||||
- **[Roadmap](.planning/ROADMAP.md)** — what's shipped and what's next
|
- **[Roadmap](.planning/ROADMAP.md)** — what's shipped and what's next
|
||||||
|
|
||||||
|
## 🌍 Translations
|
||||||
|
|
||||||
|
Calendula ships in German and English, and you're warmly invited to add your
|
||||||
|
language. Translations are managed on a self-hosted **Weblate**:
|
||||||
|
|
||||||
|
**→ [Help translate Calendula](https://weblate.dev.jeanlucmakiola.de/engage/calendula/)**
|
||||||
|
|
||||||
|
No coding needed — register on the Weblate server, pick (or request) a language,
|
||||||
|
and translate the strings in your browser. You can also reach this link in the
|
||||||
|
app from the top of **Settings → App language**.
|
||||||
|
|
||||||
## 📜 License
|
## 📜 License
|
||||||
|
|
||||||
[MIT](LICENSE) — Jean-Luc Makiola, 2026
|
[MIT](LICENSE) — Jean-Luc Makiola, 2026
|
||||||
|
|||||||
@@ -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 = 21100
|
versionCode = 21101
|
||||||
versionName = "2.11.0"
|
versionName = "2.11.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,35 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Be selectable as the system calendar app. Android has no API for
|
||||||
|
an app to make itself the default, so registering the filters
|
||||||
|
launchers and the OS use is what lets the user pick Calendula
|
||||||
|
from the system chooser when a date action fires (issue #9).
|
||||||
|
APP_CALENDAR is the "open the calendar app" action; the VIEW
|
||||||
|
filters below catch a tapped date. -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.APP_CALENDAR" />
|
||||||
|
</intent-filter>
|
||||||
|
<!-- A launcher/clock date tap fires ACTION_VIEW on the provider's
|
||||||
|
time Uri (content://com.android.calendar/time/<epochMillis>);
|
||||||
|
some surfaces use the time/epoch mime type. We open the day view
|
||||||
|
on that date (MainActivity.calendarTimeDateOrNull). -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data
|
||||||
|
android:scheme="content"
|
||||||
|
android:host="com.android.calendar"
|
||||||
|
android:pathPrefix="/time" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="time/epoch" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Open a .ics file (file manager / email attachment / browser). -->
|
<!-- Open a .ics file (file manager / email attachment / browser). -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package de.jeanlucmakiola.calendula
|
package de.jeanlucmakiola.calendula
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
import de.jeanlucmakiola.calendula.data.backup.BackupScheduler
|
||||||
|
import de.jeanlucmakiola.calendula.data.backup.BackupWorker
|
||||||
import de.jeanlucmakiola.calendula.data.crash.CrashReporter
|
import de.jeanlucmakiola.calendula.data.crash.CrashReporter
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application entry point. Registered as android:name=".CalendulaApp"
|
* Application entry point. Registered as android:name=".CalendulaApp"
|
||||||
@@ -16,5 +24,24 @@ class CalendulaApp : Application() {
|
|||||||
// Install first thing so startup crashes are captured too (privacy-
|
// Install first thing so startup crashes are captured too (privacy-
|
||||||
// respecting, on-device; the user submits the report by hand).
|
// respecting, on-device; the user submits the report by hand).
|
||||||
CrashReporter.install(this)
|
CrashReporter.install(this)
|
||||||
|
reconcileAutoBackup()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bring the scheduled auto-backup work back in line with the saved settings
|
||||||
|
* on every launch — re-arms it after a reinstall and, crucially, cancels any
|
||||||
|
* orphaned work once backup has been turned off.
|
||||||
|
*/
|
||||||
|
private fun reconcileAutoBackup() {
|
||||||
|
val deps = EntryPointAccessors.fromApplication(this, BackupWorker.Deps::class.java)
|
||||||
|
CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
|
||||||
|
val prefs = deps.settingsPrefs()
|
||||||
|
BackupScheduler.apply(
|
||||||
|
context = this@CalendulaApp,
|
||||||
|
enabled = prefs.autoBackupEnabled.first(),
|
||||||
|
intervalMinutes = prefs.autoBackupIntervalMinutes.first(),
|
||||||
|
hasFolder = prefs.autoBackupFolderUri.first() != null,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ import de.jeanlucmakiola.calendula.ui.crash.submitCrashReport
|
|||||||
import de.jeanlucmakiola.calendula.ui.settings.SettingsViewModel
|
import de.jeanlucmakiola.calendula.ui.settings.SettingsViewModel
|
||||||
import de.jeanlucmakiola.calendula.ui.theme.CalendulaTheme
|
import de.jeanlucmakiola.calendula.ui.theme.CalendulaTheme
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
@@ -156,10 +159,33 @@ class MainActivity : AppCompatActivity() {
|
|||||||
Intent.ACTION_SEND -> IntentCompat.getParcelableExtra(this, Intent.EXTRA_STREAM, Uri::class.java)
|
Intent.ACTION_SEND -> IntentCompat.getParcelableExtra(this, Intent.EXTRA_STREAM, Uri::class.java)
|
||||||
else -> null
|
else -> null
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
// The calendar "view time" Uri (a date tap) is also ACTION_VIEW/content;
|
||||||
|
// it's a navigation, not a file to import, so [navRequestOrNull] owns it.
|
||||||
|
if (uri.host == CALENDAR_PROVIDER_HOST) return null
|
||||||
return uri.takeIf { it.scheme == "content" || it.scheme == "file" }
|
return uri.takeIf { it.scheme == "content" || it.scheme == "file" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date a launcher/clock date tap points at, parsed from the AOSP calendar
|
||||||
|
* "view time" intent: ACTION_VIEW on `content://com.android.calendar/time/
|
||||||
|
* <epochMillis>`. Null for any other intent. The matching manifest filter is
|
||||||
|
* what lets users pick Calendula from the system calendar chooser (issue #9).
|
||||||
|
*/
|
||||||
|
private fun Intent.calendarTimeDateOrNull(): LocalDate? {
|
||||||
|
if (action != Intent.ACTION_VIEW) return null
|
||||||
|
val uri = data ?: return null
|
||||||
|
if (uri.host != CALENDAR_PROVIDER_HOST) return null
|
||||||
|
val segments = uri.pathSegments
|
||||||
|
if (segments.firstOrNull() != "time") return null
|
||||||
|
val millis = segments.getOrNull(1)?.toLongOrNull() ?: return null
|
||||||
|
return Instant.fromEpochMilliseconds(millis)
|
||||||
|
.toLocalDateTime(TimeZone.currentSystemDefault()).date
|
||||||
|
}
|
||||||
|
|
||||||
private fun Intent.navRequestOrNull(): WidgetNavRequest? {
|
private fun Intent.navRequestOrNull(): WidgetNavRequest? {
|
||||||
|
// An external date tap (launcher/clock) has no widget source, so it opens
|
||||||
|
// the day view rooted over the default home view (OpenDate source = null).
|
||||||
|
calendarTimeDateOrNull()?.let { return WidgetNavRequest.OpenDate(it.toString(), source = null) }
|
||||||
val source = sourceViewOrNull()
|
val source = sourceViewOrNull()
|
||||||
val eventId = getLongExtra(EXTRA_EVENT_ID, -1L)
|
val eventId = getLongExtra(EXTRA_EVENT_ID, -1L)
|
||||||
return when {
|
return when {
|
||||||
@@ -201,6 +227,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// The calendar provider's authority/host. A date tap arrives as
|
||||||
|
// ACTION_VIEW on content://com.android.calendar/time/<epochMillis>.
|
||||||
|
private const val CALENDAR_PROVIDER_HOST = "com.android.calendar"
|
||||||
|
|
||||||
private const val EXTRA_EVENT_ID = "de.jeanlucmakiola.calendula.extra.EVENT_ID"
|
private const val EXTRA_EVENT_ID = "de.jeanlucmakiola.calendula.extra.EVENT_ID"
|
||||||
private const val EXTRA_BEGIN_MILLIS = "de.jeanlucmakiola.calendula.extra.BEGIN"
|
private const val EXTRA_BEGIN_MILLIS = "de.jeanlucmakiola.calendula.extra.BEGIN"
|
||||||
private const val EXTRA_END_MILLIS = "de.jeanlucmakiola.calendula.extra.END"
|
private const val EXTRA_END_MILLIS = "de.jeanlucmakiola.calendula.extra.END"
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ object BackupScheduler {
|
|||||||
val workManager = WorkManager.getInstance(context)
|
val workManager = WorkManager.getInstance(context)
|
||||||
if (!enabled || !hasFolder) {
|
if (!enabled || !hasFolder) {
|
||||||
workManager.cancelUniqueWork(WORK_NAME)
|
workManager.cancelUniqueWork(WORK_NAME)
|
||||||
|
// Also cancel any pending immediate run — otherwise a failing run-now
|
||||||
|
// keeps retrying after the user has turned backup off.
|
||||||
|
workManager.cancelUniqueWork(WORK_NAME_NOW)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// WorkManager's own floor is 15 min; the UI floors the user choice at 30.
|
// WorkManager's own floor is 15 min; the UI floors the user choice at 30.
|
||||||
@@ -106,6 +109,9 @@ class BackupWorker(
|
|||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val deps = EntryPointAccessors.fromApplication(applicationContext, Deps::class.java)
|
val deps = EntryPointAccessors.fromApplication(applicationContext, Deps::class.java)
|
||||||
val prefs = deps.settingsPrefs()
|
val prefs = deps.settingsPrefs()
|
||||||
|
// Respect the toggle even for already-queued work: if backup was turned
|
||||||
|
// off, no-op (and don't retry) so a lingering run can't revive itself.
|
||||||
|
if (!prefs.autoBackupEnabled.first()) return Result.success()
|
||||||
val folder = prefs.autoBackupFolderUri.first()
|
val folder = prefs.autoBackupFolderUri.first()
|
||||||
?: return Result.failure() // nothing to write to — leave scheduling to settings
|
?: return Result.failure() // nothing to write to — leave scheduling to settings
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|||||||
@@ -196,7 +196,9 @@ fun CalendarHost(
|
|||||||
dismissCoveringOverlays()
|
dismissCoveringOverlays()
|
||||||
createDateIso = null
|
createDateIso = null
|
||||||
pendingDayIso = req.dateIso
|
pendingDayIso = req.dateIso
|
||||||
viewStack = viewBaseStack(defaultView, req.source).drillToDay()
|
// No widget source (an external date tap) roots over the default
|
||||||
|
// home view, so backing out of the day returns home then exits.
|
||||||
|
viewStack = viewBaseStack(defaultView, req.source ?: defaultView).drillToDay()
|
||||||
onWidgetNavConsumed()
|
onWidgetNavConsumed()
|
||||||
}
|
}
|
||||||
is WidgetNavRequest.OpenEvent -> {
|
is WidgetNavRequest.OpenEvent -> {
|
||||||
|
|||||||
@@ -13,8 +13,13 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
|||||||
* detail-key channel and leave the base view untouched.)
|
* detail-key channel and leave the base view untouched.)
|
||||||
*/
|
*/
|
||||||
sealed interface WidgetNavRequest {
|
sealed interface WidgetNavRequest {
|
||||||
/** Open the day view anchored on [dateIso] (an ISO `yyyy-MM-dd` date), over [source]. */
|
/**
|
||||||
data class OpenDate(val dateIso: String, val source: CalendarView) : WidgetNavRequest
|
* Open the day view anchored on [dateIso] (an ISO `yyyy-MM-dd` date), over
|
||||||
|
* [source]. A null [source] means the request came from outside the app (a
|
||||||
|
* launcher/clock date tap, issue #9) rather than a widget, so it roots over
|
||||||
|
* the default home view instead of a widget's view.
|
||||||
|
*/
|
||||||
|
data class OpenDate(val dateIso: String, val source: CalendarView?) : WidgetNavRequest
|
||||||
|
|
||||||
/** Open one occurrence's detail (an agenda-widget event tap), over [source]. */
|
/** Open one occurrence's detail (an agenda-widget event tap), over [source]. */
|
||||||
data class OpenEvent(
|
data class OpenEvent(
|
||||||
|
|||||||
@@ -86,8 +86,10 @@ fun <T> OptionPicker(
|
|||||||
label: @Composable (T) -> String,
|
label: @Composable (T) -> String,
|
||||||
onSelect: (T) -> Unit,
|
onSelect: (T) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
|
header: (@Composable ColumnScope.() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
FullScreenPicker(title = title, onDismiss = onDismiss) {
|
FullScreenPicker(title = title, onDismiss = onDismiss) {
|
||||||
|
header?.invoke(this)
|
||||||
options.forEachIndexed { index, option ->
|
options.forEachIndexed { index, option ->
|
||||||
val isSelected = option == selected
|
val isSelected = option == selected
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import androidx.compose.material.icons.filled.Gavel
|
|||||||
import androidx.compose.material.icons.filled.Language
|
import androidx.compose.material.icons.filled.Language
|
||||||
import androidx.compose.material.icons.filled.Notifications
|
import androidx.compose.material.icons.filled.Notifications
|
||||||
import androidx.compose.material.icons.filled.Palette
|
import androidx.compose.material.icons.filled.Palette
|
||||||
|
import androidx.compose.material.icons.filled.Translate
|
||||||
import androidx.compose.material.icons.filled.Tune
|
import androidx.compose.material.icons.filled.Tune
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -318,6 +319,18 @@ private fun LanguageRow(position: Position) {
|
|||||||
AppLanguage.apply(it)
|
AppLanguage.apply(it)
|
||||||
},
|
},
|
||||||
onDismiss = { showDialog = false },
|
onDismiss = { showDialog = false },
|
||||||
|
// Invite contributions right where users pick their language.
|
||||||
|
header = {
|
||||||
|
val translateUrl = stringResource(R.string.about_translate_url)
|
||||||
|
GroupedRow(
|
||||||
|
title = stringResource(R.string.settings_translate),
|
||||||
|
summary = stringResource(R.string.settings_translate_hint),
|
||||||
|
position = Position.Alone,
|
||||||
|
leading = { CategoryIcon(Icons.Default.Translate, ChipAccent.Neutral) },
|
||||||
|
onClick = { openUrl(context, translateUrl) },
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,6 +330,8 @@
|
|||||||
<string name="settings_section_language">Language</string>
|
<string name="settings_section_language">Language</string>
|
||||||
<string name="settings_language">App language</string>
|
<string name="settings_language">App language</string>
|
||||||
<string name="settings_language_auto">System default</string>
|
<string name="settings_language_auto">System default</string>
|
||||||
|
<string name="settings_translate">Help translate</string>
|
||||||
|
<string name="settings_translate_hint">Add or improve a language on Weblate</string>
|
||||||
<!-- Hub category subtitles -->
|
<!-- Hub category subtitles -->
|
||||||
<string name="settings_appearance_subtitle">Theme, default view, week start</string>
|
<string name="settings_appearance_subtitle">Theme, default view, week start</string>
|
||||||
<string name="settings_event_form_subtitle">Default fields for new events</string>
|
<string name="settings_event_form_subtitle">Default fields for new events</string>
|
||||||
@@ -436,6 +438,7 @@
|
|||||||
<string name="about_source_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula</string>
|
<string name="about_source_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula</string>
|
||||||
<string name="about_license_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/LICENSE</string>
|
<string name="about_license_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/LICENSE</string>
|
||||||
<string name="about_support_url" translatable="false">https://ko-fi.com/jeanlucmakiola</string>
|
<string name="about_support_url" translatable="false">https://ko-fi.com/jeanlucmakiola</string>
|
||||||
|
<string name="about_translate_url" translatable="false">https://weblate.dev.jeanlucmakiola.de/engage/calendula/</string>
|
||||||
|
|
||||||
<!-- Crash reporting: a captured report the user can submit, by hand, as a
|
<!-- Crash reporting: a captured report the user can submit, by hand, as a
|
||||||
Gitea issue (the app sends nothing automatically). -->
|
Gitea issue (the app sends nothing automatically). -->
|
||||||
|
|||||||
@@ -21,8 +21,30 @@
|
|||||||
top naming the exact dates in view, with a button to switch the range just for
|
top naming the exact dates in view, with a button to switch the range just for
|
||||||
the current session (it resets when you reopen the app); the bar can be turned
|
the current session (it resets when you reopen the app); the bar can be turned
|
||||||
off. Thanks to @zmaherdev for the suggestion ([#4]).
|
off. Thanks to @zmaherdev for the suggestion ([#4]).
|
||||||
|
- Automatic backup of local calendars. A new **Automatic backup** option
|
||||||
|
(Settings → Calendars → Backup) periodically exports your local calendars to an
|
||||||
|
`.ics` file in a folder you choose, on an interval you set (from 30 minutes up).
|
||||||
|
Useful for file-based syncing such as Syncthing, or simply as a safety net — it
|
||||||
|
is a one-way export and never touches your synced accounts. Thanks to @shield
|
||||||
|
for the discussion that inspired it ([#7], [#8]).
|
||||||
|
- Field icons in the event-form settings. Each optional-field toggle (Settings →
|
||||||
|
Event form) now shows the same icon the field uses in the new-event form, so
|
||||||
|
the list is easier to scan.
|
||||||
|
- Help translate Calendula. A **Help translate** link at the top of **Settings →
|
||||||
|
App language** opens the project's Weblate, where you can add or improve a
|
||||||
|
language in your browser — no coding needed. Contributions in any language are
|
||||||
|
welcome.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Tidied **Settings → Appearance** into clearer groups (theme & colour, calendar,
|
- Tidied **Settings → Appearance** into clearer groups (theme & colour, calendar,
|
||||||
agenda), and the agenda's empty state now reads "You're all caught up".
|
agenda), and the agenda's empty state now reads "You're all caught up".
|
||||||
|
- Reworked **Settings → Calendars**. Local and synced calendars are now grouped
|
||||||
|
into collapsible, source-branded cards — each account shows its app's icon —
|
||||||
|
with a per-account menu to enable or disable all of its calendars at once or
|
||||||
|
open the account in its source app.
|
||||||
|
- Reorganised **Settings → Notifications**: reliable-delivery and snooze settings
|
||||||
|
moved up with the other global reminder options, and the per-calendar reminder
|
||||||
|
overrides now fold into a single expandable section.
|
||||||
|
- Moved **Add Quick Settings tile** out of the event-form section to its own
|
||||||
|
top-level entry in Settings.
|
||||||
|
|
||||||
|
|||||||
6
fastlane/metadata/android/en-US/changelogs/21101.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/21101.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
### Fixed
|
||||||
|
- Calendula can now be set as your default calendar app. It registers the
|
||||||
|
calendar-app intent filters the system uses, so it appears in the chooser when
|
||||||
|
you tap a date in a launcher or clock — and opening one takes you straight to
|
||||||
|
that day. Android has no way for an app to make itself the default, so you pick
|
||||||
|
it once from the system picker. Thanks to @abrossimow for the report ([#9]).
|
||||||
Reference in New Issue
Block a user