* fix: stabilize ROCm MIGraphX inference
Serialize MIGraphX session runs so lazy compiles cannot overlap within a worker.
Use a fixed face-recognition batch size for MIGraphX to avoid compiling a new program for each detected face count.
* fix(ml): increase ROCm worker timeout
* fix(ml): narrow MIGraphX compile locking
* docs: format environment variables table
* docs: apply prettier to environment variables table
* fix(mobile): preserve zoom level when new images load in asset viewer
* fix(mobile): use actual child size for live photo
* revert fixes
* fix(mobile): keep zoom consistent when scale boundaries change
* fix(mobile): simplify scale handling in photo_view_core.dart
* feat(shared-link): enhance shared link UI and functionality with new expiry options and improved layout
* rebase & cleanup
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
* add bulk_tag_assets_action_button to general_bottom_sheet.widget
include create tag tile in 'Add Tags' action modal
* follow provider -> svc -> repo pattern for tags
* rebase and cleanup
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
* feat(mobile): slideshow view
* move slideshow settings to metadata store
* remove watch in initState
* wrap progress bar in safearea
* show slideshow button on remote albums
* fix crash on unknown assets
* always show slideshow option
* add zoom effect
* add padding to slideshow settings
* chore: styling tweak
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
When the user pops back from the asset viewer mid-flight, the hero
animation can fire its status listener after _ThumbnailTileState has
been disposed. setState then throws a null check on State._element.
Guard the listener with `if (!mounted) return;` — same pattern as
#28300 in the album sync action.
The hybrid added in onReadCompleted reuses Cronet's ByteBuffer between
reads to save a JNI wrap call when no grow is needed. That reuse breaks
advance() — Cronet's position() is cumulative across reads, so the same
K bytes get counted on every subsequent iteration. b.offset overshoots
b.capacity, the reuse branch keeps firing on a now-empty buffer, and
request.read() throws the original IllegalArgumentException again.
Always pass a fresh wrap from wrapRemaining() so byteBuffer.position()
reflects only this iteration's bytes. Same shape as the original PR
had before the broken optimization was layered on top.
CronetImageFetcher sized the response buffer from Content-Length, which is
the compressed wire size. Cronet auto-decompresses gzip/br responses and
writes decompressed bytes into the buffer, exceeding it and throwing
IllegalArgumentException: ByteBuffer is already full on the next read. Use
the growable path; Content-Length becomes an initial alloc hint only,
capped at 128 MB so an untrusted server can't overflow Int.MAX_VALUE or
OOM us upfront. Reuse Cronet's ByteBuffer between reads when no grow is
needed.