fix(contacts): adversarial-review fixes — delete UB, JPEG decode, footer clip

From an adversarial review of the avatar edit-dialog + image picker:

- HIGH: the per-row Delete icon called doDelete() (which erases from
  book.entries()) INSIDE the loop iterating that same vector — out-of-bounds
  reads / wrong rows on the confirming click. Defer it until after the loop.
- HIGH: the decode stack was compiled PNG-only (STBI_ONLY_PNG) while the image
  picker accepts .jpg/.jpeg/.bmp/.gif, so picking a JPEG (the common photo
  case) silently produced a non-loading avatar + an orphaned copy on disk.
  Enable JPEG/BMP/GIF decoders, and guard the pick: verify the source decodes
  before copying/committing (new contact_avatar_bad_image string, 8 langs).
- MED: the fixed, non-scrolling edit card floored bodyH at 260*dp, which could
  push Save/Cancel below the card on short windows — floor lowered so the
  footer always stays inside.
- LOW: the live-preview panel rounding is now dp-scaled (10*dp) to match the
  real list card it mirrors.
- LOW: re-picking the same source path after its contents changed showed a
  stale cached texture — evict the avatar texture cache entry on re-pick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:49:34 -05:00
parent e1df5ea798
commit 9649654c3c
11 changed files with 43 additions and 5 deletions

View File

@@ -1243,6 +1243,7 @@ void I18n::loadBuiltinEnglish()
strings_["contact_avatar_remove"] = "Remove";
strings_["contact_avatar_image_hint"] = "The image is copied into the app so it stays available if the original moves.";
strings_["contact_avatar_copy_failed"] = "Could not copy that image.";
strings_["contact_avatar_bad_image"] = "That image couldn't be loaded.";
strings_["contact_global_badge_tt"] = "Global contact — visible in every wallet";
strings_["address_book_added"] = "Address added to book";
strings_["address_book_count"] = "%zu addresses saved";