Commit Graph
651 Commits
Author SHA1 Message Date
Linus Rath ef1d4bc387 fix: stop the app crashing when a notification opens a mail 2026-10-13 18:07:46 +02:00
Linus Rath 61690603a2 chore: update version number to 0.1.73 0.1.73 2026-10-10 23:54:40 +02:00
Linus Rath 765ff26aa5 Merge remote-tracking branch 'origin/main' 2026-10-10 23:20:25 +02:00
Linus Rath b832ac0c49 feat(calendar): tap an empty time slot to start a new event
In the day, 3 days and week grids an event could only be started with a
long press, which few people find. Tapping an empty slot now marks it
with a "New event" block, and tapping that block opens the editor at
that hour; a second tap elsewhere moves the block. Long press still
opens the editor straight away. Tapping a day's header in the week or
3 days grid opens that day.

The editor also started events from a slot half an hour late, because
it rounded a time already on the hour up to the next half hour.
2026-10-10 22:59:35 +02:00
Linus Rath f53cee7ec9 feat(calendar): lay out the agenda as a schedule with dates on the left
The agenda showed each day as a grey header bar above its events. It now
reads like a schedule: the weekday and date sit in a column on the left
of the day's first event (today in a filled circle), a heading marks
where each month starts and a quiet line names each week's range. An
empty today says "Nothing planned. Tap to create." and tapping it opens
a new event. In the drawer the view is now called Schedule.

Events that fill a whole day are listed as all day instead of
"00:00 – 00:00".
2026-10-10 22:53:40 +02:00
Linus Rath 21d469f7e8 feat(calendar): show the month as a full-screen grid of events
The month view used to be a small grid of dots with the selected day's
events listed underneath, so you only ever saw one day's events at a
time. It now fills the screen: each day is a cell with its events as
solid bars in the calendar colour and "+N" for the ones that don't fit.
Tapping a day opens it in the day view, and long-pressing still creates
an event there. Events that fill the whole day no longer show 00:00.

The dotted month stays for the month that drops down under the title.
2026-10-10 22:47:13 +02:00
Linus Rath c1e0cb36c9 feat(calendar): move views into the drawer and add a create button
The calendar header was crowded: four icon toggles, a separate row of
arrows and a Today button, and a small create button squeezed in next
to them. Navigation now follows Google Calendar's layout while keeping
the app's own look:

- the header shows the month; tapping it drops down a month you can
  swipe through and pick a day from
- a calendar icon with today's date jumps to today
- Schedule, Day, 3 days, Week and Month are listed at the top of the
  drawer, styled like the mail drawer's folders, with Refresh below
- a round create button sits in the corner like the mail compose
  button; with tasks on it offers Event or Task
- a new 3 days view shows three columns of the time grid

The arrows stay in the header when free scrolling is off, since the
views can't be swiped then.
2026-10-10 22:41:46 +02:00
Linus Rath d8879617ed fix: never destroy mail from the notification Delete action #68
With no Trash mailbox, or when Mailbox/get came back as an error, the
Delete action fell through to destroying the email. A shared account
without its own Trash lost mail permanently on one tap.

- only move to Trash; do nothing when there is no Trash or the lookup
  failed
- check that Email/set actually updated the email
- show "(No Subject)" in the collapsed text instead of a blank line
- require unlock for Delete (Android 12+)
2026-10-10 19:20:53 +02:00
Linus Rath 7b62755aee Merge pull request #68 from arnauda-gh/feat/notification-preview-and-actions
feat(notifications): add rich preview snippet and quick actions (mark read, delete, reply)
2026-10-10 19:20:46 +02:00
Linus Rath 96e1cc1c3f fix: open a list mail without mounting every pane before it #67
The tap fix makes a notification open a single page, so the viewer no
longer needs to render up to the opened index. That setting mounted
about N mail panes on every list open, on iOS too. Also drop the
onScrollToIndexFailed handler, which FlatList never calls when
getItemLayout is set.
2026-10-10 19:20:46 +02:00
Linus Rath ec137e931b Merge pull request #67 from arnauda-gh/fix/notification-open-target-email
fix(notifications): ensure notification tap always opens target email
2026-10-10 19:20:40 +02:00
Linus Rath cf78b07939 Merge pull request #69 from arnauda-gh/fix/quick-reply-keyboard-avoidance
fix(viewer): adapt screen and auto-scroll quick reply box when keyboard appears
2026-10-10 19:20:39 +02:00
arnauda-gh 5237d9004d fix(viewer): adapt screen and auto-scroll quick reply box when keyboard appears 2026-10-09 15:00:51 +02:00
arnauda-gh be15af5d4c fix(notifications): execute background actions directly via HeadlessJs without service 2026-10-09 12:41:53 +02:00
arnauda-gh 90fe43882a feat(notifications): add email preview snippet and quick actions (mark read, delete, reply) 2026-10-09 09:16:53 +02:00
arnauda-gh 465eeffd1e fix(viewer): use info.index * windowWidth for onScrollToIndexFailed offset 2026-10-09 09:05:27 +02:00
arnauda-gh 200404483c fix(notifications): ensure notification tap always opens target email 2026-10-09 09:01:28 +02:00
Linus Rath 28f6787279 fix: confirm a copied event location on iOS too #59 2026-10-06 23:32:04 +02:00
Linus Rath 89f0524ad4 fix: only offer meeting links the app can open #59 2026-10-06 23:30:57 +02:00
Linus Rath 172110f7b0 Merge pull request #59 from lucletoffe/fix/calendar-location-links
fix(calendar): join the meeting and open the location from the event sheet
2026-10-06 23:30:07 +02:00
Linus Rath aa612e6f0e Merge pull request #55 from michaelbrusegard/fix/external-idp-handoff
fix(auth): verify external identity providers during handoff
2026-10-06 23:29:29 +02:00
Linus Rath 76180b332d ci: put every iOS release on the public TestFlight link
Uploads only reached internal testers. The public link's "external" group
still served 0.1.66, because nothing added later builds to it or
submitted them for Beta App Review.

A new `public-testflight` job runs after the upload on a Linux runner.
It waits for App Store Connect to process the build, adds it to the
external group, sets "What to Test" to the GitHub release and submits
it for review (`scripts/testflight-public.mjs`, safe to re-run). The
README links the TestFlight beta and the latest APK.
2026-10-02 16:33:07 +02:00
Linus Rath f5c491f6d7 chore: update version number to 0.1.72 0.1.72 2026-10-02 15:02:12 +02:00
Linus Rath 23af324d79 fix: verify app updates without a second slow progress bar
Since releases ship an `.apk.sha256` file, every in-app update checks
the downloaded APK against it. The check read the 40 MB file in base64
chunks and hashed it in JS, which took longer than the download, and it
reset the progress bar to 0 under the "Downloading" label, so the update
looked like it downloaded twice, the second time slowly.

The file is now read and hashed natively through expo-crypto (the JS
hasher remains as a fallback), and the bar stays full under a
"Verifying…" label while the check runs.
2026-10-02 15:02:10 +02:00
Linus Rath 761c35918f chore: update version number to 0.1.71 0.1.71 2026-10-02 14:09:36 +02:00
Linus Rath 86e33b9afe fix: sync trusted senders to the address book by default
The "Sync with address book" toggle under Content & Senders was off until
the user turned it on, so senders trusted on the phone stayed in the
local allow-list and never reached the "Trusted Senders" address book the
other devices read. A setting the user never chose now counts as on;
only an explicit opt-out, or a server without contacts, turns it off.
2026-10-02 14:09:34 +02:00
Linus Rath 2a1265a9ac chore: update version number to 0.1.70 0.1.70 2026-10-02 13:50:49 +02:00
Linus Rath aba5c70b3e docs: record the widget checks after the fixes on Stalwart 0.16 and 1.0 #52
Adds the phase 2 and 3 results to the verification checklist: the
corrections to phase 1 (two link failures were the test rig), every
action and link re-run on both servers, which commit fixes which cause,
the layout review and what is still open.
2026-10-02 13:45:57 +02:00
Linus Rath ccd8030af0 fix: round the widget corners less #52
Widgets had 22dp corners, rounder than the launcher's own widgets and
the app's cards. They now use 16dp, the Pixel launcher's widget radius,
on the surface, its frame and the picker previews.
2026-10-02 13:35:47 +02:00
Linus Rath ad10d4a8aa feat: show contact photos and sender logos in the widget avatars #52
The widgets drew every sender as initials, while the app and the
webmail show the contact's photo or the sender's logo first. The
widget avatars now follow the same order: an embedded contact photo,
then the logo of a non-personal sender domain when sender favicons are
on, then the initials.

A widget draws from the stored snapshot and cannot load anything while
it draws, so the pictures are fetched when the snapshot is built and
kept in it as small data URIs. Logos have a cache of their own (a week
for a logo, a day for a domain without one) that survives being
offline, and pictures over 40 KB or in a format Android cannot decode
stay out.
2026-10-02 13:34:30 +02:00
Linus Rath 9de8c56743 fix: work out avatar initials the way the webmail does, everywhere in the app
The app had six ways to turn a name into avatar initials, and none of
them was the webmail's. The webmail's Avatar skips punctuation and
symbols in front of each name part ("Sofia" Russo is SR, not "S), takes
two letters of a single name and the first letter of the address
without one. Senders, accounts (sidebar and sign-in), contacts, compose
suggestions and the home-screen widgets all use that now.
2026-10-02 13:18:29 +02:00
Linus Rath 5fcf9367a6 fix: show the widgets' current layouts in the widget picker #52
The picker previews still showed the layouts from before the fixes in
this series (Month's arrows, the Vacation switch, the old task rows and
triage header). Redrawn from the sample data at each widget's default
size, light theme, with the rounded corners and frame the launcher
draws.
2026-10-02 13:11:29 +02:00
Linus Rath bba0274a40 feat: translate the home-screen widgets into German #52 2026-10-02 13:05:14 +02:00
Linus Rath d179e1de82 fix: keep "Tap to try again" visible on a widget notice about a long task #52 2026-10-02 13:05:07 +02:00
Linus Rath 63f2e07254 feat: show more of the message on taller triage and latest-message widgets #52
The triage card kept its preview to two lines however tall it was
resized, leaving most of a large card empty, and Latest message showed
no preview at all. Both now use the room they have for the start of
the message.
2026-10-02 13:04:38 +02:00
Linus Rath 80b57161ca fix: say what an empty folder list or a finished task is on the widgets #52
Folder counts showed only its "Folders" heading when there was no
folder to list, which looks broken; it now says there are no folders to
show. A finished task past its due date still read "Overdue" on the
Tasks widget; it shows its date instead.
2026-10-02 13:04:01 +02:00
Linus Rath ee20f9a06d fix: drop widget controls that look like they do something they do not #52
The Month widget drew back and forward arrows, and the Vacation widget
an on/off switch. A widget cannot page through months or switch the
vacation reply, so both only opened the app, which the rest of the
widget already does. The arrows are gone; the switch becomes a chevron,
and the Active/Inactive pill under the title still says which state
the reply is in.
2026-10-02 13:03:19 +02:00
Linus Rath 60b92e6ef4 fix: give widget buttons a 48dp tap area #52
Most widget buttons were 32dp tall (Archive, Delete, Reply and Next on
the triage card, Accept, Maybe and Decline, Send wishes, Mail my free
times), Up next's were 36dp and the round compose/new buttons in the
headers 28-32dp, all under the 48dp a finger needs, so taps landed
beside them and did nothing.

Button and Fab take a transparent tap area around what they draw
(`slop`), and the layouts give up the same amount of padding or gap
next to them, so the widgets look the same but the buttons take a 48dp
tap.
2026-10-02 13:01:57 +02:00
Linus Rath c520e0a125 fix: keep widget text and counts inside small and translated widgets #52
Checked every widget at its smallest size with long names, big counts
and German labels. What spilled over:

- the Inbox header at two cells: the count pushed the compose button
  off the edge (search now goes at that width, the title truncates
  first, and only the unread count stays);
- a long sender pushed the triage card's time and position out;
- German button labels were cut ("Archivie", "Lösc"); the triage
  buttons now drop to icons, Archive and Delete first, when the labels
  do not fit;
- five-digit counts overflowed Unread count, Tag and Mail and next, and
  "/ 23000" wrapped a character per line; counts from 10,000 up are
  shortened (10K, 12,3K) and the total goes where it does not fit;
- "ungelesen" was cut beside the number in Mail and next and moves
  under it instead.

Every widget header now lets its title give way before the buttons on
its right.
2026-10-02 13:01:05 +02:00
Linus Rath c9a0718acf fix: keep the app from closing when a files widget opens it offline #52 2026-10-02 12:57:43 +02:00
Linus Rath c5143975db fix: finish the widget refresh after leaving the app #52
The previous fix started the refresh as the app went to the background,
but it still stopped after its first request: fetch hands every response
over through a JS timer, and React Native pauses timers while the app's
activity is in the background, so the answer was only read once the app
came back. The widgets kept the previous account after a switch and
Home, and changes made in the app reached them late.

The refresh now runs as a headless task of its own,
BulwarkWidgetRefresh, started through BulwarkWidgetRefreshService. A
running headless task keeps the timers going, and the service keeps the
process from being frozen before the widgets are redrawn. Where Android
refuses to start the service the app refreshes in place as before. A
refresh started for a change also no longer joins one that began before
it.
2026-10-02 12:01:31 +02:00
Linus Rath ae3121a746 fix: load calendar and tasks into the widgets when the app is not running #52
The calendar, tasks, files and scheduled-send parts of the widgets only
refreshed while the app's UI was running, because they reuse the app's
stores, which are bound to its JMAP client. A widget event or push that
woke the app without its UI refreshed mail only, so invitations
answered elsewhere, tasks ticked on another device and new events
stayed as the app last left them. A tick or RSVP the server refused in
that state was also never undone, since nothing reloaded those parts.

When no UI has started in the runtime, nothing else uses that client,
so the widget refresh now signs it in to the active account and loads
calendar, tasks, files and scheduled sends the way the app does. Once
the UI starts it owns the client again.
2026-10-02 12:01:20 +02:00
Linus Rath 2cec096040 fix: open a widget's event in the account it belongs to #52
Event links from the widgets named only the event (and the JMAP account
of a shared calendar), never the signed-in account it was shown for.
With another account active by then, the Calendar tab opened without
the event, or with an id both accounts use, on the other account's
event. The links now carry `appAccount`, and the app switches to that
account first, as it already did for message links.
2026-10-02 11:46:53 +02:00
Linus Rath a694751944 fix: send widget RSVPs and task ticks to the item the tap was made on #52
An RSVP or task tick named only the item's id and looked it up in the
stored snapshot when the tap arrived, then used the snapshot's active
account. Between a refresh and the redraw that follows, a tap made on
the old drawing could reach a snapshot of another account, and JMAP ids
are short per-account sequences, so the lookup could match that
account's task or event with the same id. A tick also flipped whatever
the stored state was, so a quick second tap on a drawing that had not
caught up yet undid the first.

The tap now carries what the call needs: the item's account, its server
ids and, for a tick, the state the user asked for. The change laid over
the widget data only touches items of that account. Drawings made
before this keep working through the old lookup.
2026-10-02 11:45:48 +02:00
Linus Rath 6c797f6e2c fix: refresh the widgets when the app goes to the background #52
Leaving the app scheduled the widget refresh with a zero-delay timer.
React Native pauses JS timers while the activity is in the background
(timers in the bridgeless runtime have no immediate path for a zero
delay), so the refresh only ran once the app came back or a widget task
happened to start. Switching accounts and pressing Home left the
widgets on the previous account's mail. The refresh now starts directly
from the app state change.
2026-10-02 11:42:50 +02:00
Linus Rath 7fe4a1ba27 feat: show on the widget when an action could not reach the server #52
A widget button whose JMAP call failed (offline, refused, the account's
credentials unavailable) only logged a console warning, so to the user
the button had simply done nothing, or had worked. The change is now
undone on the widget and a notice says what failed: "Couldn't archive.
Tap to try again." on the triage card, which goes back to the message,
and the same for RSVPs on the invitation card and task ticks on Tasks
and Today overview. Tapping the notice repeats the tap; any new tap or
ten minutes clear it.
2026-10-02 11:41:22 +02:00
Linus Rath 79c53e713c fix: make widget task ticks easy to hit #52
The tick on the Tasks widget was a 22dp circle, and the rest of the row
did nothing: a tap on the title or just beside the circle was lost. It
now takes the row's full height and 50dp of its width, and the rest of
the row opens the calendar. Today overview's task rows grow from 32dp
to 48dp for the same reason; the widget fits fewer of them where space
is short.
2026-10-02 11:32:50 +02:00
Linus Rath 26d0cf3ccc fix: keep a widget archive when a second tap's refresh lands before it #52
Every widget tap runs as its own headless task in the same JS runtime,
and the tasks interleave at every await. Each one loaded the stored
snapshot, edited it and saved it back, then refreshed from the server.
That went wrong in three ways:

- two taps loaded the same snapshot and the second save dropped the
  first change;
- the refresh of the first tap read the server before the second tap's
  archive landed and was stored anyway, so the message came back (seen
  on the emulator with a slow `Email/set`);
- a redraw of an older snapshot could land after a newer one, leaving
  buttons that act on a message the widget no longer shows.

The snapshot now only holds what the server last said. A button's
change is kept as a pending op next to it and laid over it when a
widget draws; it stays until a refresh that started after the server
took it has been stored (calendar and task changes only count as caught
up by a refresh that loaded calendar and tasks). A call that fails
drops its op, which also undoes an archive made offline: before, the
failed refresh kept the edited snapshot and the message stayed hidden
although it was never archived. Reads, writes and redraws of the
widget data run one at a time, and every redraw draws the latest view.
2026-10-02 11:32:38 +02:00
Linus Rath bc74d3bbac fix: keep the app from closing when a calendar widget opens it offline #52 2026-10-02 11:22:10 +02:00
Linus Rath 80f7278a6f fix: stop widget buttons from going dead when two are drawn in the same millisecond #52
react-native-android-widget registers every button's PendingIntent with
the current time in milliseconds as its request code and
FLAG_CANCEL_CURRENT. All buttons of a widget share the same action and
provider, so the request code is the only thing telling them apart: two
buttons registered within one millisecond got the same intent, and the
second registration cancelled the first. That button then did nothing
when tapped. The emulator takes 3-10 ms per button, a current phone well
under one, so which button died depended on timing.

The library is patched with patch-package (run on postinstall) to hand
out request codes that count up from the time, unique in the process
and clear of the codes an earlier process left on the launcher. The
patch keeps the library's CRLF line endings, hence `-text` for
`patches/*.patch`.
2026-10-02 11:14:40 +02:00