Add support for embedded Avatars#7
Conversation
📝 WalkthroughWalkthroughAdds a Flutter logo pipeline with parsed logo specifications, deterministic gradient avatars, curated anonymous icon assets, a ChangesLogo rendering pipeline
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ThunderIDLogo
participant resolveLogoSpec
participant AvatarLogoBadge
participant FlutterAssets
Client->>ThunderIDLogo: provide logo spec and fallback seed
ThunderIDLogo->>resolveLogoSpec: parse and resolve spec
resolveLogoSpec-->>ThunderIDLogo: return EmojiLogo, AvatarLogo, AssetLogo, or UrlLogo
ThunderIDLogo->>AvatarLogoBadge: render resolved avatar
ThunderIDLogo->>FlutterAssets: load resolved bundled asset
FlutterAssets-->>ThunderIDLogo: return asset image
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
🧹 Nitpick comments (2)
lib/src/logo/curated_logo_icons.dart (1)
44-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a shared helper; consider case-insensitive matching.
anonymousAnimalAssetPathandanonymousEntityAssetPathare identical apart from the list and path segment. Also, both rely on thecontentvalue already being lowercase — if a caller ever passes mixed case, the match silently fails and falls back toUrlLogoinstead of resolving the bundled asset.♻️ Proposed refactor
+String? _resolveCuratedAsset( + List<String> names, + String category, + String name, +) { + final lower = name.toLowerCase(); + return names.contains(lower) + ? 'packages/thunderid_flutter/assets/logo-icons/$category/$lower.png' + : null; +} + -String? anonymousAnimalAssetPath(String name) => - kAnonymousAnimalNames.contains(name) - ? 'packages/thunderid_flutter/assets/logo-icons/anonymous/$name.png' - : null; +String? anonymousAnimalAssetPath(String name) => + _resolveCuratedAsset(kAnonymousAnimalNames, 'anonymous', name); ... -String? anonymousEntityAssetPath(String name) => - kAnonymousEntityNames.contains(name) - ? 'packages/thunderid_flutter/assets/logo-icons/entity/$name.png' - : null; +String? anonymousEntityAssetPath(String name) => + _resolveCuratedAsset(kAnonymousEntityNames, 'entity', name);Also applies to: 92-98
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/logo/curated_logo_icons.dart` around lines 44 - 50, Extract the duplicated lookup logic from anonymousAnimalAssetPath and anonymousEntityAssetPath into a shared helper parameterized by the allowed names and asset path segment. Normalize the supplied name for case-insensitive matching while preserving the existing bundled asset path and null fallback behavior.lib/src/logo/logo_spec.dart (1)
92-128: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider narrowing
AvatarLogo.variantto exclude anonymous variants.
AvatarLogo.variantis typed as the fullAvatarVariant(all 4 cases), butresolveLogoSpeconly ever constructs anAvatarLogoforoneLetter/twoLetter—anonymousAnimal/anonymousEntityalways short-circuit toAssetLogo/UrlLogo(lines 193-201). Since the constructor is public, nothing stops a caller from buildingAvatarLogo(variant: AvatarVariant.anonymousAnimal, ...)directly, whichAvatarLogoRender.forSeed(avatar_logo.dart) would silently treat liketwoLetterrather than rejecting.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/src/logo/logo_spec.dart` around lines 92 - 128, Restrict AvatarLogo.variant to a type or contract that permits only the oneLetter and twoLetter variants, and update its constructor, equality, hashCode, and dependent usages as needed. Preserve resolveLogoSpec’s existing behavior while preventing direct construction with anonymousAnimal or anonymousEntity, and ensure AvatarLogoRender.forSeed continues receiving only supported variants.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@lib/src/logo/curated_logo_icons.dart`:
- Around line 44-50: Extract the duplicated lookup logic from
anonymousAnimalAssetPath and anonymousEntityAssetPath into a shared helper
parameterized by the allowed names and asset path segment. Normalize the
supplied name for case-insensitive matching while preserving the existing
bundled asset path and null fallback behavior.
In `@lib/src/logo/logo_spec.dart`:
- Around line 92-128: Restrict AvatarLogo.variant to a type or contract that
permits only the oneLetter and twoLetter variants, and update its constructor,
equality, hashCode, and dependent usages as needed. Preserve resolveLogoSpec’s
existing behavior while preventing direct construction with anonymousAnimal or
anonymousEntity, and ensure AvatarLogoRender.forSeed continues receiving only
supported variants.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b27b2899-9f86-4301-a0e0-e50dc38ae9fa
⛔ Files ignored due to path filters (156)
assets/logo-icons/anonymous/2.0x/capybara.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/chameleon.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/chinchilla.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/chipmunk.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/chupacabra.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/dingo.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/dinosaur.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/frog.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/giraffe.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/hedgehog.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/hippo.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/jackal.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/jackalope.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/mink.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/otter.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/platypus.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/quagga.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/raccoon.pngis excluded by!**/*.pngassets/logo-icons/anonymous/2.0x/skunk.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/capybara.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/chameleon.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/chinchilla.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/chipmunk.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/chupacabra.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/dingo.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/dinosaur.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/frog.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/giraffe.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/hedgehog.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/hippo.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/jackal.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/jackalope.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/mink.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/otter.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/platypus.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/quagga.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/raccoon.pngis excluded by!**/*.pngassets/logo-icons/anonymous/3.0x/skunk.pngis excluded by!**/*.pngassets/logo-icons/anonymous/capybara.pngis excluded by!**/*.pngassets/logo-icons/anonymous/chameleon.pngis excluded by!**/*.pngassets/logo-icons/anonymous/chinchilla.pngis excluded by!**/*.pngassets/logo-icons/anonymous/chipmunk.pngis excluded by!**/*.pngassets/logo-icons/anonymous/chupacabra.pngis excluded by!**/*.pngassets/logo-icons/anonymous/dingo.pngis excluded by!**/*.pngassets/logo-icons/anonymous/dinosaur.pngis excluded by!**/*.pngassets/logo-icons/anonymous/frog.pngis excluded by!**/*.pngassets/logo-icons/anonymous/giraffe.pngis excluded by!**/*.pngassets/logo-icons/anonymous/hedgehog.pngis excluded by!**/*.pngassets/logo-icons/anonymous/hippo.pngis excluded by!**/*.pngassets/logo-icons/anonymous/jackal.pngis excluded by!**/*.pngassets/logo-icons/anonymous/jackalope.pngis excluded by!**/*.pngassets/logo-icons/anonymous/mink.pngis excluded by!**/*.pngassets/logo-icons/anonymous/otter.pngis excluded by!**/*.pngassets/logo-icons/anonymous/platypus.pngis excluded by!**/*.pngassets/logo-icons/anonymous/quagga.pngis excluded by!**/*.pngassets/logo-icons/anonymous/raccoon.pngis excluded by!**/*.pngassets/logo-icons/anonymous/skunk.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/anchor.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/antenna.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/anvil.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/arch.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/bridge.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/chevron.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/circuit_node.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/compass.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/cube.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/diamond.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/dome.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/gate.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/hexagon.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/key.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/lighthouse.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/lock.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/obelisk.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/octagon.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/orbit_ring.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/parallelogram.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/pavilion.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/pentagon.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/plus_facet.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/silo.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/spiral.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/spire.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/star.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/tower.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/townhouse.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/triangle_stack.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/turbine.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/valve.pngis excluded by!**/*.pngassets/logo-icons/entity/2.0x/windmill.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/anchor.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/antenna.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/anvil.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/arch.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/bridge.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/chevron.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/circuit_node.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/compass.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/cube.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/diamond.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/dome.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/gate.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/hexagon.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/key.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/lighthouse.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/lock.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/obelisk.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/octagon.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/orbit_ring.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/parallelogram.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/pavilion.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/pentagon.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/plus_facet.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/silo.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/spiral.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/spire.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/star.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/tower.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/townhouse.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/triangle_stack.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/turbine.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/valve.pngis excluded by!**/*.pngassets/logo-icons/entity/3.0x/windmill.pngis excluded by!**/*.pngassets/logo-icons/entity/anchor.pngis excluded by!**/*.pngassets/logo-icons/entity/antenna.pngis excluded by!**/*.pngassets/logo-icons/entity/anvil.pngis excluded by!**/*.pngassets/logo-icons/entity/arch.pngis excluded by!**/*.pngassets/logo-icons/entity/bridge.pngis excluded by!**/*.pngassets/logo-icons/entity/chevron.pngis excluded by!**/*.pngassets/logo-icons/entity/circuit_node.pngis excluded by!**/*.pngassets/logo-icons/entity/compass.pngis excluded by!**/*.pngassets/logo-icons/entity/cube.pngis excluded by!**/*.pngassets/logo-icons/entity/diamond.pngis excluded by!**/*.pngassets/logo-icons/entity/dome.pngis excluded by!**/*.pngassets/logo-icons/entity/gate.pngis excluded by!**/*.pngassets/logo-icons/entity/hexagon.pngis excluded by!**/*.pngassets/logo-icons/entity/key.pngis excluded by!**/*.pngassets/logo-icons/entity/lighthouse.pngis excluded by!**/*.pngassets/logo-icons/entity/lock.pngis excluded by!**/*.pngassets/logo-icons/entity/obelisk.pngis excluded by!**/*.pngassets/logo-icons/entity/octagon.pngis excluded by!**/*.pngassets/logo-icons/entity/orbit_ring.pngis excluded by!**/*.pngassets/logo-icons/entity/parallelogram.pngis excluded by!**/*.pngassets/logo-icons/entity/pavilion.pngis excluded by!**/*.pngassets/logo-icons/entity/pentagon.pngis excluded by!**/*.pngassets/logo-icons/entity/plus_facet.pngis excluded by!**/*.pngassets/logo-icons/entity/silo.pngis excluded by!**/*.pngassets/logo-icons/entity/spiral.pngis excluded by!**/*.pngassets/logo-icons/entity/spire.pngis excluded by!**/*.pngassets/logo-icons/entity/star.pngis excluded by!**/*.pngassets/logo-icons/entity/tower.pngis excluded by!**/*.pngassets/logo-icons/entity/townhouse.pngis excluded by!**/*.pngassets/logo-icons/entity/triangle_stack.pngis excluded by!**/*.pngassets/logo-icons/entity/turbine.pngis excluded by!**/*.pngassets/logo-icons/entity/valve.pngis excluded by!**/*.pngassets/logo-icons/entity/windmill.pngis excluded by!**/*.png
📒 Files selected for processing (7)
lib/src/logo/avatar_logo.dartlib/src/logo/curated_logo_icons.dartlib/src/logo/logo_spec.dartlib/src/logo/thunderid_logo.dartlib/thunderid_flutter.dartpubspec.yamltest/logo_test.dart
Purpose
Adds a
logomodule and aThunderIDLogowidget that render an application/user branding "logo spec" string —emoji:<glyph>,avatar:shape=...,variant=...,content=...,colors=...,bg=..., or a bare URL — as the appropriate Flutter widget, mirroring the same spec used by the JS and Android SDKs (see thunder-id/thunderid#4182).Approach
logo_spec.dartparses theemoji:/avatar:schemes into a sealedLogoSpecresult (EmojiLogo/AvatarLogo/AssetLogo/UrlLogo) viaresolveLogoSpec(spec, fallbackSeedText), falling back toUrlLogofor anything unrecognized so callers can always render something.deriveAvatarContentfills in a spec'scontentfrom a fallback seed (e.g. a display name) when it's missing — initials for letter variants, a deterministic hash-picked name into the curated icon set for the anonymous variants, using the same 32-bit polynomial hash as the web SDK so a given seed picks the same icon on every platform.AvatarLogoRender/AvatarLogoBadge(avatar_logo.dart) natively render theone_letter/two_lettergradient-swatch variants, reproducing the web SDK's palette, hash-derived rotation angle, and gradient math bit-for-bit (covered by reference test vectors against known web SDK outputs).curated_logo_icons.dartlists the curatedanonymous_animal/anonymous_entitycontent keys, backed by bundled asset images underassets/logo-icons/anonymous/andassets/logo-icons/entity/(declared inpubspec.yaml), generated from the same source SVGs as the other SDKs' curated icon sets.ThunderIDLogo(thunderid_logo.dart) is the one-stop widget: it resolves the spec and renders the right thing (emoji text,AvatarLogoBadge, bundled asset image, orImage.network) so most callers never need to branch on the resolved kind themselves.lib/thunderid_flutter.dartbarrel.Related Issues
Related PRs
Checklist
breaking changelabel added.Security checks
Summary by CodeRabbit
New Features
Tests