Skip to content

refactor(mediaviewer): replace android-gif-drawable with Coil - #6785

Open
rapterjet2004 wants to merge 1 commit into
masterfrom
remove-droidsonroids-gif-drawable
Open

rapterjet2004 wants to merge 1 commit into
masterfrom
remove-droidsonroids-gif-drawable

Conversation

@rapterjet2004

Copy link
Copy Markdown
Contributor

GIFs in the media viewer are now decoded by the app's existing Coil ImageLoader (ImageDecoderDecoder on API 28+, GifDecoder below) and shown in the same zoomable view as other images. This drops the native pl.droidsonroids.gif dependency and its verification metadata.

Assisted-by: Claude Code:claude-opus-5-5

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

GIFs in the media viewer are now decoded by the app's existing Coil
ImageLoader (ImageDecoderDecoder on API 28+, GifDecoder below) and shown
in the same zoomable view as other images. This drops the native
pl.droidsonroids.gif dependency and its verification metadata.

Assisted-by: Claude Code:claude-opus-5-5

Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
@rapterjet2004 rapterjet2004 added the 3. to review Waiting for reviews label Sep 28, 2026
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

The media viewer no longer uses a separate GIF rendering path. Non-video media uses ImagePage, which starts decoded drawables that implement Animatable and stops the displayed drawable when the PhotoView is released. The android-gif-drawable dependency and its trusted-key and checksum records are removed.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 4ee46

Prefetched animated images can continue using device resources before they are visible. The impact is bounded, but playback should be limited to the current page.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4ee46

GIFs move to the app’s existing image loader, and animation now starts for other supported image formats too. Playback is stopped when its view is released, but it is not limited to the currently selected page. The plausible impact is extra work on the viewer’s device, not a demonstrated access-control bypass.

Retained concerns

  • Low · security · inferred: Newly started animated image formats can keep animating while their pager page remains composed but is not current; stopping is tied to view release rather than page selection. The prior GIF page had a similar current-page gap, so this concern is limited to the expanded animation behavior.
Security review details

Security Blast Radius

  • inferred — The identified resource-lifetime exposure is confined to media being viewed on a client device; the inspected change does not add a cross-account or service-side execution path.

Security Findings and Attack Paths

  • inferred — When a cached animated image is composed offscreen, its animation can run until the view is released. This is a plausible local resource-consumption path, not a verified denial of service; duration and workload remain unmeasured.

Trust Boundaries and Controls

  • observed — ImagePage requests a locally cached file through the existing image loader and stops its displayed Animatable on view release. Neither animation start nor stop is conditioned on whether the pager page is current.

Resilience and Maintainability Implications

  • inferred — Release handles the displayed drawable, but the inspected update path does not explicitly stop a previous drawable before replacing it in a retained view. Whether pager view reuse makes that path consequential is unverified.

Hardening Proposals

  • proposed — Consider tying animated playback to the selected page and stopping an installed animation before drawable replacement, while retaining release-time cleanup.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: replacing the GIF dependency with Coil in the media viewer.
Description check ✅ Passed The description clearly explains the GIF handling change, the decoder behavior, and dependency removal. It includes the checklist and AI disclosure, but it omits the template's screenshots and TODO se…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8cff50d7-75b4-414e-b0ea-b4d8c010ca2b

📥 Commits

Reviewing files that changed from the base of the PR and between f704b95 and 4ee468d.

📒 Files selected for processing (3)
  • app/build.gradle.kts
  • app/src/main/java/com/nextcloud/talk/mediaviewer/activities/MediaViewerScreen.kt
  • gradle/verification-metadata.xml
💤 Files with no reviewable changes (2)
  • app/build.gradle.kts
  • gradle/verification-metadata.xml

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment on lines +365 to +372
update = { photoView ->
drawable?.let {
photoView.setImageDrawable(it)
// Animated images (GIF, animated WebP/HEIF) decoded by Coil are not started automatically
(it as? Animatable)?.start()
}
},
onRelease = { photoView -> (photoView.drawable as? Animatable)?.stop() },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '270,390p' app/src/main/java/com/nextcloud/talk/mediaviewer/activities/MediaViewerScreen.kt
rg -n 'HorizontalPager|PagerState|currentPage|beyondViewportPageCount|ImagePage\\(' app/src/main/java/com/nextcloud/talk/mediaviewer

Repository: nextcloud/talk-android

Length of output: 4472


🏁 Script executed:

#!/bin/bash
set -e
sed -n '1,280p' app/src/main/java/com/nextcloud/talk/mediaviewer/activities/MediaViewerScreen.kt
printf '\n--- relevant usages ---\n'
rg -n -e 'HorizontalPager' -e 'PagerState' -e 'currentPage' -e 'beyondViewportPageCount' -e 'ImagePage' app/src/main/java/com/nextcloud/talk/mediaviewer
printf '\n--- PR diff for target file ---\n'
git diff --unified=30 f704b95fba3c9ac6b3136df59863a011a760e7aa 4ee468d7ebe069c815b97c05d8bac24db96ded53 -- app/src/main/java/com/nextcloud/talk/mediaviewer/activities/MediaViewerScreen.kt

Repository: nextcloud/talk-android

Length of output: 25442


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- Compose dependency declarations ---'
rg -n -i -e 'compose' -e 'foundation' -e 'androidx.compose' --glob '*.gradle' --glob '*.gradle.kts' --glob 'libs.versions.toml' --glob 'gradle.properties' .
printf '%s\n' '--- local AndroidX or pager source candidates ---'
find . -path '*/build' -prune -o -type f \( -iname '*Pager*.kt' -o -iname '*Pager*.java' \) -print | head -80
printf '%s\n' '--- comparable pager lifecycle usage ---'
rg -n -C 3 -e 'onRelease\s*=' -e 'beyondViewportPageCount' -e 'HorizontalPager' --glob '*.kt' .

Repository: nextcloud/talk-android

Length of output: 9187


🌐 Web query:

official AndroidX Compose Foundation HorizontalPager default beyondViewportPageCount composition offscreen pages onRelease

💡 Result:

For AndroidX Compose Foundation `HorizontalPager`, **the default `beyondViewportPageCount` is `0`** (`PagerDefaults.BeyondViewportPageCount`). That means it doesn’t deliberately compose and lay out extra pages before or after the visible pages. ([developer.android.com](https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerDefaults?utm_source=openai))

It may still compose pages ahead during scrolling: the pager’s prefetcher can compose and lay out pages in the scroll direction independently of that setting. ([developer.android.com](https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerDefaults?utm_source=openai))

If by “onRelease” you mean what happens to offscreen pages after a gesture ends: `beyondViewportPageCount` isn’t an offscreen-retention limit. Pager is lazy, so pages outside the visible/beyond-viewport range may leave composition; prefetching during scrolling is a separate behavior. ([developer.android.com](https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/HorizontalPager.composable?utm_source=openai))

Citations:

- 1: https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerDefaults?utm_source=openai
- 2: https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/PagerDefaults?utm_source=openai
- 3: https://developer.android.com/reference/kotlin/androidx/compose/foundation/pager/HorizontalPager.composable?utm_source=openai

Stop animated drawables when the page is not current.

HorizontalPager can compose a page during prefetch before that page is visible. ImagePage starts every decoded Animatable regardless of isCurrentPage, so a prefetched animated image can consume animation resources until AndroidView.onRelease removes the page.

Pass the existing activity flag to ImagePage and stop the drawable when the page is not current.

Suggested fix
-            else -> ImagePage(localPath = localPath, onToggleControls = onToggleControls)
+            else -> ImagePage(
+                localPath = localPath,
+                isCurrentPage = isCurrentPage,
+                onToggleControls = onToggleControls
+            )
...
-private fun ImagePage(localPath: String, onToggleControls: () -> Unit) {
+private fun ImagePage(
+    localPath: String,
+    isCurrentPage: Boolean,
+    onToggleControls: () -> Unit
+) {
...
-                (it as? Animatable)?.start()
+                (it as? Animatable)?.let { animatable ->
+                    if (isCurrentPage) animatable.start() else animatable.stop()
+                }

@github-actions

Copy link
Copy Markdown
Contributor

📱 QA build

Download app-qa-debug.apk
QR code Open the QR code for this download
Commit 4ee468d
Version 6785
Available until 7 days after this build

The QA build installs alongside a released Nextcloud app, so you can keep
using your existing install while testing.

Downloading the file requires a GitHub account, so open this link on the
device you want to test on, or transfer the APK to it.

@github-actions

Copy link
Copy Markdown
Contributor

Codacy

Lint

TypemasterPR
Warnings138138
Errors1818

SpotBugs

CategoryBaseNew
Bad practice77
Correctness1111
Dodgy code4545
Internationalization33
Malicious code vulnerability33
Performance88
Security1111
Total8888

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant