Skip to content

Conversation

@Akira2206
Copy link
Contributor

Purpose / Description

Enables the loading of local images within the Deck Description (Study Options screen).

Currently, the standard Html.fromHtml implementation does not know how to resolve relative paths against the collection.media directory. This PR implements a custom image getter to locate and display these local files correctly.

Note: Remote images (http/https) are explicitly ignored to prioritize security and user privacy

Fixes

Approach

  • Created DeckDescriptionImageGetter implementing Html.ImageGetter
  • Resolves image source filenames against the collection.media directory
  • Implemented asynchronous image loading using Kotlin Coroutines
  • Applied bitmap downsampling to handle high-resolution images efficiently

How Has This Been Tested?

Manual Verification Steps:

  1. Open the "Add Note" screen and use the attachment icon to insert an image from the gallery
  2. Copy the generated <img src="..."> tag
  3. Go to the Deck Picker, long-press a deck, and select Edit description
  4. Paste the tag into the description dialog and save
  5. Opened the Study Options screen and verified the local image displays correctly

Performance Note
Tested successfully with typical usage scenarios (5-10 images in the description) with no UI lag

Automated Tests:

  • Ran ./gradlew jacocoUnitTestReport (passed)
  • Ran ./gradlew ktlintFormat (passed)
2025-12-23.at.21.38.35.mp4

Checklist

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@ShaanNarendran
Copy link
Contributor

ShaanNarendran commented Dec 23, 2025

Pretty sure the copyright header isn't the same as the one we follow i.e. it's missing the line with your name and email

Copy link
Contributor

@criticalAY criticalAY left a comment

Choose a reason for hiding this comment

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

Not the solution I expect,

  • What happens when collection is synced I don't think the media is gonna be there afterwards
  • Where are you actually saving the image, I can just clean my unused media and voila

@criticalAY criticalAY added the Needs Author Reply Waiting for a reply from the original author label Dec 24, 2025
@Akira2206
Copy link
Contributor Author

Sorry about that, I completely overlooked those points.
I admit I don't know what happens when collection is synced or about handling of unused media. My main intention with this PR was just to enable rendering of local images.
However, I feel if we need to handle saving images properly, the current process is very unintuitive. my workflow process itself included having to copy the img tag from the Note editor and discarding it later. I think fixing this properly would require UI changes (like adding "Add Image" option in the edit description dialog) and I wasn't sure if that is required or should be included in this PR.
Also sorry about the incorrect copyright header (I had copied it from the other file).
What should be the right approach here?

@Akira2206 Akira2206 force-pushed the fix-deck-description branch 3 times, most recently from 5ff72af to 5062894 Compare December 25, 2025 03:26
@Akira2206
Copy link
Contributor Author

Fixed the copyright header,
@criticalAY I checked that col.backend.checkMedia() does not scan deck descriptions for media tags.
I tested both on Anki Desktop and AnkiDroid, and both show that the image is unused. I believe this is a backend issue.

AnkiDroid:

Anki Desktop:
Screenshot 2025-12-25 081018

@Akira2206 Akira2206 requested a review from criticalAY December 25, 2025 03:50
@criticalAY
Copy link
Contributor

@Akira2206 No that is becuase it is unused you copied it from a card and then discarded that card, so it makes it unused and user is free to remove it from their device, even after sync it will be broken in Desktop or even when you will fetch it again in AnkiDroid

@Akira2206
Copy link
Contributor Author

@criticalAY I get the concern about the file being deleted. But looking at Media.kt, I don't think I can actually fix the "Unused" status from the Android side. The check is just a direct call to the backend:

fun check(): CheckMediaResponse = col.backend.checkMedia()

Since the function takes no arguments, I can't pass a list of deck description images to mark them as used. The determination of what is "Used" happens entirely inside the backend. Given that I can't modify that behavior from here, and that this change brings parity with Desktop (which renders the image even if unused), is this acceptable?

@david-allison
Copy link
Member

Media used in the deck description should have a leading _ to mark it as used

Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Great first step, thanks!!

I don't think this handles the issue you raised where a large number of images crashed the app, causing a denial of service.

Comment on lines 49 to 52
val wrapper = LevelListDrawable()
val empty = Color.TRANSPARENT.toDrawable()
wrapper.addLevel(0, 0, empty)
wrapper.setBounds(0, 0, 0, 0)
Copy link
Member

Choose a reason for hiding this comment

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

Does this match the past rendering of a https:// image?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, previously it displayed a blue square placeholder. Is the transparent behaviour fine or should I revert it to showing the square?

* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki
Copy link
Member

Choose a reason for hiding this comment

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

needs a better location

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved the file to com.ichi2.ui, hope it's correct?

@criticalAY
Copy link
Contributor

@david-allison what is the outcome for this, I am more interested in knowing how is this usable to the user? After sync? And why would we want to store image for desc. in col? if its just for loading image into the webview we have I am okay with it, but I dont actually see a real use case as of now

@david-allison
Copy link
Member

david-allison commented Dec 26, 2025

Anki Desktop compatibility. In the deck description, assuming:

  • Anki 2.41.1+ handling is not selected
  • discord_logo_color.png exists in the collection

The description:

This deck comes from Discord (???)

<img src="discord_logo_color.png">

Produces:

Screenshot 2025-12-27 at 00 59 19

@Akira2206 Akira2206 force-pushed the fix-deck-description branch from 5062894 to b088ab1 Compare December 27, 2025 01:18
@Akira2206
Copy link
Contributor Author

Great first step, thanks!!

I don't think this handles the issue you raised where a large number of images crashed the app, causing a denial of service.

Thankyou! I have made the requested changes.
Yes, I couldn't completely solve the anti-abuse/ANR issue, regarding that I could implement a hard limit on the image count (like only load first 10 images and ignore the rest). Should I do that?

Media used in the deck description should have a leading _ to mark it as used

I wasn't aware of this convention. I am assuming we rely on the user to manually rename their files with an underscore in the deck description?

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

Labels

Needs Author Reply Waiting for a reply from the original author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants