tangem-app-android-audited/features/address-book/CLAUDE.md
2026-07-16 11:36:57 +02:00

63 lines
No EOL
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Address Book Feature
Per-wallet recipient contacts (name, avatar color, a set of `address + network (+ memo)` entries). Opened two
ways: standalone from Settings (browse/manage), and embedded in the Send screen as a recipient picker that
returns a chosen address back to Send.
## Navigation
Entry point `AddressBookComponent`; the `AddressBookOpenMode` (in `common/routing`, via `AppRoute.AddressBook`)
decides the initial screen:
- `Default` — browse/manage from Settings.
- `ContactSelection(networkId)` — from Send; a tap returns an address for that network.
- `WithContactCreation(address, networkId)` — from Send-success; opens a new contact with the address attached.
`DefaultAddressBookComponent` owns a single `childStack(AddressBookRoute)`. Children never reference each other:
`AddressBookChildFactory` builds each one and wires its callbacks to one `AddressBookClickIntents` object the
container implements.
```
AddressBookRoute
├─ List(mode) → AddressBookListModel (mode Default vs Selector only changes what a tap does)
├─ EditContact(...) → EditContactModel (+ childSlot: PortfolioSelector & AddressInfo bottom sheets)
├─ AddAddress(...) → AddAddressModel
└─ SelectNetworks → SelectNetworksModel
```
Two more components are used **outside** this stack, embedded directly in the Send screen (not reached via
`AddressBookRoute`): `AddressBookContactsBlockComponent` (the up-to-5 contacts block) and
`AddressSelectorComponent` (bottom sheet to pick one of several addresses of a contact).
## Cross-scope result delivery
Screens live in independent model scopes, so results are handed over out-of-band, not through nav args. Three
separate singleton mechanisms, each with its own contract:
- **`AddressBookResultHolder`** (`StateFlow`): AddAddress → EditContact. `ConfirmedAddress.replaces` carries the
entry being superseded in the edit-address flow (swap-in-place). Producer sets, consumer observes then
`clear()`s so it isn't re-applied on resubscription.
- **`SelectNetworksResultHolder`** (`StateFlow`): SelectNetworks → AddAddress, same produce→observe→`clear()`.
- **`DefaultContactSelectionTrigger`** (implements `ContactSelectionTrigger` + `ContactSelectionListener`):
full-list pick → Send. Deliberately a **no-replay `SharedFlow`** (1-item buffer, `tryEmit`), not a holder —
a one-shot signal with nothing retained, so the picker can close even if Send isn't collecting, and there is
nothing to `clear()`.
Holders are `clear()`ed at the start of a session (`DefaultAddressBookComponent.init`, `AddAddressModel.init`)
so a leftover result from a previous session isn't replayed.
## Gotchas
**Memo prefill can't be written synchronously.** In the edit-address flow, address validation is debounced, so
right after `prefillData()` no network is selected yet and `UpdateAddressValidationTransformer` keeps the memo
field hidden and **blanks its value** (`resolveMemoField` clears `value=""` whenever no extras-supporting network
is selected). Setting the memo eagerly is therefore wiped before the field is shown.
`AddAddressModel.prefillMemoOnceVisible` waits for `memoField.isVisible` to flip true, then writes the memo once.
Preserve this ordering when touching prefill/validation (regression test: `Prefill GIVEN prefilled memo on
extras network …`).
**`resolveMemoField` clears the memo value on hide by design.** Deselecting the extras network (or changing to a
non-extras chain) intentionally blanks the memo; `validateAndConfirm` guards with `isVisible && isNotEmpty` so a
stale hidden value never leaks into a `ValidatedAddress`.
**`ContactAddressEntriesConverter` collapses entries by address and takes the first non-null memo of the group.**
There is no per-network memo — if that ever changes, this converter (and `ValidatedAddress.memo`) needs revisiting.