Skip to content

feat: Add FaceLivenessTheme for comprehensive UI customization - #224

Open
rsrbk wants to merge 1 commit into
aws-amplify:mainfrom
rsrbk:feature/customizable-theme
Open

feat: Add FaceLivenessTheme for comprehensive UI customization#224
rsrbk wants to merge 1 commit into
aws-amplify:mainfrom
rsrbk:feature/customizable-theme

Conversation

@rsrbk

@rsrbk rsrbk commented Apr 6, 2026

Copy link
Copy Markdown

Add a new FaceLivenessTheme configuration type that allows consumers to customize the visual appearance of the Face Liveness detection UI without forking the library. All customizations have sensible defaults that preserve the existing UI exactly.

New API

var theme = FaceLivenessTheme()
theme.colors.primaryBackground = .black
theme.oval.strokeWidth = 4
theme.oval.maskColor = .black
theme.instruction.backgroundColor = .white
theme.instruction.textColor = .black
theme.instruction.useCapsuleShape = true
theme.components.showRecordingIndicator = false
theme.components.showCloseButton = false
theme.preferredColorScheme = .dark
theme.customLoadingView = AnyView(MyLoadingView())
theme.usesCompactCameraPermissionPrompt = true

FaceLivenessDetectorView(
    sessionID: sessionID,
    region: region,
    theme: theme,
    isPresented: $isPresented,
    onCompletion: { _ in }
)

Customization Surface

Colors (FaceLivenessTheme.Colors)

All 9 semantic color tokens are now configurable:

  • primaryBackground, primaryLabel (buttons, active instructions)
  • background, label (close button, verifying state)
  • errorBackground, errorLabel (error instructions)
  • warningBackground, warningLabel (photosensitivity warning)
  • previewBorder (Get Ready page preview ellipse)

Oval Overlay (FaceLivenessTheme.OvalStyle)

  • maskColor: Fill color for the mask area outside the oval
  • strokeColor: Oval border stroke color
  • strokeWidth: Oval border line width

Instruction Pill (FaceLivenessTheme.InstructionStyle)

  • backgroundColor/textColor: Override all per-state colors uniformly
  • font: Instruction text font
  • useCapsuleShape: Capsule vs rounded rectangle
  • cornerRadius: Corner radius (when not using capsule)
  • padding: Text padding within the pill

Component Visibility (FaceLivenessTheme.ComponentVisibility)

  • showRecordingIndicator: Toggle the red REC badge
  • showCloseButton: Toggle the close button
  • showProgressBar: Toggle the face match progress bar

Additional Options

  • preferredColorScheme: Force light/dark mode
  • customLoadingView: Replace the default loading spinner
  • usesCompactCameraPermissionPrompt: Use system alert instead of full-screen camera permission view

Freshness Color Check Fix

Added forceWhiteFill mechanism to ensure the freshness color overlay always renders against a white background, regardless of theme. The AWS backend validates semi-transparent overlay colors (alpha 0.75-0.9) — a non-white background causes the blended color to differ, failing the liveness check.

  • OvalView: Added forceWhiteFill property + traitCollectionDidChange for dynamic UIColor support
  • LivenessViewController: Forces white backgrounds on displayFreshness, reverts to black on completion
  • _FaceLivenessDetectionView: SwiftUI background switches to white during .displayingFreshness and .faceMatched states

Backward Compatibility

  • The theme parameter defaults to .default on all inits
  • All existing call sites compile unchanged
  • Default theme values reproduce the exact current UI
  • Theme is propagated via SwiftUI Environment to all child views
  • UIKit views (OvalView, LivenessViewController) receive config via explicit init parameters

Add a new FaceLivenessTheme configuration type that allows consumers to
customize the visual appearance of the Face Liveness detection UI without
forking the library. All customizations have sensible defaults that
preserve the existing UI exactly.

## New API

```swift
var theme = FaceLivenessTheme()
theme.colors.primaryBackground = .black
theme.oval.strokeWidth = 4
theme.oval.maskColor = .black
theme.instruction.backgroundColor = .white
theme.instruction.textColor = .black
theme.instruction.useCapsuleShape = true
theme.components.showRecordingIndicator = false
theme.components.showCloseButton = false
theme.preferredColorScheme = .dark
theme.customLoadingView = AnyView(MyLoadingView())
theme.usesCompactCameraPermissionPrompt = true

FaceLivenessDetectorView(
    sessionID: sessionID,
    region: region,
    theme: theme,
    isPresented: $isPresented,
    onCompletion: { _ in }
)
```

## Customization Surface

### Colors (FaceLivenessTheme.Colors)
All 9 semantic color tokens are now configurable:
- primaryBackground, primaryLabel (buttons, active instructions)
- background, label (close button, verifying state)
- errorBackground, errorLabel (error instructions)
- warningBackground, warningLabel (photosensitivity warning)
- previewBorder (Get Ready page preview ellipse)

### Oval Overlay (FaceLivenessTheme.OvalStyle)
- maskColor: Fill color for the mask area outside the oval
- strokeColor: Oval border stroke color
- strokeWidth: Oval border line width

### Instruction Pill (FaceLivenessTheme.InstructionStyle)
- backgroundColor/textColor: Override all per-state colors uniformly
- font: Instruction text font
- useCapsuleShape: Capsule vs rounded rectangle
- cornerRadius: Corner radius (when not using capsule)
- padding: Text padding within the pill

### Component Visibility (FaceLivenessTheme.ComponentVisibility)
- showRecordingIndicator: Toggle the red REC badge
- showCloseButton: Toggle the close button
- showProgressBar: Toggle the face match progress bar

### Additional Options
- preferredColorScheme: Force light/dark mode
- customLoadingView: Replace the default loading spinner
- usesCompactCameraPermissionPrompt: Use system alert instead
  of full-screen camera permission view

## Freshness Color Check Fix

Added forceWhiteFill mechanism to ensure the freshness color overlay
always renders against a white background, regardless of theme. The AWS
backend validates semi-transparent overlay colors (alpha 0.75-0.9) — a
non-white background causes the blended color to differ, failing the
liveness check.

- OvalView: Added forceWhiteFill property + traitCollectionDidChange
  for dynamic UIColor support
- LivenessViewController: Forces white backgrounds on displayFreshness,
  reverts to black on completion
- _FaceLivenessDetectionView: SwiftUI background switches to white
  during .displayingFreshness and .faceMatched states

## Backward Compatibility

- The theme parameter defaults to .default on all inits
- All existing call sites compile unchanged
- Default theme values reproduce the exact current UI
- Theme is propagated via SwiftUI Environment to all child views
- UIKit views (OvalView, LivenessViewController) receive config
  via explicit init parameters
@rsrbk
rsrbk requested review from a team as code owners April 6, 2026 16:55
@mattcreaser

Copy link
Copy Markdown
Member

Hi @rsrbk thanks for the Pull Request. This is very useful work. We definitely want this change so we'll take a look and let you know how to proceed!

@thisisabhash

thisisabhash commented Apr 28, 2026

Copy link
Copy Markdown
Member

Hello @rsrbk,

We reviewed the PR with the team and have the following comments.

At the moment, we are approving the addition of LivenessTheme for Color changes with the changes mentioned below:

  • Add public modifier to FaceLivenessDetectorView like below instead of constructor parameter.
let theme = FaceLivenessTheme()
theme.colors.primaryBackground = .blue

FaceLivenessDetectorView(
    sessionID: sessionID,
    region: region,
    isPresented: $isPresented,
    onCompletion: { _ in }
)
.livenessTheme(theme)

extension FaceLivenessDetectorView {
    public func livenessTheme(_ theme: FaceLivenessTheme) -> some View {
        environment(\.livenessTheme, theme)
    }
}
  • Add conformance to ObservableObject to FaceLivenessTheme.
public class FaceLivenessTheme: ObservableObject {
    public var colors = Colors()

    public init() {}
}
  • Rename the colors defaults to match named colors in the Color+Liveness.swift.
extension FaceLivenessTheme {
    public struct Colors {
        public var primaryBackground: Color = .livenessPrimaryBackground
        public var primaryLabel: Color = .livenessPrimaryLabel
        public var background: Color = .livenessBackground
        public var label: Color = .livenessLabel
        public var errorBackground: Color = .livenessErrorBackground
        public var errorLabel: Color = .livenessErrorLabel
        public var warningBackground: Color = .livenessWarningBackground
        public var warningLabel: Color = .livenessWarningLabel
        public var previewBorder: Color = .livenessPreviewBorder
    }
}

Regarding other changes, would you please open feature request ticket for the different theming requirements you're proposing? This includes the Oval Overlay, Instruction Pill, Component Visibility and other changes mentioned in the PR. These require further discussions with the Rekognition team. We are actively looking at this from our side.

@thisisabhash thisisabhash left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refer to previous comment for review comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants