Commit Graph

131 Commits

Author SHA1 Message Date
Benoit Marty 72e3a7d92b Introduce ShareDataSender so that SharePresenter is not session scoped
Instead of creating a SharePresenter from the graph of another session, the
SharePresenter now gets the client of the selected session from the
MatrixClientProvider and uses an AppScope ShareDataSender to send the data.

To be able to send media from AppScope, MediaSenderRoomFactory is now bound in
AppScope and takes the MatrixClient as a parameter, and a new
MediaOptimizationConfigProviderFactory provides the media optimization config
of any session.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 15:50:08 +02:00
Benoit Marty 954eb731e7 Remove unused Gradle dependencies
Remove dependencies flagged as unused by the Dependency Analysis Gradle Plugin
(`./gradlew buildHealth`) and explicitly declared in module build files.

Removals were skipped when the dependency provides transitively something the
module (or a consumer of an `api` dependency) uses directly, as well as for
runtime-only dependencies (element-call-embedded, sentry-android,
rustls-platform-verifier). Dependencies added by the convention plugin helpers
(composeDependencies, testCommonDependencies) are left untouched.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:47:48 +02:00
Benoit Marty ed250320b3 Close the threaded timelines created by ShareLocationPresenter and DefaultMediaSender
Both created a dedicated timeline for each operation performed in a thread, and
never closed it, leaking SDK resources. Apply the same treatment as PollRepository:
replace getTimeline() with a scoped withTimeline() helper, which closes the created
timeline once the operation completes, whether it succeeds, fails or gets cancelled.

In DefaultMediaSender.sendGallery(), waiting for the upload handler is now done
inside the block, so that the timeline stays open until the upload completes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:21:14 +02:00
hayaksi1 c239190994 Merge develop into the wildcard mime type branch
Develop gained resolveImageMimeType, which reads the image header so a
share that declares a broad type is encoded as the format it really is.
Resolving the wildcard first would hand that helper a concrete image/jpeg
and make it return early, so a transparent PNG shared as image/* would be
flattened and an animated GIF would be recompressed. The header is now
read from the type the sender declared and the default subtype is applied
to whatever comes back, so the sniffing still runs and an undecodable file
no longer keeps a wildcard.

A non-image shared as image/* is therefore sent as image/jpeg rather than
image/*, which is the whole point of this branch, so the test covering
that case now expects the default subtype.
2026-08-20 14:26:57 +03:00
Benoit Marty 3ea36d54b4 Merge pull request #7451 from hayaksi1/fix/7059-relax-image-compression
Relax image compression so photos and screenshots keep more detail
2026-08-18 10:03:36 +02:00
hayyaksi 75607c73ec Keep transparency when an image is shared into the app with an imprecise mime type (#7452)
* Resolve the real image format before choosing the encoder

The upload pre-processor picks the encoder from the mime type it is
given. When an image is shared from another app that type is often
broader than the image really is, so a transparent PNG shared as a
wildcard image type is re-encoded as JPEG and its alpha channel is
flattened to black.

Read the format from the image header when the declared type is not
already a concrete PNG or JPEG, and use that to decide both whether the
image can be compressed at all and which encoder to use.

* Cover the mime type fallbacks the wildcard resolution introduced

resolveImageMimeType now decides whether an image is compressible, so two of
its outcomes had no test: a GIF shared with a wildcard type has to be
recognised and left uncompressed rather than recompressed into a still, and a
file the decoder cannot read at all has to fall back to the type it was shared
with.

The isMimeTypeImage() guard on the decoded type is dropped with them. The
BitmapFactory decoders only ever report an image type in outMimeType, so the
guard could not fire and there was no way to exercise it.
2026-08-17 21:08:48 +02:00
hayaksi1 98ef737b1d Rename withDefaultSubtype to ensureDefaultSubtype
The old name read as though the default subtype always replaced whatever the
receiver carried, when a mime type that already has a concrete subtype is
returned untouched.
2026-08-17 15:52:46 +03:00
hayaksi1 932a4d013c Relax image compression so photos keep more detail
Images are downscaled against a 640px reference, which makes inSampleSize
halve the source one step further than the documented intent: the KDoc
says images should be sent at up to 1280x1280, but a 12000x3916 photo is
sent as 3006x979. Text in screenshots and detail in photos is lost at
that size.

Raise the reference to 1280. inSampleSize is a power of two, so this
moves each image one step up rather than to an exact size.
2026-08-17 11:33:41 +03:00
hayaksi1 4c5baf8d60 Send a concrete mime type for media shared with a wildcard one
Some apps share an image with the mime type image/* rather than a
concrete one. That value was carried straight into the uploaded event, so
the message reached the room with an unusable media type and bridges
rejected it as unsupported.

Resolve a wildcard subtype to a default for its top level type before
processing the upload, reusing the mapping the download path already
applies so the SDK can pick the right extension.
2026-08-17 04:01:05 +03:00
Benoit Marty 23a346907c Add KDoc to the libraries/mediaupload API interfaces
Document MediaSender and all its members, its two factories, MediaPreProcessor,
MaxUploadSizeProvider and MediaOptimizationConfigProvider.

The MediaSender doc explains the two-step shape of the API, which the individual
signatures do not convey: pre-processing and sending are separate so a preview
can be shown in between, and the temporary files that pre-processing creates are
only released by cleanUp, whether or not anything was ultimately sent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 09:44:42 +02:00
hayaksi1 750db0c777 Drop the comments from the bitrate clamp
Review feedback: the clamp expression and the test name state the rule.
2026-08-09 15:33:37 +03:00
hayaksi1 1be87994b1 Never re-encode a video at a higher bitrate than the source
The compression config computed a target bitrate purely from the output
resolution and frame rate, ignoring the bitrate already extracted from the
source. A clip that is small because it was efficiently encoded, rather
than because it is short, was therefore re-encoded upwards: the upload got
slower and the resulting file was larger than the original.

The target is now clamped to the source bitrate when that is known.
METADATA_KEY_BITRATE is the container total, video plus audio, so it is a
safe upper bound for the video track alone. An unknown or zero bitrate
still falls back to the calculated value.

Relates to https://github.com/element-hq/element-x-android/issues/4191
2026-08-09 04:09:24 +03:00
Benoit Marty 6124d99273 Merge pull request #7020 from bxdxnn/misc/svg-support
Add full SVG support
2026-07-08 10:57:12 +02:00
bxdxnn 4fd0be1fe5 Review 2026-07-07 16:46:42 +00:00
bxdxnn 87aff40b26 Support gallery messages (#6519)
* Support gallery messages

* Address review

* Remove unused function

* Fix indent

* Add "Gallery" message prefix

* Support galleries for image editing

* Fix tapping on a tile opens the last item

* Fix overflow count

* Add caption editing to gallery messages

* Use the new gallery string for prefix

* Lock sending galleries behind a feature flag

* Fix detekts

* Fix

* Ensure image edition is saved when navigating

* Fix sending media broken on edited gallery.

* Fix tests

* Order imports

* Remove unused parameters.

* Fix copyright header of new files.

* Fix Konsist test

* Extract new previews to a dedicated file.

* Sync strings

* Remove unused import

* Update screenshots

* Trigger CI

* Remove parameters with default value.

* More cleanup

* Restore sendAsFile behavior.

* Improve Preview.

* Improve Preview.

* Improve Preview.

* Fix gallery sending cancel and retry issue

* Ensure any previous job is cancelled.

* Fix issue in summary message

* Gallery feature is disabled by default.

* Kotlin convention

* Remove useless parenthesis

* Update screenshots

* Fix test

* List -> ImmutableList

* Remove useless code.

* Render formatted caption for attachment list.

* Replace set of Booleans by an enum

* Remove unused model for individual caption in a gallery Event.

* Fix tests

* Fix tests

* Rework MediaViewer entry point.

And ensure that the clicked image from the gallery is displayed first.

* Ensure gallery item can be click in the pinned message list
Improve the gallery item click handling code.

* Improve code and fix separator color
Closes #7101

* React on attachment item click
Improve code

* Improve code and support 0 items in gallery.

* Fix click on attachment item not rendering anything.

---------

Co-authored-by: Benoit Marty <benoitm@element.io>
Co-authored-by: Benoit Marty <benoit@matrix.org>
Co-authored-by: ElementBot <android@element.io>
2026-06-26 16:00:56 +02:00
bxdxnn c4c0c482f0 Address review 2026-06-23 14:02:15 +00:00
Benoit Marty c430877d87 Ensure Robolectric test are run using API 36 until the library support API 37. 2026-06-18 12:18:32 +02:00
bxdxnn 5b53686c3e Add full SVG support 2026-06-14 10:06:17 +00:00
bxdxnn 88d419c03f Revert "Send SVG images as files (#4595)" 2026-06-14 10:06:17 +00:00
cizra a33d717aa0 Don't compress images sent through the Files attachment picker (#6755)
* Don't compress images sent through the Files attachment picker

Images and videos picked through the "Attachment" picker are now
uploaded without re-encoding, regardless of the "Optimize media quality"
setting. The gallery and camera pickers keep the existing behaviour,
matching what Element Web/Desktop and most other messengers do.

Fixes #6365

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Make sure we select the right video preset for sending as file

Wait for the video size estimations to be calculated before preprocessing the video file

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
2026-05-20 17:19:08 +02:00
Benoit Marty 4d0be69b4c In the module :libraries:matrix.api, change the dependencies to:
- libraries.sessionStorage.api
- projects.libraries.architecture
from `api` to `implementation`.

Modules who need `:libraries:matrix.api` do not necessarily need to use the session storage api.
2026-04-30 09:52:10 +02:00
Gianluca Iavicoli 523ede744a Fix portrait image metadata when uploading without media optimization (#6362)
* fix(media): preserve image orientation metadata without optimization

* style: linting
2026-04-08 11:01:54 +02:00
Jorge Martin Espinosa 23f105442e Remove all video metadata (#6224)
Use `InAppMp4Muxer` with a custom metadata provider that removes all metadata entries
2026-02-18 16:51:44 +00:00
Jorge Martin Espinosa 8e3e0951e7 Use the right video preset when sharing videos (#5892) 2025-12-16 16:40:44 +01:00
Benoit Marty 24e3705671 run command ./gradlew ktlintFormat :) 2025-12-04 10:18:19 +01:00
Benoit Marty 8285478688 API modules do not need to setup dependency injection.
Move the implementation to the impl modules.
2025-11-12 19:05:39 +01:00
Benoit Marty eb31505dc7 Copyright: Add Element Creations Ltd. copyright 2025-11-10 11:05:05 +01:00
Benoit Marty f86431e663 Copyright: Add final period 2025-11-10 10:13:41 +01:00
Benoit Marty c4e0370f41 Copyright: run command uv run license-editor --repository ../element-x-android 2025-11-10 10:09:26 +01:00
Benoit Marty 263e761907 Remove @Inject, not necessary anymore when class is annotated with @ContributesBinding 2025-10-22 18:52:37 +02:00
Benoit Marty b6d23b2508 Remove unused dependency on javax.inject:javax.inject 2025-10-02 10:25:54 +02:00
renovate[bot] 5b9ce7a210 Update metro to v0.6.7 (#5416)
* Update metro to v0.6.7

* Replace `@Inject` with `@AssistedInject` where needed

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
2025-09-26 08:16:36 +00:00
Benoit Marty a575019760 Use DependencyHandlerScope.testCommonDependencies() 2025-09-16 14:38:31 +02:00
Jorge Martín 4ab62a6a38 Redo DI gradle setup code 2025-09-04 16:49:20 +02:00
Jorge Martín 29a97fc0d9 Fix lint issues and restore commented out code 2025-09-04 16:49:18 +02:00
Jorge Martín b76a71ebf5 Start migrating Anvil KSP to Metro 2025-09-04 16:48:34 +02:00
Jorge Martin Espinosa fa755e5b75 Use variable bitrate mode when transcoding to ensure compatibility with old devices (#5223)
* Use variable bitrate mode when transcoding

This should be compatible with more devices that may lack the needed codecs to properly encode using constant bitrate mode (CBR).

* Fix video output size (again)
2025-08-26 10:41:07 +02:00
Jorge Martin Espinosa 6e6440940f Add extra logs for sending media (#5218)
* Add extra logs for sending media

We have some issues with stuck media uploads that could use some logs to understand where the process gets stuck.

* Fix mocks for test
2025-08-26 07:02:15 +00:00
Jorge Martin Espinosa 35928e3630 Threads - first iteration (#5165)
* Initial threads support: parse `ThreadSummary`.

Replace several `isThreaded` values with `EventThreadInfo`, which contains the info about the event either being the root of a thread or part of it.

* Add `Threaded` timeline mode

* Add a `liveTimeline` parameter to `TimelineController`'s  constructor. This way we can customise which timeline will be used as the 'live' one. Also add `@LiveTimeline` DI qualifier for the actual live timeline of the room.

* Create `ThreadedMessagesNode`. Allow opening a thread in a separate screen.

* Add the callbacks for the list menu actions - even if they're the wrong ones and will send the data to the room instead

* Send attachments and location in threads

* Fix polls in threads, add support for sending voice messages in threads

* Display thread summaries only when the feature flag is enabled

* Use 'Reply' instead of 'Reply in thread' when in threaded timeline mode

* Remove incorrect usage of `Timeline` in `MessageComposerPresenter`. This led to replies to threaded events not appearing as actual replies.

---------

Co-authored-by: ElementBot <android@element.io>
2025-08-19 13:35:48 +00:00
renovate[bot] 90ad9616c0 Update dependency org.matrix.rustcomponents:sdk-android to v25.8.18 (#5182)
* Update dependency org.matrix.rustcomponents:sdk-android to v25.8.18

* Fix broken API changes:
- The send queue usage is now mandatory.
- The media upload progress now comes back in the send queue state (this still hasn't been applied to the UI in the timeline).

* Update screenshots
---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
Co-authored-by: ElementBot <android@element.io>
2025-08-18 16:41:14 +02:00
Jorge Martin Espinosa adc61b3826 Add media file limit size warning and media quality selection (#5131)
* Add `VideoCompressorPreset` enum

This represents the different compression presets used for processing videos before uploading them

* Add `VideoCompressorHelper` util class to calculate the scaled output size of the video given an input size and its optimal bitrate

Also add `MediaOptimizationConfig` which will be used to decide how to apply compression in `MediaPreProcessor`

* Add `RustMatrixClient.getMaxFileUploadSize()` function and `MaxUploadSizeProvider` so we can import only this functionality into other components

* Try preloading the max file upload size the first time we get network connectivity - it's a best effort

This should help ensure we'll have this value available later, even if we still need to load it asynchronously.

* Split the `compressMedia` preference into `compressImages` and `compressMediaPreset`

* Modify the media processing parts to use the new classes and utils

* Add `MediaOptimizationSelectorPresenter`, which will retrieve the compression values and the max file upload size, also estimating the compressed video file sizes if needed.

* Add a feature flag to allow selecting the media upload quality per upload

* Integrate the previous changes with the attachments preview screen

Add strings from localazy too.

* Adapt the rest of the app calls to upload media to using the media optimization configs

* Allow modifying the default compression values in advanced settings, based on the feature flag value

* Pass the `fileSize` in `MediaUploadInfo` too, to be able to check it against the `maxUploadSize`

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
2025-08-11 17:22:46 +02:00
renovate[bot] 040fde7f22 Update dependency org.matrix.rustcomponents:sdk-android to v25.7.23 (#5073)
* Update dependency org.matrix.rustcomponents:sdk-android to v25.7.23

* Adapt to SDK changes:

- Add 'creator' role, adapt existing logic to it.
- Remove `ReplyParameters`, replace with `EventId` where possible.
- Fix changes in OIDC auth methods.
- Add more join rules.

* Make sure both creators and users with power level >= 150 are displayed as 'owners' in the room member list.

* Don't close the roles and permissions screen if the user is a creator

* Use `MediaPreviewValue.DEFAULT` for `MediaPreviewConfig.DEFAULT` too

* Improve APIs around checking roles and power levels:
    - Ensure `RoomInfo.RoomPowerLevels.users` can't be directly used to check power levels since it can't check the power levels for creators.
    - Add a few helper functions to handle actions that relied on the previous `users` property, and docs to explain their usages.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
2025-07-24 11:58:30 +02:00
Jorge Martin Espinosa 33aa7a914f Calculate video output size taking into account portrait mode (#5068) 2025-07-23 14:19:08 +02:00
Jorge Martin Espinosa d53457ec66 Make sure we clean up the pre-processed and uploaded media (#5039)
* Add `MediaSender.cleanUp()` and `MediaPreProcessor.cleanUp()` methods: this will remove the temporary files created when pre-processing media before sending them.

* Make sure we clean up also the previous temporary media.

* Fix the condition for the custom back handler in the attachments preview screen

* Tests: check the clean up is performed when needed
2025-07-23 08:55:20 +02:00
Jorge Martin Espinosa e8154ec19d Replace video transcoder with Media3 Transformer (#5018) 2025-07-15 11:08:56 +02:00
Jorge Martin Espinosa 9994562e60 Keep video rotation metadata when transcoding (#5008) 2025-07-10 15:02:42 +00:00
Benoit Marty 06a551c388 Fix test regression on API 33. 2025-06-30 17:01:01 +02:00
Jorge Martin Espinosa efdc10e60a Add catchingExceptions method to replace runCatching (#4797)
- Add `runCatchingExceptions` and `mapCatchingExceptions` to replace `runCatching` and `mapCatching`.
- Make `tryOrNull { ... }` catch only exceptions too.
- Apply the changes to the whole project.
- Add new Rust fakes for tests to handle the code that's now unblocked - previously it just threw an `UnsatisfiedLinkError` which we ignored.
- Add a new `detekt-rules` project with a `RunCatchingRule` to prevent `runCatching` and `mapCatching` usages.
2025-06-04 09:02:26 +02:00
Benoit Marty 4572419ed2 Stronger lambda error (#4771)
* Make sure lambdaError() make the test fail in all circumstances.

* Fix existing errors on tests.

* Uniformize the way we are creating class under test.

* Cleanup

* Fix typo

* Fix failing test after rebase.
2025-05-27 15:32:09 +00:00
Benoit Marty 8d115213cc Reduce API of JoinedRoom, caller must use the Timeline API from liveTimeline instead. (#4731)
This removes lots of boilerplate code.
2025-05-20 09:07:43 +02:00