Country Picker State
Owns the picker's transient UI state: sheet visibility, search query, region filter, and the pending selection.
What this class deliberately does not do
It holds no business logic and reaches no repository of the host's. It does not own the confirmed selection either — that is hoisted to the caller, which is what makes com.ezzy.ccp.countrypicker.ui.CountrySelector a controlled component. The state holder knows about the confirmed value only so it can render it and diff against it.
Pending vs. confirmed
In multi-select, pendingSelection is scratch space. confirmSelection is the only path by which it reaches the caller; dismiss and resetPendingSelection discard it. A caller's set is never mutated behind their back, so "Cancel" is genuinely a cancel rather than a visual one.
Detection precedence
applyDetection refuses to overwrite a selection the user made themselves (hasExplicitSelection). A detected country is a guess; a tapped country is a statement. Once the user has stated something, a late-arriving network lookup must not silently contradict it.
Create via rememberCountryPickerState, which also wires restoration and the recents store.
Properties
All countries from the data source, before any availability or region filtering.
Regions offered as chips, ordered by CountryRegion declaration order.
Whether Confirm should be enabled in multi-select.
The active config. Settable so a recomposition with a changed config updates behaviour without discarding the user's in-progress search or pending selection.
The caller's confirmed selection, mirrored here for rendering and diffing.
The latest detection outcome.
The detection source, when the current selection came from detection rather than the user.
A transient message for the user — currently only "you can select at most N countries".
True once the user has picked a country in this state's lifetime.
True when there is nothing to show because everything was filtered out by configuration.
True when a query matched nothing but countries were available to match.
Whether the search field currently holds focus, which drives the filled→outlined morph.
True when a query long enough to filter is active.
Whether the picker sheet is showing.
Load state of the country list.
Ranked search results over availableCountries.
The staged selection. In single-select this holds at most one country.
The countries worth one tap: the detected country, then recents, then the host's suggestions, each listed once. Disabled countries are left out — a shortcut that cannot be taken is noise.
Whether recent and suggested countries appear as sections at the top of the list (true), or are surfaced somewhere else — the quick-pick carousel — and left in place in the full list.
Recent ISO codes, most recent first, supplied by the recents store.
How many available countries each region holds, for the counts on the region filters.
Number of countries matching the current query and region.
The raw query the user typed. Never persisted anywhere.
"Did you mean …" candidates: countries within a small edit distance of the query, populated only when the query matched nothing — so a typo like "Kenia" offers Kenya instead of a dead end.
The sections to render, grouped and deduplicated.
The active region filter, or null for "All".
Functions
Clears the query and restores the region filter that was active before searching.
Promotes the pending selection to confirmed and returns it, or null when the bounds are not satisfied.
Dismisses the transient feedback message.
True when country is shown but not selectable.
Marks the selection as user-made, e.g. when the host restores a saved choice as explicit.
Records whether the search field holds focus.
Clears the pending selection. The sheet's "Reset" action.
Selects a region filter, or null for "All". Does not touch the selection.
Toggles country in the pending selection.
Updates the query.