Electronic Signatures
Regulated apps ask a person to sign a controlled action: they say who they are again, state what the signature means and why they sign, and get a receipt. vyuh_cdx_ui ships that as a small set of composable parts, not a dialog. The parts are UI only: they collect text, draw what they are told and call back. Verifying the credentials, checking that the signer is eligible and persisting the signature are the app's.
| Part | Shows |
|---|---|
SignatureCeremony | The shell: arranges the slots below in reading order |
SignatureSubject | What is being signed: the action, the record, and what changes on it |
SignerChain | Who signs, in order, as a typed list of SignatureParticipant |
SignatureMeaning | The read-only statement "I am signing as Witness" |
SignatureCredentials | Identifier and password or PIN fields |
SignatureReason | The remark, hidden, optional or required |
SignatureReceipt | Signer, meaning, time, method and assertion id |
SignatureStyle | One style object per part |
Assemble a ceremony
Every slot of SignatureCeremony is optional and a missing one takes no space. The order is fixed: subject, signer, meaning, credentials, reason, statusMessage, actions; spacing and padding come from SignatureCeremonyStyle. It does not scroll, so inside a DialogShell wrap it in a SingleChildScrollView; inline in a page, use it as it is.
SignatureCeremony(
subject: const SignatureSubject(
title: 'Approve order',
entityType: 'Order',
entityName: 'PO-1042',
fromStatus: 'In review',
toStatus: 'Approved',
),
signer: const SignerChain(participants: [
SignatureParticipant(label: 'Doer', state: SignatureParticipantState.done),
SignatureParticipant(label: 'Approver', state: SignatureParticipantState.current),
]),
meaning: const SignatureMeaning(role: SignerRole.approver),
credentials: SignatureCredentials(
title: 'Verify your identity',
identifierController: identifier,
secretController: password,
secretErrorText: passwordError,
onSubmitted: (_) => sign(),
),
reason: SignatureReason(
mode: SignatureRemarks.required,
controller: reason,
errorText: reasonError,
),
statusMessage: failure == null
? null
: Banner(type: BannerType.critical, message: failure),
actions: Row(spacing: CdxSpacing.sm, mainAxisSize: MainAxisSize.min, children: [...]),
)Parts alone
SignatureSubject
Three plain parts, so the signer sees what is happening:
Witness site activation the action (title)
Site Hyderabad Site the record: entityType labels entityName
Status Draft → Active what changes: changeLabel, fromStatus → toStatuschangeLabel names what changes (default Status). A row with nothing to show is left out. statusDisplay is SignatureStatusDisplay.chips (a chip per status with an arrow, the destination emphasised) or .text ("Draft → Active").
SignerChain
A caption ("Signing order", null to leave it out) over a Wrap of steps with a chevron between them. A step is a SignatureParticipant(label, role, state). The current step is the person using the dialog, so it is the only solid, bold step, with a person glyph. A done step is quiet, with a check and no outline; a pending step is only outlined. It reads to a screen reader as one sentence: "Signature chain: Doer, signed, then Approver, you are signing now".
SignatureMeaning
SignerRole is the typed capacity: author, reviewer, approver, witness, or SignerRole.custom('Owner'). The widget states it read-only; pass statement to say more.
SignatureCredentials
Dumb fields: identifierController/secretController, change callbacks, and the *ErrorText/*HelperText the caller computed. method is SignatureAuthMethod.password or .pin (digits only, numeric keyboard, labelled PIN). A Caps Lock hint appears under the secret while it has focus (capsLockOn forces it). identifierReadOnly fixes the identifier of the signed-in user; access and size are the usual CdxAccess and CdxControlSize.
SignatureReason
mode is SignatureRemarks.hidden (nothing drawn), optional or required (marked label). The widget renders only: whether an empty required remark blocks signing is computed by the caller and passed back as errorText.
SignatureReceipt
Signer, role, signedAt (shown in UTC; pass formatTimestamp to change), optional method and assertionId with a button that copies the id, then calls onCopied.
Styling
SignatureStyle holds one style object per part (SignatureSubjectStyle, SignerChainStyle, …). It is a plain class resolved from the theme and CdxConfig, not a theme extension. A part uses its own style argument, else the nearest SignatureStyleScope, else the resolved defaults:
final base = SignatureStyle.resolve(context);
SignatureStyleScope(
style: base.copyWith(
signerChain: base.signerChain.copyWith(radius: BorderRadius.zero),
),
child: ceremony,
)SignatureStyle.resolve(context, tone: SignatureTone.onPrimary) gives the colours for parts that sit on a brand-filled dialog header.
Access
SignatureCredentials and SignatureReason take a CdxAccess: a non-editable access keeps the values legible but fixed, locked adds the lock, and disabled mutes the group as one unit.