Compare commits

..
Author SHA1 Message Date
makiolaj 754745586d Run the Play job on trixie for fastlane's Ruby 3.2 requirement (#509) 2026-11-13 19:17:46 +01:00
Jean-Luc Makiola 1a1df07dc3 Release v2.24.2 (#511)
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 12s
Renovate / renovate (push) Successful in 1m53s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Successful in 17m38s
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Failing after 34s
- Queued drops: moves made while an earlier one is still saving are queued instead of dropped, with messages for stale and skipped moves (#494)
- Day view title names the month (#501)
- Dependency updates: kotlinx-datetime, AppCompat, fastlane, Renovate image (#507–#510)
- Version 2.24.2 (2240299), CHANGELOG, What's New in all store locales

#494 stays open until the reporter answers; the fix covers the ways a quick move could get lost, the original report isn't reproduced.

Refs #494
Closes #501
2026-11-13 18:56:38 +01:00
makiolaj 75315d92a7 Merge main into release/2.24.2
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Skipped
2026-11-13 16:10:53 +01:00
95ad3a30cf Release v2.24.1 (#504)
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 6s
Renovate / renovate (push) Successful in 1m20s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Successful in 19m27s
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Successful in 1m58s
- Agenda widget: Cards / Classic style option, opacity slider for all Solid/Tinted widgets (#489)
- Month widget fits events into short week rows (#491)
- Week numbers follow the locale's rules and the row's first day (#493)
- Readable dimmed events in dark widgets, pinned Light/Dark theme matches system colours (#475)
- Dropped unused WorkManager permissions: network state, wake lock, foreground service (#448)
- Weblate translations (#434), incl. the Arabic string from after the 2.24.0 squash
- Version 2.24.1 (2240199), CHANGELOG, What's New in all store locales

#489 deviates from the issue: the classic look comes back as a style option instead of replacing the new design.

Closes #489
Closes #491
Closes #493
Closes #475
Closes #448

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Co-authored-by: Sup99999 <alelmnour1o234@proton.me>
Co-authored-by: Anonymous <noreply@weblate.org>
Co-authored-by: Louis Leblanc <sandraev@posteo.dk>
Co-authored-by: Dmitri Gabinski <dkgabi@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/504
2026-11-13 11:17:48 +01:00
Jean-Luc Makiola 8ce4831efa Translations update from Weblate (#434)
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 12s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Skipped
Renovate / renovate (push) Successful in 2m59s
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/434
2026-11-13 11:07:42 +01:00
7 changed files with 10 additions and 94 deletions

No files matched your search

+4 -1
View File
@@ -318,13 +318,16 @@ jobs:
# not provide node inside custom job containers, so JavaScript actions
# (checkout, download-artifact) can't run there. The Renovate job gets away
# with a container because its only step is a shell command. Ruby is installed
# the same way sshpass, jq and fdroidserver are in the job above.
# the same way sshpass, jq and fdroidserver are in the job above, from trixie:
# fastlane >= 2.241 needs Ruby 3.2+, and bookworm's apt only has 3.1.
play:
needs: [detect, release]
# workflow_dispatch is the F-Droid re-sign recovery path — it must never
# touch Play, so gate on a real release only.
if: needs.detect.outputs.is_release == 'true'
runs-on: docker
container:
image: node:22-trixie
env:
VERSION: ${{ needs.detect.outputs.version }}
VERSION_CODE: ${{ needs.detect.outputs.version_code }}
@@ -5,7 +5,6 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
@@ -19,9 +18,7 @@ import de.jeanlucmakiola.calendula.data.prefs.CalendarPrefs
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.is24Hour
import de.jeanlucmakiola.calendula.data.prefs.resolveFirstDay
import de.jeanlucmakiola.calendula.domain.reminders.reminderMapQuery
import de.jeanlucmakiola.floret.android.hasPermission
import de.jeanlucmakiola.floret.android.resolvableMapsSearchIntent
import kotlinx.coroutines.flow.first
import kotlinx.datetime.isoDayNumber
import java.time.DayOfWeek
@@ -113,11 +110,6 @@ class ReminderNotifier @Inject constructor(
context.getString(R.string.reminder_action_dismiss),
actionIntent(alert, ReminderActionReceiver.ACTION_DISMISS),
)
.apply {
mapIntent(alert)?.let {
addAction(R.drawable.ic_notification_map, context.getString(R.string.event_detail_open_in_maps), it)
}
}
.build()
val manager = NotificationManagerCompat.from(context)
cancelLegacy(alert)
@@ -189,24 +181,6 @@ class ReminderNotifier @Inject constructor(
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
/**
* Opens [alert]'s location in maps like the detail screen does, started
* directly (no receiver trampoline). Leaves the notification up for a later
* snooze or dismiss. Null without a place to look up (a bare meeting link
* isn't one) or an app to open it.
*/
private fun mapIntent(alert: ReminderAlert): PendingIntent? {
val query = reminderMapQuery(alert.location) ?: return null
val intent = context.resolvableMapsSearchIntent(query) ?: return null
return PendingIntent.getActivity(
context,
// An implicit activity intent: filterEquals already keeps it apart from the others.
/* requestCode = */ 0,
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
/** Channel creation is idempotent; re-running refreshes the localized name. */
private fun ensureChannel() {
val channel = NotificationChannel(
@@ -1,14 +0,0 @@
package de.jeanlucmakiola.calendula.domain.reminders
// Same link shape the detail screen linkifies (ui/detail/WebLinks.kt).
private val webLinkRegex = Regex("""https?://[^\s<>"]+""", RegexOption.IGNORE_CASE)
/**
* What a reminder's "Open in maps" button looks up for [location] — the same
* text the detail screen's location card opens in maps — or null when there is
* no place to find: blank, or nothing but web (meeting) links.
*/
fun reminderMapQuery(location: String?): String? {
val trimmed = location?.trim()?.takeIf { it.isNotEmpty() } ?: return null
return trimmed.takeIf { webLinkRegex.replace(it, "").any(Char::isLetterOrDigit) }
}
@@ -90,7 +90,6 @@ import de.jeanlucmakiola.calendula.domain.timeZoneOptionOf
import de.jeanlucmakiola.calendula.domain.zoneDescriptor
import de.jeanlucmakiola.calendula.domain.zoneOrNull
import de.jeanlucmakiola.calendula.ui.common.orUnnamed
import de.jeanlucmakiola.floret.android.mapsSearchIntents
import de.jeanlucmakiola.floret.components.IconInfoCard
import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme
import de.jeanlucmakiola.floret.identity.predictiveBack
@@ -885,6 +884,9 @@ private fun startOrSayNoApp(context: Context, intent: Intent) {
/** Open a maps intent for [query]; fall back to a web maps URL if no app handles geo:. */
private fun openInMaps(context: Context, query: String) {
val opened = mapsSearchIntents(query).any { runCatching { context.startActivity(it) }.isSuccess }
if (!opened) Toast.makeText(context, R.string.link_open_failed, Toast.LENGTH_SHORT).show()
val encoded = Uri.encode(query)
val geo = Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=$encoded"))
if (runCatching { context.startActivity(geo) }.isFailure) {
openWebLink(context, "https://www.google.com/maps/search/?api=1&query=$encoded")
}
}
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Monochrome notification-action mark: Material "place" glyph. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z" />
</vector>
@@ -1,37 +0,0 @@
package de.jeanlucmakiola.calendula.domain.reminders
import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test
class ReminderMapQueryTest {
@Test
fun `an address is looked up as written`() {
assertThat(reminderMapQuery(" Main St 1, Berlin ")).isEqualTo("Main St 1, Berlin")
}
@Test
fun `no location means no lookup`() {
assertThat(reminderMapQuery(null)).isNull()
assertThat(reminderMapQuery(" ")).isNull()
}
@Test
fun `a bare meeting link is not a place`() {
assertThat(reminderMapQuery("https://meet.google.com/abc-defg-hij")).isNull()
assertThat(reminderMapQuery("HTTP://example.com")).isNull()
assertThat(reminderMapQuery("<https://teams.microsoft.com/l/meetup-join/x>")).isNull()
assertThat(reminderMapQuery("https://a.example\nhttps://b.example")).isNull()
}
@Test
fun `dotted place names are still places`() {
assertThat(reminderMapQuery("Hauptstr. 12/3, Wien")).isEqualTo("Hauptstr. 12/3, Wien")
assertThat(reminderMapQuery("St.Gallen/Zentrum")).isEqualTo("St.Gallen/Zentrum")
}
@Test
fun `a place with a link beside it is still looked up`() {
assertThat(reminderMapQuery("Room 4, https://example.com/map")).isEqualTo("Room 4, https://example.com/map")
}
}