Skip to content

crm_sdk: the facade and setup

What package:crm_sdk/crm_sdk.dart exports to set the package up and talk to it: the facade, its configuration and sign-in, the host’s hooks, links, problems and the stores.

import 'package:crm_sdk/crm_sdk.dart';
NameWhat it is
CrmPurrCRM in a product.
CrmAppUnavailablePurrCRM refused because the app is archived or deleted (app_unavailable): final.
CrmAuthHow PurrCRM knows who the guest is: a token from the host, which signs its guests in itself, or, for an app whose staff turned on unverified sign-in, the guest’s id alone.
CrmConfigThe product, as PurrCRM needs to know it.
CrmContractErrorPurrCRM answered with something the package cannot read: a body that is not JSON, or a model that failed to decode it.
CrmDestinationOne of PurrCRM’s pages: a place, and for a conversation, a ticket, a collection or an article, which one (id, an id or a slug).
CrmEventcrmEvents as a type.
CrmFailureWhy a request did not succeed.
CrmFeaturecrmFeatures as a type.
CrmFetchedTokenA token the host’s backend signed for the guest, and when it expires.
CrmFetchedTokensCrmTokens over the host’s backend: fetch asks it for a token for the guest (null while nobody is signed in), and the token is kept until a little before it expires (earlier, a minute unless told), so that a request never leaves with one about to run out.
CrmHostThe host’s parts.
CrmLinkA place in the host a link may go, as the host declares it (CrmHost.links): its name for PurrCRM’s staff (“Offer page”), its path in the host’s link space, where a part in braces is filled in by whoever writes the link (/offers/{offer_id}, /menu/all?category= {category}), and optionally its page on the web (webUrl: where the link goes on a phone without the app, which may name the same parts) and its screen’s name (screen, as the host tells it to Crm.screen).
CrmLinkSpaceThe links that open the app, and the paths in it they name.
CrmLinksPurrCRM’s link space: the paths of its pages, and which page a link names.
CrmLocalizationsCallers can lookup localized strings with an instance of CrmLocalizations returned by CrmLocalizations.of(context).
CrmNavigatorTakes the guest to one of PurrCRM’s pages, in the host’s own routes: GoRouterCrmNavigator (lib/go_router.dart) for a host that routes with go_router, NavigatorCrmNavigator for one that pushes routes itself.
CrmNoticeOne notice in the guest’s inbox: what PurrCRM pushed them, kept to read again.
CrmNoticesThe guest’s inbox as the package has it.
CrmOfflinePurrCRM could not be reached, or did not answer in time.
CrmOrderRefAn app’s order reference: what its guests call an order’s reference, the pattern one matches, an example the bot shows, and whether an answer is matched and kept in capitals.
CrmPictureOne picture the guest picked: its bytes, its file name, and its content type (” when the phone did not say).
CrmPicturePickerShows the phone’s picker for up to most pictures: the ones picked, none when the guest changed their mind.
CrmPlaceWhich of PurrCRM’s pages a CrmDestination is.
CrmPlatformcrmPlatforms as a type.
CrmProblemPurrCRM answered, and said no: a problem+json body, or, for a response without one, a problem made up from its status.
CrmPublicAppAn app’s public face, which PurrCRM answers anyone who names its key (GET /public/v1/apps/{key}), before a guest signs in: how it is named and coloured, what it has switched on, its currency and order reference, and the SDK it needs.
CrmPublicBrandAn app’s brand as its guests see it: its name (never empty), its accent (#rrggbb, or empty for the product’s own), its logo and where its guests may write.
CrmPushPlatformcrmPushPlatforms as a type.
CrmPushResultWhat a push that arrived while the product was open was, to PurrCRM.
CrmPushTypecrmPushTypes as a type.
CrmScopeProvides crm to the widgets below it (CrmScope.of): PurrCRM’s screens, its CrmOverlay and its tiles.
CrmSessionA signed-in guest, as the host’s session has them: their id with the host’s sign-in, the name they chose, whether they turned “Help improve …” off, and, when the host holds some of PurrCRM’s parts back from them, which of the app’s it lets them have.
CrmStoreWhere PurrCRM keeps what it keeps on the phone, every key under one namespace prefix.
CrmStringsDelegateThe package’s strings, for a MaterialApp’s localizationsDelegates in place of CrmLocalizations.delegate, with what a host may change:
CrmTokensWhere the transport gets the guest token it sends.
MemoryCrmStoreA CrmStore in memory, for tests and a build with no phone to keep anything on.
crmLinksDigestThe digest of what a host declares (CrmLinks), which a check-in sends so that PurrCRM asks for them only when they changed: the links sorted by path then name (as UTF-8 bytes), each its four fields joined by U+001F and ended by U+001E, SHA-256, the first 16 bytes as lowercase hex.
crmSdkVersionThe version of crm_sdk this is.
crmSupportedLocalesThe languages the package’s own strings are written in: one for each ARB file in lib/l10n, as flutter gen-l10n lists them.
pictureTypeOfThe content type a picture’s file name says, or ” for one that is not a picture the messenger takes.
class Crm

PurrCRM in a product.

Crm({required CrmConfig config, required CrmAuth auth, CrmHost host = const CrmHost()})
final CrmAuth auth

Where the guest’s token comes from.

final CrmConfig config

The product, as PurrCRM knows it.

CrmHost host

The host’s parts. A host that makes the facade before some of its parts exist (it makes it with its services, before its router, say) hands them in once they do, and takes them back as they go: crm.host = crm.host.copyWith(navigator: …).

ValueListenable<Set<CrmFeature>> get appFeatures

PurrCRM’s parts the app has on, whoever is signed in: publicApp’s features, which until PurrCRM answers this start are the ones kept on the phone from the last start that had an answer; on a phone that has never had one, the host’s CrmConfig.initialFeatures until PurrCRM answers. None once PurrCRM says the key is no app’s. It notifies as they change, for a host that shows its own ways in to them.

CrmSession? get currentSession

The signed-in guest, as the host last said; null for nobody.

Set<CrmFeature> get features

PurrCRM’s parts the guest signed in has: those the app has on (appFeatures), narrowed to those the host names in the session when it names any (CrmSession.features). None for nobody.

bool get isTestMode

Whether this build is in test mode: its app key is a live app’s test test app’s (app_test_…, a staging build’s), whose guests, events and messages PurrCRM keeps apart from the live app’s. The app’s public face says so once PurrCRM has answered at start (publicApp, CrmPublicApp.mode); until then, and with a CRM that cannot be reached, the key’s own prefix does, which PurrCRM gives only to test apps.

publicApp notifies when the answer arrives, for a widget that shows it (CrmTestModeBanner).

ValueListenable<CrmNotices> get notices

The guest’s inbox of notices: what PurrCRM pushed them, kept to read again, with its unread count for the host’s bell. Empty for nobody, and for an app PurrCRM does not deliver for itself.

String get productName

The product’s name as the guest knows it, which PurrCRM’s words say where they name the product: the config’s CrmConfig.productName, else the app’s brand’s name as PurrCRM answered it (publicApp), else nothing.

ValueListenable<CrmPublicApp?> get publicApp

The app’s public face, as PurrCRM answered it at start for the config’s app key: its brand, features, currency, order reference and the SDK it needs. Until it answers, the one kept on the phone from the last start that had an answer for the same key; null before either, and once PurrCRM says the key is no app’s.

ValueListenable<bool> get ready

Whether the guest is past the host’s own steps after signing in, as the host last said (setReady): the CrmOverlay shows nothing until they are.

ValueListenable<bool> get testPhone

Whether the guest is one of staff’s test phones, as the last check-in said.

ValueListenable<int> get unread

The messenger’s count of unread replies: the badge.

Future<int?> checkNow()

Checks in at once, whatever the last check-in’s hint: a test phone asking for its tests. Answers how many arrived, or null when the check-in did not get through or the guest has no in-app messages.

Future<void> dispose()

Lets go of everything: the lifecycle, the cubits, and the network clients it made. What is waiting to be sent stays on the phone for next time.

Future<bool> forgetPushToken()

Lets go of the device’s push token for the guest signed in, so that they are pushed nothing more here: call it before the host signs the guest out, while their token still works (the facade tries again as the session ends, when it may no longer). The token is kept, and is registered for the next guest who signs in. Answers whether PurrCRM took it.

Future<bool> handleLink(Uri link)

Whether link, a link the phone handed the host (a universal link, an app link: from the app_links package, a platform channel, a scanned QR code, wherever the host hears of them), is PurrCRM’s: one under the app’s link base (CrmPublicApp.linkBase: https://<the CRM's host>/l/<the app's link name>/<path>, or https://<a host of the app's own>/<path>), or, before the app’s public face has answered, any under /l/ on PurrCRM’s own host. Any other link is the host’s to follow, and the answer is false.

PurrCRM then takes the guest where it goes once a guest is signed in and ready (setReady), as it does a tapped push: one of PurrCRM’s pages through the navigator, an in-app message its link opens (showMessage), or a place in the host through CrmHost.followable and CrmHost.openInApp. An email’s link (one with a click in it) is counted as the email’s click, and every one is a link_opened event. True even when the path goes nowhere: the link was PurrCRM’s.

CrmPushResult handlePushArrival(Map<String, Object?> data)

A push arrived while the product was open, with data as the push carried it. A support_reply reads the messenger’s count again (a conversation that is open has the reply already, by its wait); a crm_message from staff makes a test phone ask for its tests at once, whatever the last check-in’s hint, and anyone else check in as they would anyway. The host keeps its own notice for either: the answer says whether it is a reply already read on the screen, or a silent crm_message (a nudge to check in), which is no notice at all.

bool handlePushTap(Map<String, Object?> data)

A push was tapped, with data as the push carried it: push_opened, whatever the push (PurrCRM’s or the product’s own). Answers whether PurrCRM takes the guest where it goes: a support_reply or a crm_message naming one of PurrCRM’s pages, which opens once the guest is ready (setReady). Any other tap is the host’s to follow.

bool has(CrmFeature feature)

Whether the guest signed in has feature (features).

Future<bool> markNoticesRead(List<String> ids, {bool all = false})

Marks the notices ids read (those the guest has seen), or every one when all: at once on the phone, and then PurrCRM’s count. Answers whether PurrCRM took it. Each newly read notice is a notice_read event PurrCRM records itself.

bool mayOpen(CrmDestination where)

Whether the guest signed in may open where: its feature is on for them, or it is behind none.

Future<void> moreNotices()

Reads the next page of older notices onto the inbox.

Future<bool?> offersByPush()

The guest’s consent to offers by push, as PurrCRM keeps it, or null when it could not be read.

Future<bool> open(CrmDestination where)

Takes the guest to where, through the host’s navigator, in place of where they are (a tapped push): whether it could. An in-app message (CrmPlace.message) is shown over the page instead (showMessage).

bool openLink(String link)

Follows link, from a message, a reply or an article: one of the CRM’s pages is shown over the page through the navigator, a place in the host goes through its allow-list (CrmHost.followable) and CrmHost.openInApp, and an https page opens in the browser. Answers whether it went anywhere; a link the content’s rules do not allow goes nowhere.

Future<void> permissionChanged(String permission, {required bool? allowed})

The guest’s answer about permission (‘push’ or ‘location’) is now allowed, or null for not asked yet: an event when an answer this phone already knew changed.

Future<CrmPreferences?> preferences()

The guest’s preferences: which of the product’s topics of offers and news they hear about, by which channel, as PurrCRM keeps them, or null when they could not be read. The package’s own screen shows them (preferencesScreen); a host that draws its own reads them here.

Widget preferencesScreen({void Function()? onExit})

The package’s preferences screen, for a host that mounts it itself (its routes are otherwise /messages/preferences, CrmPlace. preferences). onExit is what Back does when there is no page before it.

Future<void> refreshNotices()

Reads the inbox’s first page again (a pull to refresh).

Future<bool> registerPushToken(String token, {CrmPushPlatform? platform})

The device’s push token, from the host’s Firebase: registered with PurrCRM for the guest signed in, at once, and for each guest who signs in after. Called on every start and whenever Firebase gives a new token. platform is the phone’s own unless said. Answers whether PurrCRM took it; one it did not is tried again at the next sign-in or the next call.

void screen(String name)

The host is showing the screen name (never a path or an id): screen_viewed, unless it was the last screen told of.

The names are the host’s own, the same every time a screen shows: staff aim a message at them (InAppMessage.screens), which is held until one of them is on.

void session(CrmSession? session)

Who is signed in: session, or nobody when null. Called whenever the host’s own session changes, and only once it has settled: a session still being decided, or refused for now, changes nothing.

A guest who signs in has the messenger’s count read and is checked in for, each by the parts they have (features); the same guest told again with the messenger or in-app messages newly theirs has that read, or is checked in for, then, as when the app’s public face turns one on. Nobody forgets the last guest’s messages, conversations and queued events, so that the next guest on the phone sees only theirs.

Future<bool?> setOffersByPush(bool on)

Keeps the guest’s choice about offers by push, made in the host’s own screens: a marketing push goes only to a guest who agreed. Answers the consent as PurrCRM kept it, or null when it could not be kept.

Future<CrmPreferences?> setPreferences(List<CrmPreferenceChoice> choices, {bool allOff = false, String wording = crmPreferencesWording})

Keeps the guest’s choices, made in the host’s own screens, under wording (the version of the words they agreed under), or turns all their offers and news off (allOff). Answers their preferences as PurrCRM kept them, or null when they could not be kept.

void setReady(bool ready)

Whether the guest is past the host’s steps after signing in, so that a tapped push may take them somewhere and the CrmOverlay may show a message (ready). Called whenever that changes.

Future<void> show(CrmDestination where)

Shows where over the page the guest is on, through the host’s navigator, as a tap inside PurrCRM does (a conversation from the messenger’s list, an article from a reply): done once they have come back from it, at once when there is no navigator.

Future<bool> showMessage(String id)

Shows the in-app message id that a link names (/messages/show/<id>) over the page the guest is on: only a message whose link opens it, live, to the guest signed in and in its audience. Answers whether the CRM had one to show; the CrmOverlay shows it once the guest is ready.

Future<void> start()

Starts PurrCRM, once, as the product starts: the product events kept from the last run are read back, the product’s opening is told (app_opened, and app_updated after an update), and from now on the product’s lifecycle is followed, unless the config says not to.

void track(String name, [Map<String, String> props = const {}])

Records that the guest did name, with props: one of the package’s events (CrmEvent), or one the config declared ([CrmConfig. customEvents]). Any other name fails an assert in a debug build and is dropped in a release one: a typo, found at the desk. PurrCRM refuses an event the app does not define on its own, and keeps the rest of its batch.

final class CrmAppUnavailable extends CrmProblem

PurrCRM refused because the app is archived or deleted (app_unavailable): final. The transport asks for no fresh token and sends nothing more; the guest is told the app’s support is not available. A CrmProblem, so whatever reads a refusal reads this one too.

const CrmAppUnavailable(Problem problem)
final class CrmAuth

How PurrCRM knows who the guest is: a token from the host, which signs its guests in itself, or, for an app whose staff turned on unverified sign-in, the guest’s id alone.

const CrmAuth.tokens(CrmTokens tokens, {CrmFailure Function(Object)? tokenFailure})

The token tokens answers: one the host’s backend signs with the app’s identity secret (CrmTokens.fetched), or one of the host’s own sign-in whose key set the app names. When it cannot answer and throws something of the host’s own, tokenFailure says which of PurrCRM’s failures that is (no connection, unless it says otherwise), so that the guest is told what the host would have told them.

const CrmAuth.unverified(String? Function() userId)

No token: the guest’s id, as userId answers it (null while nobody is signed in), sent as X-CRM-User-Id. Only for an app whose staff turned on unverified sign-in in PurrCRM, which anyone can pretend to be anyone in: a prototype, or an app whose messages hold nothing private. Any other app refuses it.

final CrmFailure Function(Object)? tokenFailure
final CrmTokens tokens
final String? Function()? userId

The guest’s id, for an unverified app; null for one that signs its guests in with tokens.

final class CrmConfig

The product, as PurrCRM needs to know it.

const CrmConfig({required Uri apiBase, required String appVersion, required String appKey, String? productName, String storagePrefix = defaultStoragePrefix, Set<CrmFeature> initialFeatures = const {}, Set<String> customEvents = const {}, bool followLifecycle = true, bool autoScreenViews = false, Client? httpClient, String Function()? languageTag, Duration flushEvery = const Duration(seconds: 30), CrmStore? store, DateTime Function()? now})
final Uri apiBase

Where PurrCRM’s routes are: PurrCRM’s own host (such as https://crm.example), whose routes are at its root, or a host and the prefix they are under (https://platform.example/crm).

final String appKey

The product’s public key with PurrCRM (app_…, or app_test_… for its test app, from PurrCRM site’s app settings), sent with every request as X-CRM-App-Key: which app’s contacts, messages and help center the guest is in, whose guest sign-in checks their token, and whose public face PurrCRM answers at start. Required: a CRM refuses a request that names no app.

final String appVersion

The product’s version, which every request and every batch of product events says, and which app_updated compares between runs.

final bool autoScreenViews

Whether PurrCRM’s own screens say they were shown (screen_viewed) by themselves. Off while the host names every screen it shows, the CRM’s included (Crm.screen).

final Set<String> customEvents

The product’s own events, by their words, which PurrCRM knows beside the package’s own (CrmEvent): Crm.track records no other name.

static const String defaultStoragePrefix

The prefix PurrCRM’s keys on the phone have unless the host names one.

final Duration flushEvery

How often the product events are sent while any are waiting.

final bool followLifecycle

Whether the facade follows the product to the background and back by itself (lib/src/state/lifecycle.dart). Widget tests turn it off.

final Client? httpClient

The HTTP client PurrCRM talks to PurrCRM and uploads pictures through; a fake of the platform in a test or a demo build. Null for a client of PurrCRM’s own.

final Set<CrmFeature> initialFeatures

PurrCRM’s parts the host believes the app has on, for a phone that has never had PurrCRM’s answer for appKey (Crm.appFeatures): they stand in until the app’s public face first answers, and never again on that phone, whose kept answer stands in from then on. A CRM that says the key is no app’s (404) leaves none. None unless the host says: nothing of PurrCRM’s shows before PurrCRM says so.

For a host whose parts were on before PurrCRM’s settings decided them (it named them in the session itself), so that the first start of the update with no connection keeps them.

final String Function()? languageTag

The guest’s language as a BCP 47 tag (en-GB), asked for on every request so that a problem’s detail follows the phone’s setting.

final DateTime Function()? now

What time it is: the phone’s clock, unless a test says otherwise.

final String? productName

The product’s name, as the guest knows it: what PurrCRM’s words call the product. Left out, the app’s brand’s name as PurrCRM answers it for appKey at start (Crm.publicApp) stands in.

final String storagePrefix

What every key PurrCRM keeps on the phone starts with, followed by a dot (lib/src/events/store.dart): crm unless the host says. A product whose keys had another prefix before (an earlier version of the package’s default, or its own code before it took the package) keeps that one, so that an update loses nothing still waiting to be sent.

final CrmStore? store

Where PurrCRM keeps what it keeps on the phone: shared_preferences, under storagePrefix, unless the host hands in another store (one in memory, for a demo build or a test).

final class CrmContractError extends CrmFailure

PurrCRM answered with something the package cannot read: a body that is not JSON, or a model that failed to decode it. The guest is shown a general message; the detail is for a log.

const CrmContractError(String message)
final String message
final class CrmDestination

One of PurrCRM’s pages: a place, and for a conversation, a ticket, a collection or an article, which one (id, an id or a slug).

const CrmDestination.article(String slug)

One article of the help center’s.

const CrmDestination.collection(String slug)

One collection of the help center’s.

const CrmDestination.conversation(String id)

One conversation.

const CrmDestination.message(String id)

One in-app message, which its link opens.

const CrmDestination(CrmPlace place, [String id = ''])
const CrmDestination.ticket(String id)

One ticket.

static const CrmDestination helpCenter

The help center’s first screen.

final String id

The conversation’s or the ticket’s id, or the collection’s or the article’s slug; empty for the others.

static const CrmDestination messenger

The messenger’s first screen.

static const CrmDestination newConversation

A new conversation.

final CrmPlace place
static const CrmDestination preferences

The guest’s preferences.

static const CrmDestination tickets

The guest’s tickets.

CrmFeature get feature

The feature that switches this page on: the messenger’s pages are the messenger’s, the help center’s the help center’s; CrmFeature.unknown for the preferences, which no feature holds back (gated).

bool get gated

Whether a feature holds the page back (feature): every page but the preferences, which every guest signed in may open.

String get link

The page’s link, in PurrCRM’s link space (CrmLinks).

enum CrmEvent implements WireWord

crmEvents as a type.

ValueMeaning
appOpenedThe product started, or came back to the front.
sessionEndedThe product went to the back, with how long it was in front.
screenViewedA screen was shown, by its name.
appUpdatedThe first run after an update, from one version to another.
pushPermissionChangedThe guest’s answer about notifications changed.
locationPermissionChangedThe guest’s answer about location changed.
pushOpenedA push was tapped.
linkOpenedThe product was opened from a link that was PurrCRM’s (handleLink): its path, and where it came from (an email, or any other link).
unknown
const CrmEvent(String word)
static CrmEvent parse(String word)

The event word names, or unknown. A product’s own event is unknown here.

sealed class CrmFailure implements Exception

Why a request did not succeed. One of three things, and the screens say something different for each.

const CrmFailure()
enum CrmFeature implements WireWord

crmFeatures as a type.

ValueMeaning
messengerConversations with the app’s staff and its help bot, and the guest’s tickets.
helpCenterThe help center’s articles, searched and read in the product.
inAppMessagesBanners, posts, carousels and surveys shown in the product.
unknown
const CrmFeature(String word)
static CrmFeature parse(String word)

The feature word names, or unknown.

final class CrmFetchedToken

A token the host’s backend signed for the guest, and when it expires.

const CrmFetchedToken(String token, {DateTime? expiresAt})

token, which expires at expiresAt; when that is not given, the token’s own exp claim says (a JWT’s), and a token with neither is kept until PurrCRM refuses it as expired.

final DateTime? expiresAt
final String token
DateTime? get expiry

When the token expires: expiresAt, else its exp claim, read without checking anything about the token; null for neither.

class CrmFetchedTokens implements CrmTokens

CrmTokens over the host’s backend: fetch asks it for a token for the guest (null while nobody is signed in), and the token is kept until a little before it expires (earlier, a minute unless told), so that a request never leaves with one about to run out.

Requests that need a token while one is being fetched wait for that one rather than each asking (one fetch in flight at a time). A token PurrCRM refused as expired is forgotten (expired), and so is everything on forget, which a host calls when the guest signs out.

final tokens = CrmFetchedTokens(() async {
final answer = await myBackend.get('/crm-token');
return CrmFetchedToken(answer['token'] as String);
});
Crm(config: config, auth: CrmAuth.tokens(tokens));
CrmFetchedTokens(Future<CrmFetchedToken?> Function() fetch, {DateTime Function()? now, Duration earlier = const Duration(minutes: 1)})
final Duration earlier

How long before a token expires a new one is fetched instead.

final Future<CrmFetchedToken?> Function() fetch

Asks the host’s backend for a token for the guest; null while nobody is signed in. What it throws fails the request, as CrmTokens.current says.

void forget()

Forgets the token, and any on its way: the guest signed out.

final class CrmHost

The host’s parts. A part the host leaves out is a thing PurrCRM does not do: a link into the host goes nowhere without followable, a carousel asks for no permission without askPermission, and so on.

const CrmHost({String? Function(String)? followable, void Function(String)? openInApp, CrmNavigator? navigator, Future<bool> Function(String)? openExternal, Future<List<({List<int> bytes, String name, String type})>> Function(int)? pickPictures, Future<String> Function()? timeZone, Future<bool> Function(CarouselPermission)? askPermission, void Function(int, int)? onUnreadChanged, List<CrmLink> links = const <CrmLink>[]})
final Future<bool> Function(CarouselPermission)? askPermission

Asks the phone for a carousel screen’s permission: whether it was given.

final String? Function(String)? followable

The host’s own allow-list: where a link in a message or a reply goes in the host, or null for nowhere. PurrCRM’s own links are the package’s (CrmLinks) and are not asked about.

final List<CrmLink> links

The places in the host a link may go, declared for PurrCRM’s staff (CrmLink): PurrCRM is sent them once a build, when a check-in asks, and offers them in its link picker. What a path opens is still followable’s to say. Empty declares none.

final CrmNavigator? navigator

Shows PurrCRM’s pages.

final void Function(int, int)? onUnreadChanged

A check-in found the messenger’s count of unread replies changed, from before to after: a chat staff started is pushed to nobody, and a reply to a guest with no pushes is kept only on the host’s notices, so a host with a bell of its own may want to read it again.

final Future<bool> Function(String)? openExternal

Opens an https page in the browser: whether it opened.

final void Function(String)? openInApp

Goes to followable’s location in the host.

final Future<List<({List<int> bytes, String name, String type})>> Function(int)? pickPictures

The phone’s picker, for the messenger’s pictures.

final Future<String> Function()? timeZone

The phone’s IANA time zone (“Europe/London”), which a check-in and the product events say; ” when the phone will not say.

CrmHost copyWith({String? Function(String)? followable, void Function(String)? openInApp, CrmNavigator? navigator, Future<bool> Function(String)? openExternal, Future<List<({List<int> bytes, String name, String type})>> Function(int)? pickPictures, Future<String> Function()? timeZone, Future<bool> Function(CarouselPermission)? askPermission, void Function(int, int)? onUnreadChanged, List<CrmLink>? links})

These parts, with those given replaced.

final class CrmLink

A place in the host a link may go, as the host declares it (CrmHost.links): its name for PurrCRM’s staff (“Offer page”), its path in the host’s link space, where a part in braces is filled in by whoever writes the link (/offers/{offer_id}, /menu/all?category= {category}), and optionally its page on the web (webUrl: where the link goes on a phone without the app, which may name the same parts) and its screen’s name (screen, as the host tells it to Crm.screen).

PurrCRM is sent what the host declares once a build (when a check-in asks), and offers it to its staff to add to the app’s destinations, the places its link picker lists. The host’s own router decides what a path opens (CrmHost.followable); declaring one opens nothing by itself.

const CrmLink({required String name, required String path, String webUrl = '', String screen = ''})
final String name
final String path
final String screen
final String webUrl
Map<String, Object?> toJson()

The link as PurrCRM’s report takes it (guest.DeclaredLink).

abstract final class CrmLinkSpace

The links that open the app, and the paths in it they name.

static const String clickParam

The query parameter an email’s link carries its click in.

static const String prefix

Where the app’s links are under PurrCRM’s own host: /l/<the app's link name>/.

static ({String path, String token})? inAppPath(Uri link, {required String base, required Uri apiBase})

The path in the app link names, and an email’s click it carries (empty for none), when link is one of the app’s: under base, the app’s link base (CrmPublicApp.linkBase), or, while that is not known (empty), under /l/<any name>/ on PurrCRM’s own host (apiBase’s). Null for any other link. http is taken only when apiBase is http (a CRM on the desk). The rules are guest.InAppPath’s.

abstract final class CrmLinks

PurrCRM’s link space: the paths of its pages, and which page a link names.

static const String helpCenter

The help center’s first screen; an article is under it, by its slug.

static const String helpCollections

Where the help center’s collections are, each by its slug.

static const String messageShow

Where an in-app message is shown by its link, by its id.

static const String messages

The messenger’s first screen; a conversation is under it, by its id.

static const String newConversation

A new conversation.

static const String preferences

The guest’s preferences.

static const String tickets

The guest’s tickets; a ticket is under it, by its id.

static CrmDestination? parse(String link)

PurrCRM page link names, or null for a link that is not one of the CRM’s: a product’s own page, a page on the web, or a path the package does not know (one from a later version, say). Only the path counts; a query or a fragment is let go.

abstract class CrmLocalizations

Callers can lookup localized strings with an instance of CrmLocalizations returned by CrmLocalizations.of(context).

Applications need to include CrmLocalizations.delegate() in their app’s localizationDelegates list, and the locales they support in the app’s supportedLocales list. For example:

import 'l10n/crm_localizations.dart';
return MaterialApp(
localizationsDelegates: CrmLocalizations.localizationsDelegates,
supportedLocales: CrmLocalizations.supportedLocales,
home: MyApplicationHome(),
);
dependencies:
# Internationalization support.
flutter_localizations:
sdk: flutter
intl: any # Use the pinned version from flutter_localizations
# Rest of dependencies

iOS applications define key application metadata, including supported locales, in an Info.plist file that is built into the application bundle. To configure the locales supported by your app, you’ll need to edit this file.

First, open your project’s ios/Runner.xcworkspace Xcode workspace file. Then, in the Project Navigator, open the Info.plist file under the Runner project’s Runner folder.

Next, select the Information Property List item, select Add Item from the Editor menu, then select Localizations from the pop-up menu.

Select and expand the newly-created Localizations item then, for each locale your application supports, add a new item and select the locale you wish to add from the pop-up menu in the Value field. This list should be consistent with the languages listed in the CrmLocalizations.supportedLocales property.

CrmLocalizations(String locale)
static const LocalizationsDelegate<CrmLocalizations> delegate
final String localeName
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates

A list of this localizations delegate along with the default localizations delegates.

Returns a list of localizations delegates containing this delegate along with GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, and GlobalWidgetsLocalizations.delegate.

Additional delegates can be added by appending to this list in MaterialApp. This list does not have to be used at all if a custom list of delegates is preferred or required.

static const List<Locale> supportedLocales

A list of this localizations delegate’s supported locales.

String get articleHelpful

Under an article.

In en, this message translates to: ‘Was this helpful?’

String get articleNo

Under an article: it did not help.

In en, this message translates to: ‘No’

String get articleRelated

The heading over an article’s related articles.

In en, this message translates to: ‘Related articles’

String get articleThanks

After the guest said whether an article helped.

In en, this message translates to: ‘Thanks for your feedback.’

String get articleYes

Under an article: it helped.

In en, this message translates to: ‘Yes’

String get back

A page header’s round back button, as a screen reader says it.

In en, this message translates to: ‘Back’

String get botAnswerLabel

The box where the guest answers the help bot’s question.

In en, this message translates to: ‘Your answer’

String get botEmailLabel

The box where the guest gives the help bot their email address.

In en, this message translates to: ‘Your email address’

String get botHandedOff

Under the help bot’s message handing the guest to a person; followed by when to expect a reply.

In en, this message translates to: ‘You’re through to the team. They’ll reply here.’

String get botNotEmail

Under the help bot’s box, when what was written is not an email address.

In en, this message translates to: ‘That doesn’t look like an email address.’

String get botNotOrderRef

Under the help bot’s box, when what was written is not an order number.

In en, this message translates to: ‘That doesn’t look like an order number.’

String get botNotText

Under the help bot’s box, when nothing, or too much, was written.

In en, this message translates to: ‘Write an answer of up to 500 characters.’

String get botOrderLabel

The box where the guest gives the help bot an order’s number, in the app’s own format.

In en, this message translates to: ‘Your order number’

String get botRefused

Under the help bot’s box, when the service refused the answer.

In en, this message translates to: ‘The help bot couldn’t take that. Try again, or write to us below.’

String get carouselContinue

The button under a carousel’s question that lets the guest pick several answers: it goes on with those picked.

In en, this message translates to: ‘Continue’

String get carouselDone

The button of a carousel’s screen that ends it, when the screen has no buttons of its own.

In en, this message translates to: ‘Done’

String get carouselSkip

In a carousel’s top bar, in place of the close button: closes it.

In en, this message translates to: ‘Skip’

String get close

The button that closes a sheet.

In en, this message translates to: ‘Close’

String get composerAddPicture

The composer’s button that picks pictures.

In en, this message translates to: ‘Add a picture’

String get composerHint

The composer’s placeholder.

In en, this message translates to: ‘Write a message’

String get composerPictureTooLarge

A picture too large to send.

In en, this message translates to: ‘That picture is over 5 MB.’

String get composerPictureWrongType

A file that is not a picture the messenger takes.

In en, this message translates to: ‘Send a JPEG, PNG, GIF or WebP picture.’

String get composerSend

The composer’s send button.

In en, this message translates to: ‘Send’

String get composerSendAsMessage

Beside what the help bot would not take, in the composer: sends it to the team instead.

In en, this message translates to: ‘Send it as a message’

String get composerWaitForPictures

Send pressed while pictures are still uploading.

In en, this message translates to: ‘Wait for your pictures to finish uploading.’

String get conversationClosed

The line where a conversation was closed.

In en, this message translates to: ‘The conversation was closed’

String get conversationClosedNote

Above the composer of a closed conversation.

In en, this message translates to: ‘This conversation is closed. Write again and we’ll reopen it.’

String get conversationEnded

In place of the composer, at its height, when the conversation is not there any more (gone, or the messenger switched off).

In en, this message translates to: ‘This conversation has ended.’

String get conversationHelloThere

At the top of a new conversation, for a guest with no nickname.

In en, this message translates to: ‘Hi there’

String get conversationNewIntro

Under the hello at the top of a new conversation.

In en, this message translates to: ‘Ask us anything, and we’ll get back to you here.’

String get conversationNewMessage

The floating button over a conversation scrolled up when a message arrives below: it scrolls down to it.

In en, this message translates to: ‘New message’

String get conversationNewTitle

The heading of a conversation not yet sent.

In en, this message translates to: ‘New message’

String get conversationNotSent

Under the guest’s message when it did not go: tapping the message sends it again.

In en, this message translates to: ‘Not sent · Tap to retry’

String get conversationPicture

What a screen reader says for a picture in a conversation.

In en, this message translates to: ‘Picture’

String get conversationPictureFailed

A picture in a conversation that did not load.

In en, this message translates to: ‘This picture can’t be shown.’

String get conversationPictureOpen

What tapping a picture in a conversation does, to a screen reader: it opens full screen.

In en, this message translates to: ‘Open the picture’

String get conversationReopened

The line where a closed conversation was opened again.

In en, this message translates to: ‘The conversation was opened again’

String get conversationSending

Under the guest’s message while it is on its way.

In en, this message translates to: ‘Sending…’

String get conversationToNewest

The floating round button over a conversation scrolled up: it scrolls down to the newest message.

In en, this message translates to: ‘Go to the newest message’

String get conversationTrouble

Shown while the conversation cannot be refreshed.

In en, this message translates to: ‘Can’t reach us right now. We’ll keep trying.’

String get helpCenterClear

The button at the end of the help center’s search box that empties it.

In en, this message translates to: ‘Clear the search’

String get helpCenterNoResults

A search with no results.

In en, this message translates to: ‘No articles match that. Try other words, or send us a message.’

String get helpCenterPopular

The heading over the featured articles.

In en, this message translates to: ‘Popular articles’

String get helpCenterResults

The heading over a search’s results.

In en, this message translates to: ‘Results’

String get helpCenterSearch

The help center’s search box.

In en, this message translates to: ‘Search help articles’

String get helpCenterTitle

The heading of the help center.

In en, this message translates to: ‘Help articles’

String get helpCenterTopics

The heading over the help center’s collections.

In en, this message translates to: ‘Topics’

String get helpStillStuck

The help heading over contacting us.

In en, this message translates to: ‘Still stuck?’

String get inAppDismiss

The button that closes an in-app banner or message.

In en, this message translates to: ‘Dismiss’

String get inAppPostFromUs

Under the product’s name at the top of a post from no one in particular.

In en, this message translates to: ‘From the team’

String get inAppTest

A tag on an in-app message that staff sent to a test phone, so that it is not taken for the real one.

In en, this message translates to: ‘Test’

String get inAppTestLabel

What a screen reader says for the Test tag on an in-app message.

In en, this message translates to: ‘Test message’

String get loading

What a screen reader hears for the shape of a page still loading.

In en, this message translates to: ‘Loading…’

String get messengerArticles

The heading over the help center’s featured articles on the messenger.

In en, this message translates to: ‘Suggested articles’

String get messengerBrowseHelp

Opens the help center from the messenger.

In en, this message translates to: ‘Browse help articles’

String get messengerCarryOn

On the messenger’s home in place of the button that starts a conversation, while the guest has one open and the product allows one at a time.

In en, this message translates to: ‘Carry on in your open conversation.’

String get messengerClosedChip

The tag on a closed conversation.

In en, this message translates to: ‘Closed’

String get messengerEarlier

The heading over the guest’s closed conversations.

In en, this message translates to: ‘Earlier conversations’

String get messengerNoPreview

In the messenger’s list, for a conversation with no words to preview.

In en, this message translates to: ‘Open to see the conversation’

String get messengerOpen

The heading over the guest’s open conversations.

In en, this message translates to: ‘Your conversations’

String get messengerReplyDay

How soon staff usually answer (ReplyTime day).

In en, this message translates to: ‘We usually reply in a day’

String get messengerReplyHours

How soon staff usually answer (ReplyTime hours).

In en, this message translates to: ‘We usually reply in a few hours’

String get messengerReplyMinutes

How soon staff usually answer (ReplyTime minutes).

In en, this message translates to: ‘We usually reply in a few minutes’

String get messengerSearchHelp

A card on the messenger’s home that opens the help center’s search.

In en, this message translates to: ‘Search for help’

String get messengerSend

The button that starts a new conversation.

In en, this message translates to: ‘Send us a message’

String get messengerTickets

The heading over the guest’s tickets, and the tickets page’s title.

In en, this message translates to: ‘Your tickets’

String get messengerTitle

The heading of the messenger, and the row that opens it.

In en, this message translates to: ‘Messages’

String get messengerYou

The author of the guest’s own messages, in a conversation’s preview.

In en, this message translates to: ‘You’

String get next

Button that moves on to the next step of a survey or a carousel.

In en, this message translates to: ‘Next’

String get preferencesAllOff

The button at the foot of the preferences screen that turns every topic off on every channel.

In en, this message translates to: ‘Unsubscribe from all offers and news’

String get preferencesAllOffNow

On the preferences screen when the guest has turned all marketing off.

In en, this message translates to: ‘All offers and news are off. Turn on a topic to hear about it again.’

String get preferencesChannelChat

A switch under a topic on the preferences screen: get messages about the topic in the app’s chat with the team.

In en, this message translates to: ‘Chat messages’

String get preferencesChannelEmail

A switch under a topic on the preferences screen: hear about the topic by email.

In en, this message translates to: ‘Email’

String get preferencesChannelInApp

A switch under a topic on the preferences screen: see messages about the topic inside the app (banners, posts).

In en, this message translates to: ‘In the app’

String get preferencesChannelPush

A switch under a topic on the preferences screen: hear about the topic by push notification.

In en, this message translates to: ‘Push notifications’

String get preferencesNone

On the preferences screen when the product has no topics yet.

In en, this message translates to: ‘There is nothing to choose from just now.’

String get preferencesSaved

Under the preferences screen’s heading once every change has been kept.

In en, this message translates to: ‘Saved’

String get preferencesSaving

Under the preferences screen’s heading while a change goes to us.

In en, this message translates to: ‘Saving…’

String get preferencesTitle

The heading of the preferences screen, where the guest chooses which of the product’s topics of offers and news they hear about, and how.

In en, this message translates to: ‘Offers and news’

String get problemAnswerInvalid

An answer the help bot refused (422 answer_invalid).

In en, this message translates to: ‘The help bot couldn’t take that answer.’

String get problemArticleGone

A help article or collection that is not there, or the help center switched off (404).

In en, this message translates to: ‘That help article isn’t here any more.’

String get problemAttachmentInvalid

A picture the messenger would not take (attachment_invalid).

In en, this message translates to: ‘That picture couldn’t be sent. Try a JPEG, PNG, GIF or WebP of up to 5 MB.’

String get problemBadRequest

A service could not read what the app sent, which usually means the app is out of date.

In en, this message translates to: ‘The app sent something that could not be read. If it keeps happening, update the app.’

String get problemBotMovedOn

An answer to a help-bot question it no longer waits on (409 bot_moved_on).

In en, this message translates to: ‘The help bot has moved on from that question.’

String get problemConversationGone

A conversation that is not there, or not the guest’s, or the messenger switched off (404).

In en, this message translates to: ‘We can’t find that conversation.’

String get problemNotFound

Something the guest asked about no longer exists.

In en, this message translates to: ‘That could not be found. It may have been removed.’

String get problemOffline

The app could not reach the platform at all.

In en, this message translates to: ‘There is no connection. Check your phone’s connection and try again.’

String get problemRateLimited

Too many requests, when the service did not say whose limit it was.

In en, this message translates to: ‘That is a lot at once. Give it a little while.’

String get problemRateLimitedGuest

The guest has used up their own requests for the hour.

In en, this message translates to: ‘You have asked for a few already. Give it a little while.’

String get problemRatingClosed

A rating sent for a conversation that has one already, or never asked (rating_closed).

In en, this message translates to: ‘This conversation isn’t asking for a rating any more.’

String get problemSignInAgain

The guest’s session ran out and could not be renewed.

In en, this message translates to: ‘Your session has expired. Sign in again.’

String get problemSurveyClosed

A survey’s answers after it was finished, dismissed or stopped (survey_closed).

In en, this message translates to: ‘This survey has closed. Thanks all the same.’

String get problemTicketGone

A ticket that is not there, or not the guest’s (404).

In en, this message translates to: ‘We can’t find that ticket.’

String get problemUnavailable

A service is temporarily unavailable.

In en, this message translates to: ‘That cannot be done just now. Try again in a moment.’

String get ratingAsk

The heading of the rating prompt at the end of a conversation.

In en, this message translates to: ‘How did we do?’

String get ratingRemarkHint

The remark box under the stars.

In en, this message translates to: ‘Anything to add? (optional)’

String get ratingSend

Sends the rating.

In en, this message translates to: ‘Send rating’

String get refusalRefused

Why something the guest sent was refused, when PurrCRM gave no reason the app knows.

In en, this message translates to: ‘That was not accepted.’

String get retrySoon

After a limit was reached, when the wait is under a minute.

In en, this message translates to: ‘You can try again in a moment.’

String get showMore

Button that loads more search results.

In en, this message translates to: ‘Show more’

String get somethingWentWrong

Shown when a service answered with something the app could not understand, or said no without saying why.

In en, this message translates to: ‘Something went wrong. Try again in a moment.’

String get surveyChoose

A dropdown question with nothing chosen yet.

In en, this message translates to: ‘Choose one’

String get surveyDiscard

In surveyDiscardTitle’s dialog: leave the survey, and its answers with it.

In en, this message translates to: ‘Discard’

String get surveyDiscardTitle

The question asked when the guest goes back (the system’s back, or a swipe) from the first step of a survey they have started answering.

In en, this message translates to: ‘Discard your answers?’

String get surveyDone

The button on a survey’s thank-you step.

In en, this message translates to: ‘Done’

String get surveyEmojiBad

The second face of an emoji question.

In en, this message translates to: ‘Unhappy’

String get surveyEmojiGood

The fourth face of an emoji question.

In en, this message translates to: ‘Happy’

String get surveyEmojiOkay

The middle face of an emoji question.

In en, this message translates to: ‘Neutral’

String get surveyEmojiVeryBad

The first face of an emoji question.

In en, this message translates to: ‘Very unhappy’

String get surveyEmojiVeryGood

The last face of an emoji question.

In en, this message translates to: ‘Very happy’

String get surveyKeepGoing

In surveyDiscardTitle’s dialog: stay in the survey.

In en, this message translates to: ‘Keep going’

String get surveyNotAnswered

What a screen reader says of a survey’s scale before a score is picked.

In en, this message translates to: ‘Not answered’

String get surveyNotSent

A survey’s submit that did not reach us.

In en, this message translates to: ‘Your answers didn’t send. Try again.’

String get surveyOptional

Beside a question that need not be answered.

In en, this message translates to: ‘Optional’

String get surveyOther

The option for an answer of the guest’s own.

In en, this message translates to: ‘Other’

String get surveyOtherHint

The box for the words with Other.

In en, this message translates to: ‘Tell us more’

String get surveyQuickQuestion

The small heading at the top of a survey in a sheet.

In en, this message translates to: ‘Quick question’

String get surveyRequired

Under a required question the guest tried to skip.

In en, this message translates to: ‘Answer this one to go on.’

String get surveySending

A survey’s Next or Send button while the answers go to us; it waits, turned off, with a spinner beside the words.

In en, this message translates to: ‘Sending…’

String get surveyStart

The button on a large survey’s introduction.

In en, this message translates to: ‘Start’

String get surveySubmit

The button that finishes a survey.

In en, this message translates to: ‘Send’

String get surveyThanks

A survey’s end, when it has no thank-you step of its own.

In en, this message translates to: ‘Thank you!’

String get testPhoneCheckNow

The row in Account’s Test phone group that asks PurrCRM at once for the tests staff sent this phone (CrmCubit.checkNow).

In en, this message translates to: ‘Check for tests now’

String get testPhoneChecking

Under testPhoneCheckNow while it asks.

In en, this message translates to: ‘Checking…’

String get testPhoneDetail

Under testPhoneCheckNow before it was pressed: what the group is for.

In en, this message translates to: ‘For trying messages from PurrCRM on this phone. Tests also arrive when the app opens.’

String get testPhoneFailed

Under testPhoneCheckNow when the check-in did not get through.

In en, this message translates to: ‘Could not check. Try again.’

String get testPhoneHeading

The Account group shown only to a guest staff chose as a test phone (Checkin.test_phone), to try messages from PurrCRM on their own phone.

In en, this message translates to: ‘Test phone’

String get ticketDetails

The heading over a ticket’s fields.

In en, this message translates to: ‘Details’

String get ticketMessages

The heading over a ticket’s conversation.

In en, this message translates to: ‘Messages’

String get ticketNo

A yes-or-no field that is no.

In en, this message translates to: ‘No’

String get ticketStateInProgress

A ticket’s state.

In en, this message translates to: ‘In progress’

String get ticketStateResolved

A ticket’s state.

In en, this message translates to: ‘Resolved’

String get ticketStateSubmitted

A ticket’s state.

In en, this message translates to: ‘Submitted’

String get ticketStateUnknown

A ticket in a state this app does not know.

In en, this message translates to: ‘Updated’

String get ticketStateWaiting

A ticket’s state: staff need something from the guest.

In en, this message translates to: ‘Waiting on you’

String get ticketYes

A yes-or-no field that is yes.

In en, this message translates to: ‘Yes’

String get ticketsEmpty

The tickets page with none.

In en, this message translates to: ‘You have no tickets.’

String get today

The inbox heading over today’s notices.

In en, this message translates to: ‘Today’

String get tryAgain

Button that repeats whatever just failed.

In en, this message translates to: ‘Try again’

String get yesterday

When a notice from yesterday came.

In en, this message translates to: ‘Yesterday’

String articleUpdated(DateTime at)

When an article last changed.

In en, this message translates to: ‘Updated {at}’

String botArticleOpen(String title)

What a screen reader says of an article the help bot suggests.

In en, this message translates to: ‘Open the article {title}’

String carouselPage(int page, int pages)

A screen reader’s label for where the guest is in a carousel.

In en, this message translates to: ‘Page {page} of {pages}’

String carouselProgress(int percent)

A screen reader’s label for a carousel’s progress bar: how much of the way to its end the guest has come.

In en, this message translates to: ‘{percent}% of the way’

String composerRemovePicture(int number)

The button on a picture waiting to be sent.

In en, this message translates to: ‘Remove picture {number}’

String composerTooLong(int max)

A message that is too long.

In en, this message translates to: ‘A message can be up to {max} characters.’

String composerTooManyPictures(int max)

More pictures than one message takes.

In en, this message translates to: ‘You can send up to {max} pictures in one message.’

String composerUploadFailed(int number)

A picture that did not upload.

In en, this message translates to: ‘Picture {number} didn’t upload. Remove it and try again.’

String composerUploading(int number)

What a screen reader says for a picture still uploading.

In en, this message translates to: ‘Picture {number}, uploading’

String conversationDate(DateTime date)

The line across a conversation where an older day starts: the date.

In en, this message translates to: ‘{date}’

String conversationDayName(DateTime day)

The line across a conversation where a day of the past week starts: the day’s name.

In en, this message translates to: ‘{day}’

String conversationHello(String name)

At the top of a new conversation, by the guest’s nickname.

In en, this message translates to: ‘Hi {name}’

String conversationSaid(String name, DateTime time)

What a screen reader says before a part: who wrote it and when.

In en, this message translates to: ‘{name}, {time}’

String conversationTitle(String product)

The heading of a conversation.

In en, this message translates to: ‘{product} team’

String helpCollectionCount(int count)

How many articles a topic has.

In en, this message translates to: ‘{count, plural, =1{1 article} other{{count} articles}}’

String helpMessagesSubtitle(String product)

Under Messages on the help page.

In en, this message translates to: ‘Chat with the {product} team’

String inAppFromTeam(String name, String product)

Who a full-screen message is from, under its words.

In en, this message translates to: ‘From {name} at {product}’

String inAppFromUs(String product)

A full-screen message with no teammate named.

In en, this message translates to: ‘From {product}’

String inAppPostTeam(String product)

Under a teammate’s first name at the top of a post from them.

In en, this message translates to: ‘{product} team’

String messengerBackAt(DateTime time)

Staff are not at work now, and are back later today.

In en, this message translates to: ‘We’re back at {time}’

String messengerBackOn(DateTime day, DateTime time)

Staff are not at work now, and are back on a later day.

In en, this message translates to: ‘We’re back {day} at {time}’

String messengerHeading(String product)

The big heading on the messenger’s first screen.

In en, this message translates to: ‘Talk to the {product} team’

String messengerNames(String names, String last)

The teammates shown on the messenger’s home, under their faces: the others separated by commas, then the last.

In en, this message translates to: ‘{names} and {last}’

String messengerNothingYet(String product)

The messenger with no conversations.

In en, this message translates to: ‘No messages yet. Ask us anything about {product}.’

String messengerTeam(String product)

The author of a part written by the messenger or an unnamed member of staff.

In en, this message translates to: ‘{product}’

String messengerUnread(int count)

The count on a conversation with unread replies.

In en, this message translates to: ‘{count, plural, =1{1 unread} other{{count} unread}}’

String messengerYouSaid(String text)

In the messenger’s list, what the guest wrote last in a conversation.

In en, this message translates to: ‘You: {text}’

String noticeAtTime(DateTime time)

When a notice from today came.

In en, this message translates to: ‘{time}’

String noticeOnDate(DateTime date)

When an older notice came: the date.

In en, this message translates to: ‘{date}’

String noticeOnDay(DateTime day)

When a notice from the past week came: the day’s name.

In en, this message translates to: ‘{day}’

static CrmLocalizations? of(BuildContext context)
String preferencesIntro(String product)

Under preferencesTitle: what the screen is for. Service messages are not affected by any switch on it.

In en, this message translates to: ‘Choose what {product} sends you about each topic, and how. Messages about your account and orders always come.’

String ratingGiven(int count)

The rating prompt once answered.

In en, this message translates to: ‘You rated this {count} out of 5’

String ratingStar(int count)

A star button in the rating prompt.

In en, this message translates to: ‘{count, plural, =1{1 star} other{{count} stars}}’

String retryAt(DateTime time)

After a limit was reached: the time of day when asking again is worth it.

In en, this message translates to: ‘You can try again at {time}.’

String stepOf(int step, int total)

What a screen reader says for the dots over the steps after signing in.

In en, this message translates to: ‘Step {step} of {total}’

String surveyScaleEnds(int low, String lowLabel, int high, String highLabel)

What a screen reader adds about a survey’s scale: what its two ends mean.

In en, this message translates to: ‘{low} is {lowLabel}, {high} is {highLabel}’

String surveyScore(int score, int max)

A screen reader’s label for one number on a scale.

In en, this message translates to: ‘{score} out of {max}’

String surveyStars(int count)

A screen reader’s label for a star rating.

In en, this message translates to: ‘{count} of 5 stars’

String testPhoneArrived(int count)

Under testPhoneCheckNow once it has asked: how many tests arrived, which then show.

In en, this message translates to: ‘{count, plural, =0{Nothing waiting} =1{1 test arrived} other{{count} tests arrived}}’

String ticketLabel(String ref, String title, String state)

What a screen reader says for a ticket in a list.

In en, this message translates to: ‘{ref}, {title}, {state}’

String ticketProgress(String state, int step, int steps)

What a screen reader says for a ticket’s steps: where it is, and how far along.

In en, this message translates to: ‘{state}, step {step} of {steps}’

String ticketResolvedOn(DateTime at)

When a ticket was resolved.

In en, this message translates to: ‘Resolved on {at}’

String ticketUpdated(DateTime at)

When a ticket last changed.

In en, this message translates to: ‘Updated {at}’

abstract interface class CrmNavigator

Takes the guest to one of PurrCRM’s pages, in the host’s own routes: GoRouterCrmNavigator (lib/go_router.dart) for a host that routes with go_router, NavigatorCrmNavigator for one that pushes routes itself.

Future<bool> open(CrmDestination where)

Shows where in place of where the guest is, as a tapped push does: whether it could.

Future<void> push(CrmDestination where)

Shows where over the page the guest is on, as a tap inside PurrCRM does, so that Back comes back to it: done once they have come back.

class CrmNotice

One notice in the guest’s inbox: what PurrCRM pushed them, kept to read again.

factory CrmNotice.fromJson(Map<String, Object?> json)
const CrmNotice({required String id, required CrmPushType type, required String title, required String body, required DateTime createdAt, String link = '', DateTime? readAt})
final String body
final DateTime createdAt
final String id
final String link

A path in the app, which the app’s allow-list decides whether to open; empty for none.

final DateTime? readAt

When the guest read it, or null for unread.

final String title
final CrmPushType type

What the notice is about; unknown for a type the package does not handle, which is still the guest’s to read.

bool get unread

Whether the guest has not read it.

CrmNotice readOn(DateTime at)

The notice read at at.

class CrmNotices

The guest’s inbox as the package has it.

const CrmNotices({List<CrmNotice> notices = const <CrmNotice>[], int unread = 0, bool loaded = false, bool loading = false, CrmFailure? failure, String nextBefore = ''})
final CrmFailure? failure

Why the last read failed, or null.

final bool loaded

Whether a page has been read since the guest signed in.

final bool loading

Whether a page is on its way.

final String nextBefore

The notice older ones are read before; empty when there are no more.

final List<CrmNotice> notices

The notices read so far, newest first.

final int unread

The whole inbox’s unread count: the bell’s number.

bool get hasMore

Whether there are older notices to read (NoticesKeeper.more).

CrmNotices copyWith({List<CrmNotice>? notices, int? unread, bool? loaded, bool? loading, CrmFailure? failure, bool clearFailure = false, String? nextBefore})
final class CrmOffline extends CrmFailure

PurrCRM could not be reached, or did not answer in time. Nothing is known about whether it acted: a POST that ends this way is not retried.

const CrmOffline(Object cause)
final Object cause
bool get timedOut

PurrCRM did not answer in time, rather than not being reached.

class CrmOrderRef

An app’s order reference: what its guests call an order’s reference, the pattern one matches, an example the bot shows, and whether an answer is matched and kept in capitals. A collecting part carries it, so that the package checks an answer as PurrCRM will.

factory CrmOrderRef.fromJson(Map<String, Object?> json)
const CrmOrderRef({required String label, required String pattern, required String example, String prop = '', bool upper = false})
final String example

One that matches, which the bot shows.

final String label

What the app calls it: “Order number”.

final String pattern

A regular expression the whole answer matches.

final String prop

The event prop that prefills a ticket’s order; empty for none.

final bool upper

Matched and kept in capitals.

String get hint

What the box says to an answer that is not one: “Order numbers look like …”, in the app’s own words.

String? check(String value)

The answer as PurrCRM keeps it, or null when PurrCRM would refuse it. A pattern this phone cannot read leaves the check to PurrCRM.

typedef CrmPicture = ({List<int> bytes, String name, String type})

One picture the guest picked: its bytes, its file name, and its content type (” when the phone did not say).

typedef CrmPicturePicker = Future<List<({List<int> bytes, String name, String type})>> Function(int)

Shows the phone’s picker for up to most pictures: the ones picked, none when the guest changed their mind.

enum CrmPlace

Which of PurrCRM’s pages a CrmDestination is.

ValueMeaning
messengerThe messenger’s first screen: the open conversations, the reply line, the featured articles and the tickets.
newConversationA new conversation, which the guest’s first message starts.
conversationOne conversation, by its id.
ticketsThe guest’s tickets.
ticketOne ticket, by its id.
helpCenterThe help center’s first screen: its collections and its search.
collectionOne collection of articles, by its slug.
articleOne article, by its slug.
messageOne in-app message whose link opens it, by its id: shown over the page the guest is on (Crm.showMessage), not a page of its own.
preferencesThe guest’s preferences: which topics of offers and news they hear about, by which channel.
enum CrmPlatform implements WireWord

crmPlatforms as a type.

Values: ios, android, web, unknown.

const CrmPlatform(String word)
static CrmPlatform parse(String word)

The platform word names, or unknown.

final class CrmProblem extends CrmFailure

PurrCRM answered, and said no: a problem+json body, or, for a response without one, a problem made up from its status.

const CrmProblem(Problem problem)
CrmProblem.of({required int status, required String code, String title = '', String detail = '', String? scope, int? retryAfterSeconds, String requestId = ''})

A refusal made up from its parts, as PurrCRM would have answered it: what a host’s sign-in refusing to give a token is, say, or what a host’s fake of one of the package’s APIs throws in a test. code is one of the guest routes’ error codes, and scope, for a 429, whose limit it was, each as its word.

final Problem problem
int get status
class CrmPublicApp

An app’s public face, which PurrCRM answers anyone who names its key (GET /public/v1/apps/{key}), before a guest signs in: how it is named and coloured, what it has switched on, its currency and order reference, and the SDK it needs. Nothing in it is secret.

factory CrmPublicApp.fromJson(Map<String, Object?> json)
const CrmPublicApp({required String key, String mode = 'live', required CrmPublicBrand brand, List<String> features = const <String>[], String currency = '', CrmOrderRef? orderRef, String minSdkVersion = '', String linkBase = '', List<String> languages = const <String>[]})
final CrmPublicBrand brand
final String currency

The ISO 4217 code its money is in; empty when its staff named none.

final List<String> features

The parts of the guest experience it has on, by their words.

final String key

The app’s public key, the one asked for.

final List<String> languages

PurrCRM picks one of them for each guest and sends the content in it: the package picks nothing, and a host need not either.

final String linkBase

Where the app’s links are: https://<the CRM's host>/l/<name>, or https://&lt;a host of the app's own&gt;; empty while it has no link domain, and from a CRM before links.

final String minSdkVersion

The oldest SDK its staff want its guests on (“0.6.0”); empty for none.

final String mode

live, or test for a live app’s test app: a staging build’s, whose data is kept apart from the live app’s. Live from a CRM that says neither.

final CrmOrderRef? orderRef

Its order reference; null for none.

bool get isTest

Whether the key is a test app’s.

class CrmPublicBrand

An app’s brand as its guests see it: its name (never empty), its accent (#rrggbb, or empty for the product’s own), its logo and where its guests may write.

factory CrmPublicBrand.fromJson(Map<String, Object?> json)
const CrmPublicBrand({required String name, String accent = '', String logoUrl = '', String supportEmail = ''})
final String accent
final String logoUrl
final String name
final String supportEmail
enum CrmPushPlatform implements WireWord

crmPushPlatforms as a type.

Values: android, ios, web, unknown.

const CrmPushPlatform(String word)
static CrmPushPlatform parse(String word)

The platform word names, or unknown.

final class CrmPushResult

What a push that arrived while the product was open was, to PurrCRM.

const CrmPushResult({bool isCrm = false, bool replyOnScreen = false, String noticeId = '', bool silent = false})
final bool isCrm

One of PurrCRM’s pushes (a support_reply or a crm_message), which the CRM has acted on: the messenger’s count read again, a check-in made.

final String noticeId

The host’s notice the push is, as its data names it; empty for none.

final bool replyOnScreen

A reply in a conversation or a ticket that is on the screen, whose wait has brought the reply and marked it read already. The host’s notice for it (PurrCRM pushes a reply at once, usually before that read reaches it, so the platform keeps one) can be marked read too.

final bool silent

A crm_message with nothing to show: a nudge PurrCRM sends an app left open to check in for an in-app message queued for its guest (its data says nudge), which PurrCRM has done. It carries no notification and is no notice, so the host shows nothing for it.

enum CrmPushType implements WireWord

crmPushTypes as a type.

ValueMeaning
supportReplyA reply in one of the guest’s conversations, or a change to one of their tickets.
crmMessageA message the app’s staff sent from PurrCRM.
unknown
const CrmPushType(String word)
static CrmPushType parse(String word)

The type word names, or unknown: a push of the product’s own, say.

class CrmScope extends StatelessWidget

Provides crm to the widgets below it (CrmScope.of): PurrCRM’s screens, its CrmOverlay and its tiles. A host mounts one above its MaterialApp, once, so that its routes, its Navigator and the overlay in its builder are all below it.

The package’s own screens read the messenger’s cubit through it too; that is the package’s, and a host never reads it.

const CrmScope({Key? key, required Crm crm, required Widget child})
final Widget child
final Crm crm
static Crm? maybeOf(BuildContext context)

The facade of the nearest CrmScope above context, or null for none.

static Crm of(BuildContext context)

The facade of the nearest CrmScope above context. It may be asked in a State’s initState: a scope’s facade is the one object for as long as the product runs, and nothing below is rebuilt for it.

final class CrmSession

A signed-in guest, as the host’s session has them: their id with the host’s sign-in, the name they chose, whether they turned “Help improve …” off, and, when the host holds some of PurrCRM’s parts back from them, which of the app’s it lets them have.

Which of PurrCRM’s parts a guest has is the app’s to say: the features its staff switched on in PurrCRM site, which PurrCRM answers with the app’s public face (CrmPublicApp.features) and the facade keeps on the phone for a start with no connection. A host that says nothing (features null) leaves it at that. A host that names features narrows it: the guest has a part only when the app has it on and the host names it. A host cannot switch on a part the app has off.

const CrmSession({required String guestId, String displayName = '', bool productEventsOff = false, Set<CrmFeature>? features})
final String displayName

The name the guest chose, for a hello in a new conversation; empty when they have none yet.

final Set<CrmFeature>? features

PurrCRM’s parts the host lets this guest have, of those the app has on; null for every one the app has on. A product’s own features are the host’s, and are not here.

final String guestId

The guest’s id with the host’s sign-in (the token’s subject), which the product events are kept with.

final bool productEventsOff

The guest turned product events off: nothing is kept or sent for them (lib/src/events/tracker.dart).

bool has(CrmFeature feature)

Whether the host lets this guest have feature, if the app has it on: always, when the host names no features.

CrmSession withFeatures(Set<CrmFeature>? features)

This session with features in place of its own.

abstract interface class CrmStore

Where PurrCRM keeps what it keeps on the phone, every key under one namespace prefix.

String get prefix

What every key starts with, followed by a dot: crm unless the host names another (CrmConfig.storagePrefix).

Future<String?> read(String name)

What is kept under name, or null for nothing.

Future<void> remove(String name)

Forgets what is kept under name.

Future<void> write(String name, String value)

Keeps value under name.

class CrmStringsDelegate extends LocalizationsDelegate<CrmLocalizations>

The package’s strings, for a MaterialApp’s localizationsDelegates in place of CrmLocalizations.delegate, with what a host may change:

  • strings: its own CrmLocalizations for a language, by BCP 47 tag (cy, pt-BR, zh-Hant, in any case, _ or -; a tag of a language alone serves each of its regions and scripts), for one the package has no ARB file for, or to say its own words in one it has. The most precise tag that fits a locale wins: zh-Hant-TW, then zh-Hant, then zh-TW, then zh. The host writes them as gen-l10n would (a class that extends CrmLocalizations, or the English one to change a few).
  • supportedLocales: the languages the package may speak, when the host wants fewer than it has: the package’s and strings’ when left out. In a language not among them the package speaks English.
CrmStringsDelegate({Map<String, CrmLocalizations> strings = const {}, List<Locale>? supportedLocales})
final Map<String, CrmLocalizations> strings

The host’s own strings, by language tag in its canonical form (pt_br given is pt-BR here).

final List<Locale> supportedLocales

The languages the package may speak.

abstract interface class CrmTokens

Where the transport gets the guest token it sends. The host implements it, over its own sign-in.

Several requests can run at once, and several can be refused as expired together; an implementation exchanges one fresh token for all of them rather than one each.

factory CrmTokens.fetched(Future<CrmFetchedToken?> Function() fetch, {DateTime Function()? now, Duration earlier})

Tokens fetched from the host’s own backend, which signs one for the guest with the app’s identity secret: fetch answers one (null while nobody is signed in), which is kept until a minute before it expires, fetched once for every request waiting on it, and forgotten when PurrCRM refuses it as expired. A host that signs the guest out keeps the CrmFetchedTokens itself to call CrmFetchedTokens.forget.

Future<String?> current()

The guest token to send, exchanging a fresh one first when it has run out, or null when the guest is signed out.

When it cannot answer (the host’s sign-in could not be reached, or refused the exchange), it throws, and the request fails without being sent: as the CrmFailure thrown, or as what the transport’s tokenFailure makes of any other exception (no connection, unless the host says otherwise).

Future<void> expired(String token)

Forgets token, which PurrCRM has just refused as expired, so that the next current answers a new one. A token that is no longer the current one is already forgotten, and asking again changes nothing.

class MemoryCrmStore implements CrmStore

A CrmStore in memory, for tests and a build with no phone to keep anything on. It keeps each value under its whole key, prefix and all, so that a test can see the keys a store on the phone would use.

MemoryCrmStore({required String prefix})
final Map<String, String> values

What is kept, by whole key.

String crmLinksDigest(Iterable<CrmLink> links)

The digest of what a host declares (CrmLinks), which a check-in sends so that PurrCRM asks for them only when they changed: the links sorted by path then name (as UTF-8 bytes), each its four fields joined by U+001F and ended by U+001E, SHA-256, the first 16 bytes as lowercase hex.

const String crmSdkVersion

The version of crm_sdk this is. CHANGELOG.md says the oldest CRM each version works against.

const List<Locale> crmSupportedLocales

The languages the package’s own strings are written in: one for each ARB file in lib/l10n, as flutter gen-l10n lists them.

String pictureTypeOf(String fileName)

The content type a picture’s file name says, or ” for one that is not a picture the messenger takes.