Commit Graph

2495 Commits

Author SHA1 Message Date
bmarty 5102f6b0fa Sync Strings from Localazy 2026-09-14 00:54:38 +00:00
ElementBot aced1ea64d Sync Strings (#7657)
* Sync Strings from Localazy

* Update screenshots

---------

Co-authored-by: jmartinesp <480955+jmartinesp@users.noreply.github.com>
2026-09-07 14:19:38 +02:00
Benoit Marty 55bfcac6ff Remove empty (or comment only) proguard files. 2026-09-04 11:51:59 +02:00
hayyaksi d2f540ae79 Expose the timeline item long press and swipe to reply as TalkBack actions (#7458)
* Expose the timeline item gestures as TalkBack actions

Long pressing a timeline item opens the action list, and swiping it
starts a reply. Neither gesture is reachable with a screen reader
enabled, so those two actions were unavailable to TalkBack users.

Publish them as accessibility custom actions on the event row, reusing
the existing "Message actions" and "Reply" strings. The reply action is
only offered when the user is allowed to reply, matching the swipe.

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
2026-09-02 16:58:26 +02:00
jmartinesp dfccde93c6 Sync Strings from Localazy 2026-09-02 10:07:17 +00:00
Benoit Marty 52fcc797b7 Merge pull request #7523 from hayaksi1/fix/1874-empty-formatted-body
Fall back to the message body when the formatted body has nothing to render
2026-09-01 11:37:59 +02:00
Benoit Marty 012c5a1f81 Do some renaming now that user can select multiple answers per poll 2026-08-31 15:45:14 +02:00
Benoit Marty 0ad3ab1d9d Merge pull request #6477 from bxdxnn/feat/polls-max-selections
Support multi-select polls
2026-08-31 15:26:35 +02:00
hayyaksi c6fe9786a9 Stop rescanning message bodies for emoji only on every recomposition (#7531)
Deciding whether a text message is emoji only compares the formatted body to
the plain one and runs a grapheme scan over the whole message. Nothing was
remembered, so both ran again on every recomposition of every visible message.

Remember the decision against the body and the formatted body. The preview
branch moves into the same computation, so the composable wrapper around
containsOnlyEmojis, which had a single caller and existed only to read
LocalInspectionMode, is no longer needed.
2026-08-31 12:23:01 +02:00
hayaksi1 b15125289b Merge branch 'develop' into fix/1874-empty-formatted-body 2026-08-30 02:04:57 +03:00
Benoit Marty 4d12ec3682 Merge pull request #7536 from hayaksi1/fix/3434-mention-suggestion-ranking
Rank the mention suggestions by how well they match what was typed
2026-08-28 10:39:24 +02:00
bxdxnn 84cd878069 Merge remote-tracking branch 'origin/develop' into HEAD 2026-08-27 18:40:10 +00:00
hayaksi1 e8e67dc0c5 Merge remote-tracking branch 'upstream/develop' into fix/5356-sender-name-fallback
# Conflicts:
#	features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt
2026-08-27 21:19:35 +03:00
hayaksi1 0832b7de23 Merge remote-tracking branch 'upstream/develop' into fix/7397-keep-thread-after-redaction
# Conflicts:
#	features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt
#	features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryTest.kt
#	features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt
#	libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactoryTest.kt
2026-08-27 16:27:18 +03:00
Benoit Marty d9c39e8f98 Merge pull request #7480 from bxdxnn/fix/deleted-messages-thread
Fix removed messages hiding its thread on deletion
2026-08-27 10:04:36 +02:00
hayaksi1 4290a9c3b3 Keep the formatting of a caption that is being edited
Editing the caption of a media message dropped all of its formatting: bold,
links and the rest came back as plain text, and saving wrote that plain text
over the formatted caption.

The composer was being opened on the plain caption. Editing a message body
already starts from its HTML when rich text editing is on, and the caption
path was the only one that did not - it read `captionOrNull()` and never had
the caption's HTML to read, because the timeline content only kept the
already-parsed `formattedCaption`.

Media contents now carry the caption's HTML alongside the parsed one, the
same way text contents carry `htmlBody`, and editing a caption starts from it
when rich text editing is on. The save path already sent the composer's HTML,
so nothing changes there.

Part of #6730
2026-08-25 22:03:14 +03:00
hayaksi1 2278116579 Keep the thread of a message that has been deleted
Deleting a message that headed a thread took the thread with it: the "N
replies" summary under the message disappeared and there was no way back to
the replies, which are still there.

Every branch of the timeline content mapper passes `MsgLikeContent`'s thread
information on except the redacted one, which returned a bare
`RedactedContent` and dropped it. The thread summary lives on
`MsgLikeContent` next to `kind`, so it survives the redaction; only the app
was throwing it away. `RedactedContent` now carries the thread info like the
other contents do, and `EventTimelineItem.threadInfo()` reads it.

A deleted message that still heads a thread is also left out of the "N
removed messages" group, which would otherwise hide the summary again as soon
as three deletions ended up next to each other.

Part of #7397
2026-08-25 21:52:20 +03:00
hayaksi1 e09d6e0a22 Name a sender the timeline has no profile for from the room member list
The timeline renders a message with the sender's raw Matrix ID whenever the
SDK has no profile for them, even though the room member list the same screen
already holds usually knows their display name and avatar.

The timeline item factory now falls back to the room member for a sender whose
profile is not resolved, both when the item is first built and when the member
list arrives afterwards, so the name and the avatar settle on the display name
instead of staying on the ID.
2026-08-25 15:22:32 +03:00
hayaksi1 88b3559c6b Rank the mention suggestions by how well they match what was typed
The member suggestions were emitted in whatever order the room member list
happened to be in, so a member who only matches somewhere in the middle of
their name could be offered above one whose name starts with the query.

Matches are now ordered by how well they match: display name prefix first,
then user ID localpart prefix, then any other match. The sort is stable, so
members of equal rank keep the order the member list gave them.

The @room suggestion was also matched case sensitively, so typing @Ro or
@ROOM dropped it while @ro kept it. It now matches the query the same way
the members do.
2026-08-25 14:30:09 +03:00
Benoit Marty d7310b6941 Split code into several files. 2026-08-25 10:55:38 +02:00
Benoit Marty 17213ac93e RoomMemberModerationEvents -> RoomMemberModerationEvent 2026-08-25 10:18:21 +02:00
Benoit Marty d42a93100d ThreadsListEvents -> ThreadsListEvent 2026-08-25 10:06:20 +02:00
hayaksi1 6b2e83cb63 Fall back to the message body when the formatted body has nothing to render
The HTML sanitiser in toHtmlDocument() drops every tag outside its safelist,
which does not include img. A formatted body whose only content is an image —
a custom emoji, or a bot posting an image inline — therefore cleans down to an
empty document, and because that document is not null the ?: body fallbacks in
the timeline item factory, the room list preview, the notification resolver and
plainText are all dead. The message renders as an empty bubble.

Return null from toHtmlDocument() when no text survives the sanitisation, so the
existing fallbacks take the plain text body, which for these messages carries the
alt text the sender provided.
2026-08-25 11:03:42 +03:00
bxdxnn b08224fedb Merge 2026-08-24 20:09:11 +00:00
bxdxnn 3d400bad4a Merge 2026-08-24 20:04:47 +00:00
Benoit Marty a3a0fdd0e9 Merge branch 'develop' into fix/4657-preview-param-suffix 2026-08-24 14:08:52 +02:00
bxdxnn 65a827559b Merge 2026-08-24 10:30:25 +00:00
Benoit Marty e6116d5840 Move all binding containers to a di package and normalize their method names
Every @BindingContainer now lives in a di subpackage, in a file named after
it. Containers that were declared inside a shared file (SimplePlayer.kt,
LockScreenConfig.kt, VoiceMessagePresenter.kt, TimelineItemPresenterFactories.kt,
MediaItemPresenterFactories.kt) are extracted into their own file.

Method names are made consistent too:
- @Binds functions are named bind<ConcreteParameterType>, fixing three that
  named something else (notably RoomAliasResolverBindingContainer, whose
  provider was still called providesJoinRoomPresenterFactory).
- @Provides functions are named after what they provide, and all use the
  provides prefix rather than a mix of provide and provides.

ANDROID_KEYSTORE moves back to the cryptography impl package, next to
KeyStoreSecretKeyRepository which uses it; it is not DI specific and would
otherwise be unreachable from there.

No behaviour change: Metro discovers containers via @ContributesTo and does
not care about method names, so the only call site to follow was in
DefaultVoiceRecorderTest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 11:38:11 +02:00
Benoit Marty 1018d26211 Rename all @BindingContainer types with a BindingContainer suffix
Metro's @BindingContainer replaced Dagger's @Module, but the declarations
kept the old Module suffix. Rename them (and their dedicated files) so the
type name matches the annotation, matching UserStatusBindingContainer which
already followed this convention.

No behaviour change: Metro discovers containers via @ContributesTo, so there
were only two call sites to update, both in tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 10:08:54 +02:00
hayaksi1 23005317cf Merge develop into the PreviewParam rename
The 174 commits develop gained since this branch was cut touch preview data
in three ways, and each is resolved so the flipped konsist rule still holds
across the merged tree.

Two screens the branch had renamed were deleted upstream by "Remove the
account provider search screen and homeserver resolver": the
changeaccountprovider and searchaccountprovider packages. Their deletion
wins, so ChangeAccountProviderStatePreviewParam, SearchAccountProviderStatePreviewParam
and the two views that used them go with them rather than coming back
under the new name.

Two preview classes develop added still carried the old suffix and are
renamed here, along with the `@PreviewParameter` reference in each view:
GlobalSearchStateProvider -> GlobalSearchStatePreviewParam and
SendFailureDialogStateProvider -> SendFailureDialogStatePreviewParam.

Everything else merged textually. No production class extending
PreviewParameterProvider is left with a `Provider` name, and no reference
to any of the 194 renamed names survives outside the CHANGES.md entry that
records a past pull request title.
2026-08-24 10:55:40 +03:00
Benoit Marty 4cde4b40c6 Merge pull request #6676 from bxdxnn/misc/reactions-pinned-messages
Show reactions in the pinned messages list
2026-08-24 09:37:51 +02:00
Benoit Marty eeaf0166f5 Merge pull request #7443 from hayaksi1/fix/4519-hide-verification-fallback
Do not render in-chat key verification requests in the timeline
2026-08-21 16:52:58 +02:00
Benoit Marty 586805bcfb Merge pull request #7463 from hayaksi1/fix/4460-focused-timeline-pagination
Keep a focused timeline focused until the event has been rendered
2026-08-21 16:00:41 +02:00
Benoit Marty a59997336d Suppress Large Class warning on test. 2026-08-21 10:17:45 +02:00
Benoit Marty eb37c7bd55 Update test with Claude 2026-08-21 09:54:48 +02:00
Benoit Marty 25989135a8 Add error content to Localazy 2026-08-21 09:54:47 +02:00
Benoit Marty 3f2a1d95fc Fix issues and add missing text. 2026-08-20 19:44:05 +02:00
Benoit Marty 27d3f3dabc Add preview for the new dialog 2026-08-20 19:06:07 +02:00
Benoit Marty c10e84923b Let user see an error dialog in the case a message fails to be sent.
Closes #7470
2026-08-20 19:02:02 +02:00
Jorge Martin Espinosa f2883ef9df Fix flaky test in DefaultVoiceMessageComposerPresenterTest (#7487) 2026-08-20 11:49:59 +02:00
hayyaksi 2525019ca1 Show the right image when opening one that has not finished sending (#7359)
* Show the right image when opening one that has not finished sending

* Reuse the timeline media gallery for events that have no event id
2026-08-20 11:16:13 +02:00
bxdxnn 2ff6a56e7d Use the new SDK thread info for redacted messages 2026-08-19 16:12:15 +00:00
hayyaksi 7341feeed8 Model the fake voice recorder on a cancellable recording job (#7466)
* Model the fake voice recorder on a cancellable recording job

FakeVoiceRecorder pushed a whole recording synchronously from a single
suspending call, which made it both weaker and stricter than
DefaultVoiceRecorder.

Every level emission carried the same elapsed time and the emissions were
coalesced by Molecule, so no intermediate recording state was observable and
the level-limit test had to skip half the emissions to compensate. Each level
now advances the test time source and suspends for its share of the recording
duration, so the states arrive one at a time and that magic number is gone.

Call verification moved to the constructor lambdas the rest of the repo uses,
in place of hand-rolled counters and assertCalls. The counters discarded the
argument, so nothing pinned that cancelling a recording stops it with
cancelled = true; that is now asserted. Truth is no longer needed by the
fixture module.

A second startRecord() no longer raises an error where production only warns
and returns, and stopping or deleting terminates an in-flight emission loop
the way cancelling the real recording job does, so a stale recording state can
no longer arrive after the recording has finished.

* Keep each recording's level loop to its own recording

The level loop checked a single isRecording flag, which cannot tell "this
recording was cancelled" from "a later recording has started". After a cancel
followed by a new start, the first loop resumed, saw the flag set again, and
advanced the shared test time source, so the new recording reported the elapsed
time of both. Each recording now carries an id and its loop stops as soon as
that id is no longer the active one.
2026-08-19 13:44:11 +02:00
hayyaksi ee1348e19e Give the room name in the timeline top bar a 48dp tap target (#7450)
* Give the room name in the timeline top bar a 48dp tap target

The row carrying the room avatar and name is the control that opens the
room details, but it is only as tall as its content, which is under the
48dp minimum recommended for a touch target. Constrain it to a minimum
height of 48dp. The children stay centred inside the app bar, so nothing
moves visually.

* Update screenshots

The accessibility snapshot of the timeline is the only one the change
affects: the node which opens the room details now reports a 48dp tall
bounding box. The room name, the avatar and the icons stay where they
were.
2026-08-19 12:29:24 +02:00
hayaksi1 0aa283729c Cover every branch of the forward pagination gate
Codecov reported 55.55% patch coverage on the previous commit: four of the nine
measured lines were only partially covered. No test paginates forwards in a
thread timeline, and the focus states Requested, Loading and Failure were never
live when a forward pagination arrived, so only the Success branch of
isPending() was ever evaluated both ways.

Add a test per uncovered state: a forward pagination is deferred while the focus
request is still being debounced and while the focused timeline is being
created, it is performed again once the request has failed, and it is dropped in
a thread timeline while a backward one still goes through.

The pagination recorder now also sits on the live timeline, and the presenter is
built on that same timeline. Before the detached timeline exists, a pagination
that escaped the gate would go to the live one, so without this the new guards
would pass against the unfixed presenter. The deferred cases end on the emitted
state rather than on cancelAndIgnoreRemainingEvents for the same reason: the
recorder throws from inside the presenter coroutine and cancelling swallows it.

Verified by reverting the gate: the three deferred-pagination tests then fail
with "This lambda should never be called."
2026-08-18 00:49:03 +03:00
hayyaksi cb6ac751cc Refresh the room members when a mention is started (#7438)
* Refresh the room members when a mention is started

The mention suggestions read room.membersStateFlow, which is a cache filled
only by an explicit updateMembers() call. Nothing calls it while a room is
open, so a member who joined after the room was opened never appears in the
suggestion list until the room is left and entered again.

Call updateMembers() when a mention search starts. The trigger is
distinctUntilChanged on "the current suggestion is a mention", so it fires
once per mention rather than on every keystroke, and fetchRoomMembers already
no-ops while a fetch is in flight.
2026-08-17 21:10:08 +02:00
hayaksi1 03c330ee07 Rename PreviewParameterProvider subclasses to the PreviewParam suffix
The `Provider` suffix reads as a dependency provider rather than as preview
data, and it collides with the real providers in the codebase — `StringProvider`,
`PickerProvider`, the login feature's `AccountProvider` — so the name says
nothing about which of the two a class is. `PreviewParam` says it.

195 classes are renamed, along with the 185 files named after them, the konsist
rule that enforces the convention, the presentation-class file templates and the
onboarding documentation.

This cannot be split. `Konsist.scopeFromProduction()` scans the whole project, so
flipping the assertion before every class has moved leaves the rule red, and a
rule that accepted either suffix during a transition would be exactly the kind of
compatibility layer this convention change is meant to remove.

Two judgement calls in the rule itself. The `name.endsWith("PreviewProvider")`
exclusion is dropped: its only job was to reject a redundant `Preview` before
`Provider`, and the new suffix already contains it; no class currently ends with
`PreviewProvider`, so removing it changes nothing today. And
`EditableAvatarViewUriProvider` is renamed rather than deleted from the exclusion
list even though no such class exists in this repository, since it presumably
comes from the modules overlaid for the enterprise build.

Nothing keyed off the old suffix: no snapshot filename contains a provider class
name, so no Paparazzi image changes, and the Kover variants match on `*Presenter`,
`*State` and `*ViewKt`, so coverage bounds are unaffected.
2026-08-17 17:40:33 +03:00
hayaksi1 11e3cb9b19 Keep a focused timeline focused until the event has been rendered
Opening a room from a notification asks the timeline to focus the notified
event. When that event is outside the loaded live window the controller
creates a detached timeline for it, and a detached timeline always carries a
forward loading indicator as its last chronological item. The items list is
reversed for display, so that indicator is the bottom-most row and is composed
on first layout, which unconditionally emits a forward pagination request
nobody asked for. Because a notified event sits close to the live end, that one
page reaches the end, and the controller responds by closing the detached
timeline. The live window replaces the item list while it is still anchored at
the bottom, so the user lands on the newest message and the focus request is
discarded.

Defer automatic forward pagination while a focus request is still pending. Once
the focused event has been rendered the viewport has moved off the indicator
row, so only a real scroll back to the live end switches to the live timeline,
which is what the controller's existing behaviour is for. Backward pagination
and the explicit jump-to-live action are untouched.
2026-08-17 16:20:43 +03:00
Benoit Marty a740423343 Sync strings. 2026-08-17 11:34:52 +02:00
hayaksi1 39afaa1fe4 Do not render in-chat key verification requests in the timeline
A m.key.verification.request sent from a client that supports in-chat
verification carries a plain text body for clients that do not. Element X
has no msgtype of its own for it, so it fell through to OtherMessageType
and the timeline printed that body, telling the user their client does
not support in-chat key verification right after a verification they had
just completed.

Drop the event when building the timeline items, as Element X iOS does.
2026-08-17 03:44:53 +03:00