Skip to content

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.

PartShows
SignatureCeremonyThe shell: arranges the slots below in reading order
SignatureSubjectWhat is being signed: the action, the record, and what changes on it
SignerChainWho signs, in order, as a typed list of SignatureParticipant
SignatureMeaningThe read-only statement "I am signing as Witness"
SignatureCredentialsIdentifier and password or PIN fields
SignatureReasonThe remark, hidden, optional or required
SignatureReceiptSigner, meaning, time, method and assertion id
SignatureStyleOne 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.

dart
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:

text
Witness site activation      the action (title)
Site      Hyderabad Site     the record: entityType labels entityName
Status    Draft → Active     what changes: changeLabel, fromStatus → toStatus

changeLabel 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:

dart
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.