Compare commits
5 Commits
feat/per-c
...
40d70d9f2b
| Author | SHA1 | Date | |
|---|---|---|---|
| 40d70d9f2b | |||
| 14b19cd902 | |||
| 8d530c5681 | |||
| 11578a5588 | |||
| 405ff16233 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -14,8 +14,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
a reminder a week before *and* one on the day. The reminder pickers are now
|
||||
multi-select; per-calendar overrides can still inherit the global default or
|
||||
turn reminders off entirely. Thanks to @moonj for the suggestion ([#14]).
|
||||
- Widget headers now open the app. Tapping the month/year title on the month
|
||||
widget opens the app on the month view, and tapping the "Upcoming" title on
|
||||
the agenda widget opens it on your default view — so there's a one-tap way
|
||||
back into the app that lands where you'd expect, instead of only through a day
|
||||
or event. The month widget's "today" button now snaps the grid back to the
|
||||
current month in place. Thanks to @rgz46vic and @ptab for the suggestions
|
||||
([#18], [#20]).
|
||||
|
||||
### Fixed
|
||||
- Month widget arrows and "today" button work again. On release builds the
|
||||
prev/next-month arrows and the jump-to-today control on the month widget did
|
||||
nothing when tapped — code shrinking had stripped the tap handlers behind
|
||||
them. They respond again. Thanks to @rgz46vic for the report ([#18]).
|
||||
- Disabled calendars no longer notify. Reminders for events in a calendar you
|
||||
have disabled (Settings → Calendars) are now suppressed instead of still
|
||||
popping up — matching how a disabled calendar's events already stay hidden
|
||||
@@ -754,3 +765,5 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#14]: https://codeberg.org/jlmakiola/calendula/issues/14
|
||||
[#16]: https://codeberg.org/jlmakiola/calendula/issues/16
|
||||
[#17]: https://codeberg.org/jlmakiola/calendula/issues/17
|
||||
[#18]: https://codeberg.org/jlmakiola/calendula/issues/18
|
||||
[#20]: https://codeberg.org/jlmakiola/calendula/issues/20
|
||||
|
||||
12
app/proguard-rules.pro
vendored
12
app/proguard-rules.pro
vendored
@@ -16,6 +16,18 @@
|
||||
-keep class * extends androidx.room.RoomDatabase { *; }
|
||||
-dontwarn androidx.room.paging.**
|
||||
|
||||
# Glance runs an @Composable's `actionRunCallback<T>()` by persisting the
|
||||
# callback's fully-qualified class name into the click PendingIntent, then
|
||||
# reflectively instantiating it (Class.forName(name).newInstance()) when the tap
|
||||
# fires. Under R8 full mode (AGP 9 default) these ActionCallback classes — only
|
||||
# ever referenced reflectively — get renamed or have their no-arg constructor
|
||||
# stripped, so the lookup fails silently and the tap does nothing. In the month
|
||||
# and agenda widgets that broke every run-callback control (the prev/next/today
|
||||
# month arrows and the agenda refresh) in release builds while actionStartActivity
|
||||
# taps, which ride a PendingIntent and need no reflection, kept working. Keep
|
||||
# every ActionCallback's name and constructor intact.
|
||||
-keep class * implements androidx.glance.appwidget.action.ActionCallback { <init>(...); }
|
||||
|
||||
# WorkManager instantiates an InputMerger reflectively (Class.newInstance) from
|
||||
# the fully-qualified class name persisted in the WorkSpec, so the class must
|
||||
# keep both its name and a no-arg constructor. Glance renders every widget
|
||||
|
||||
@@ -193,6 +193,12 @@ class MainActivity : AppCompatActivity() {
|
||||
// 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) }
|
||||
// A widget header tap: open a top-level view with no date drill-in. The
|
||||
// empty string carried by [openViewIntent] means "the default home view".
|
||||
if (hasExtra(EXTRA_OPEN_VIEW)) {
|
||||
val name = getStringExtra(EXTRA_OPEN_VIEW).orEmpty()
|
||||
return WidgetNavRequest.OpenView(CalendarView.entries.firstOrNull { it.name == name })
|
||||
}
|
||||
val source = sourceViewOrNull()
|
||||
val eventId = getLongExtra(EXTRA_EVENT_ID, -1L)
|
||||
return when {
|
||||
@@ -244,6 +250,10 @@ class MainActivity : AppCompatActivity() {
|
||||
private const val EXTRA_DATE_ISO = "de.jeanlucmakiola.calendula.extra.DATE_ISO"
|
||||
private const val EXTRA_CREATE = "de.jeanlucmakiola.calendula.extra.CREATE"
|
||||
|
||||
// A widget header tap asking to open a top-level view (no date drill-in).
|
||||
// Its value is the target [CalendarView] name, or "" for the default view.
|
||||
private const val EXTRA_OPEN_VIEW = "de.jeanlucmakiola.calendula.extra.OPEN_VIEW"
|
||||
|
||||
// The [CalendarView] (by name) of the widget a launch came from. Roots the
|
||||
// in-app back stack in that view; absent for non-widget launches (reminders).
|
||||
private const val EXTRA_SOURCE_VIEW = "de.jeanlucmakiola.calendula.extra.SOURCE_VIEW"
|
||||
@@ -307,5 +317,19 @@ class MainActivity : AppCompatActivity() {
|
||||
putExtra(EXTRA_DATE_ISO, date.toString())
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the app on a top-level [view] with no date drill-in — a widget
|
||||
* header tap. A null [view] opens the user's default home view (the agenda
|
||||
* widget's "Upcoming" title); a concrete view roots there over the default
|
||||
* home (the month widget's month/year title → [CalendarView.Month]). The
|
||||
* per-view data URI keeps distinct headers' PendingIntents from collapsing.
|
||||
*/
|
||||
fun openViewIntent(context: Context, view: CalendarView?): Intent =
|
||||
Intent(context, MainActivity::class.java).apply {
|
||||
data = "calendula://view/${view?.name ?: "default"}".toUri()
|
||||
putExtra(EXTRA_OPEN_VIEW, view?.name ?: "")
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,17 @@ fun CalendarHost(
|
||||
detailKey = key
|
||||
onWidgetNavConsumed()
|
||||
}
|
||||
is WidgetNavRequest.OpenView -> {
|
||||
// A widget header tap: land on a top-level view with no date
|
||||
// drill-in. Reveal it by dropping any covering overlay, then root
|
||||
// the stack on the target (null → the default home) over the
|
||||
// default home — so backing out returns to the default, then exits.
|
||||
dismissCoveringOverlays()
|
||||
createDateIso = null
|
||||
pendingDayIso = null
|
||||
viewStack = viewBaseStack(defaultView, req.view ?: defaultView)
|
||||
onWidgetNavConsumed()
|
||||
}
|
||||
is WidgetNavRequest.Create -> {
|
||||
// External "new event" entries (QS tile / launcher shortcut /
|
||||
// widget) must land on top of whatever is open — the form overlay
|
||||
|
||||
@@ -31,4 +31,13 @@ sealed interface WidgetNavRequest {
|
||||
|
||||
/** Open the create-event form prefilled for [dateIso] (today when null). */
|
||||
data class Create(val dateIso: String?) : WidgetNavRequest
|
||||
|
||||
/**
|
||||
* Open the app rooted on a top-level [view] with no date drill-in — a widget
|
||||
* header tap. A null [view] means "the user's default home view" (the agenda
|
||||
* widget's "Upcoming" title, issue #20); a concrete view roots there over the
|
||||
* default home (the month widget's month/year title → [CalendarView.Month],
|
||||
* issue #18), so backing out returns to the default view, then exits.
|
||||
*/
|
||||
data class OpenView(val view: CalendarView?) : WidgetNavRequest
|
||||
}
|
||||
|
||||
@@ -193,6 +193,8 @@ private fun AgendaHeader() {
|
||||
modifier = GlanceModifier.fillMaxWidth().padding(horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
// Tap the "Upcoming" title to open the app on the user's default view
|
||||
// (issue #20) — a null target resolves to the default home in the host.
|
||||
Text(
|
||||
text = context.getString(R.string.widget_agenda_title),
|
||||
style = TextStyle(
|
||||
@@ -200,7 +202,11 @@ private fun AgendaHeader() {
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
modifier = GlanceModifier.defaultWeight(),
|
||||
modifier = GlanceModifier
|
||||
.defaultWeight()
|
||||
.clickable(
|
||||
actionStartActivity(MainActivity.openViewIntent(context, view = null)),
|
||||
),
|
||||
)
|
||||
IconButton(
|
||||
resId = R.drawable.ic_widget_refresh,
|
||||
|
||||
@@ -194,6 +194,8 @@ private fun MonthHeader(label: String) {
|
||||
),
|
||||
),
|
||||
)
|
||||
// Tap the month/year title to open the app on the month view (issue #18),
|
||||
// rooted over the default home so backing out returns there.
|
||||
Text(
|
||||
text = label,
|
||||
style = TextStyle(
|
||||
@@ -204,16 +206,17 @@ private fun MonthHeader(label: String) {
|
||||
),
|
||||
modifier = GlanceModifier
|
||||
.defaultWeight()
|
||||
.clickable(actionRunCallback<ResetMonthAction>()),
|
||||
.clickable(
|
||||
actionStartActivity(
|
||||
MainActivity.openViewIntent(context, CalendarView.Month),
|
||||
),
|
||||
),
|
||||
)
|
||||
// The "today" button snaps the grid back to the current month in place.
|
||||
HeaderIcon(
|
||||
resId = R.drawable.ic_widget_today,
|
||||
contentDescription = context.getString(R.string.widget_today),
|
||||
onClick = GlanceModifier.clickable(
|
||||
actionStartActivity(
|
||||
MainActivity.openDateIntent(context, today(systemZone()), CalendarView.Month),
|
||||
),
|
||||
),
|
||||
onClick = GlanceModifier.clickable(actionRunCallback<ResetMonthAction>()),
|
||||
)
|
||||
HeaderIcon(
|
||||
resId = R.drawable.ic_widget_chevron_right,
|
||||
|
||||
Reference in New Issue
Block a user