Add contact profile pictures#373
Conversation
49272dd to
46cdd3c
Compare
There was a problem hiding this comment.
Thanks for the PR! I really like this idea. Please see the AI review below and have Claude address them. Also in the future, please make sure you're creating your branches from dev and pointing your PRs into dev as well. Thanks!
Requesting changes for one data-loss bug, then backup, test, and locale follow-ups.
Blocking: photo is cleared by everyday edits
updateContactAvatar correctly uses existing.with(avatarImageData:). Other ContactService updates rebuild a DTO from a temporary Contact(...) and omit avatarImageData, so it defaults to nil. saveContact then runs apply and overwrites the stored image.
Affected paths (unchanged by this PR, but they wipe the new field once it exists):
updateContactPreferences(ContactService.swift~463–488) — nickname / blockupdateContactOCVSettings(~513–537)setContactFavorite(~596–620)setTelemetryPermissions(~671–695)
Repro: set a photo, then save a nickname, toggle favorite, change OCV, or change telemetry flags. The photo is gone.
Note: the same OCV / favorite / telemetry rebuilds also drop isMuted and unreadMentionCount (pre-existing silent-drop pattern; worth fixing while touching those call sites).
Minimal fix: pass avatarImageData: existing.avatarImageData on every rebuild, or stop building temporary Contact(...) values and use with-style copies that always keep local-only fields.
Please add a regression test: set avatar → updateContactPreferences / setContactFavorite → assert the avatar is still present. The new set/clear test is not enough.
Also before merge
mergeBackupMetadata(PersistenceStore+BackupHelpers.swift~184–227): adoptavatarImageDatawhen local isnil(same pattern as nickname / OCV). Fresh insert is fine; merge onto an existing contact drops the backup photo.BackupIntegrationTests: encode/decode with non-nil data, legacy key absent →nil, export→import, merge onto existing.- Locales: add the five
contacts.detail.avatar.*keys to de, es, fr, it, nl, pl, ru, uk, and zh-Hans (all non-English project locales; English placeholders are fine).
Strongly recommended
- Cache decoded avatars (or list thumbnails);
UIImage(data:)inContactAvatar.bodyruns in scrolling lists format.scale = 1and off-main processing inprocessAvatarImage(default screen scale can turn “512 pt” into 1536 px)- Disable the avatar control while saving; surface an error when PhotosPicker returns
nildata - Richer VoiceOver label (name + photo state + saving)
What already looks good: local-only scope, chat-only edit, security-scoped file access with defer, picker re-arm, optional Codable for legacy backups, and with(isMuted:) / with(isFavorite:) already forward the new field.
Contacts can now have a custom profile picture set from Photos or Files, overriding the auto-generated initials avatar. See discussion: Avi0n#372 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes a data-loss bug: updateContactPreferences, updateContactOCVSettings, setContactFavorite, and setTelemetryPermissions rebuilt contacts via a temporary Contact(...) that omitted avatarImageData (and, in some cases, isMuted/unreadMentionCount), silently wiping the photo on any of those everyday edits. All four now forward every field from the existing contact. Also: - mergeBackupMetadata adopts a backup's avatarImageData when local is nil. - Added regression tests for the avatar-preservation fix, a backup encode/decode/export/import/merge round-trip test, and locale placeholder strings (contacts.detail.avatar.*) for the 8 non-English project locales. - Cache decoded avatar UIImages (NSCache) so ContactAvatar doesn't re-decode JPEG data on every scroll redraw. - Downscale off the main thread (Task.detached) and force format.scale = 1 so "512pt" doesn't balloon to 1536px at 3x screen scale. - Disable the avatar button while saving and surface an error when PhotosPicker returns nil data instead of failing silently. - Richer VoiceOver label combining contact name and saving state.
46cdd3c to
ce805a4
Compare
|
Thanks for the thorough review! Rebased onto Blocking: data-loss bug — fixed. Before merge:
Strongly recommended — all done:
All changes verified on this branch (based on current
|
CI's swiftformat --lint --strict caught two issues not covered by swiftlint: nonisolated must precede static in modifier order, and a regular // comment on a private declaration should be a doc /// comment per this repo's docComments rule.
Avi0n
left a comment
There was a problem hiding this comment.
Looks great, thank you for your contribution!
Description
Adds custom contact profile pictures, discussed in #372. Split out of the original combined PR into its own PR so it can be reviewed independently from the Nodes context-menu change (now in #374).
Contacts currently only get an auto-generated initials avatar (
ContactAvatar). This adds the ability to set a real profile picture from Photos or Files.Overview of Changes
MC1Services/Sources/MC1Services/Models/Contact.swift: addedavatarImageData: Data?to theContactSwiftData model andContactDTO, threaded through all inits,apply(_:), and thewith(...)copy helpers (also fixedwith(isMuted:)/with(isFavorite:), which previously dropped any field not explicitly listed — would have silently wiped the avatar on those calls).MC1Services/Sources/MC1Services/Services/PersistenceStore.swift: documented the new field as a v5→v6 lightweight schema migration (noVersionedSchema, matching the project's existing convention).MC1Services/Sources/MC1Services/Services/ContactService.swift: addedupdateContactAvatar(contactID:imageData:).MC1/Views/Components/ContactAvatar.swift: renders the stored image when present, falls back to the existing initials circle otherwise.MC1/Views/Contacts/ContactDetailView.swift: tapping the avatar (chat-type contacts only) opens a confirmation dialog — Choose Photo (PhotosPicker), Choose File... (.fileImporter), Remove Photo. Picked images are downscaled to a 512pt max dimension and re-encoded as JPEG before saving. Purely local metadata — not synced to the radio/mesh.Contacts.strings/ regeneratedL10n.swift(via SwiftGen).MC1Services/Tests/MC1ServicesTests/Services/ContactServiceTests.swift—updateContactAvatar sets and clears avatarImageData.The Nodes long-press "Send Message" item that was originally bundled here has been moved to #374.
Testing
xcodebuild build -project MC1.xcodeproj -scheme MC1 -destination 'platform=iOS Simulator,name=iPhone 17e,OS=26.5'— BUILD SUCCEEDED, isolated on this branch.swiftlint lint— clean, no warnings/errors.make test-app/MC1Testsrun hit a pre-existing macro-expansion compile error inMessageLinkTokenizerTests.swiftunrelated to this change — reproduced identically on a cleanmaincheckout with these changes stashed, so it's an environment/toolchain issue, not something this PR introduces.MC1ServicesTests(where the new avatar test lives) isn't wired into any Xcode scheme in this environment and I wasn't able to execute it directly; the new test follows the exact pattern of the adjacent passing tests in that file and the file parses/compiles cleanly.Tested on
Checklist
CONTRIBUTING.md