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>
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>
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>
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.
* 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.
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.
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.
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.
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>
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
* 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>
* 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>
- 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.
* 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>
* 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)
* 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
* 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>
* 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>
* 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>
* 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>
* 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
- 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.
* 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.