Skip to content

Latest commit

 

History

History
1419 lines (1172 loc) · 45.1 KB

File metadata and controls

1419 lines (1172 loc) · 45.1 KB

Seam Components - How to Use

This guide provides comprehensive documentation on how to use the Seam Components library, which offers both complete screen solutions and individual UI components for building credential management applications.

Table of Contents

  1. Installation & Setup
  2. Quick Start - Complete Integration
  3. Initialization & Authentication
  4. Screen Components
  5. UI Components
  6. Theming and Customization
  7. Error Handling
  8. Component Relationships and Integration

Installation & Setup

Adding Dependencies

Add the Seam Components library and required SDK modules to your app's build.gradle.kts (or build.gradle for Groovy):

Kotlin DSL (build.gradle.kts):

dependencies {
    val seamVersion = "3.1.1" // Check for latest version

    // Required: Seam Components UI library
    implementation("co.seam:seam-phone-sdk-android-seamcomponents:$seamVersion")

    // Required: Core SDK functionality
    implementation("co.seam:seam-phone-sdk-android-core:$seamVersion")

    // Optional: Add integration modules as needed based on your lock providers
    implementation("co.seam:seam-phone-sdk-android-assaabloy:$seamVersion")  // minSdk 28
    implementation("co.seam:seam-phone-sdk-android-latch:$seamVersion")      // minSdk 26
    implementation("co.seam:seam-phone-sdk-android-saltoks:$seamVersion")    // minSdk 24
    implementation("co.seam:seam-phone-sdk-android-saltospace:$seamVersion") // minSdk 24
}

Groovy (build.gradle):

dependencies {
    def seamVersion = "3.1.1" // Check for latest version

    // Required: Seam Components UI library
    implementation "co.seam:seam-phone-sdk-android-seamcomponents:$seamVersion"

    // Required: Core SDK functionality
    implementation "co.seam:seam-phone-sdk-android-core:$seamVersion"

    // Optional: Add integration modules as needed
    implementation "co.seam:seam-phone-sdk-android-assaabloy:$seamVersion"  // minSdk 28
    implementation "co.seam:seam-phone-sdk-android-latch:$seamVersion"      // minSdk 26
    implementation "co.seam:seam-phone-sdk-android-saltoks:$seamVersion"    // minSdk 24
    implementation "co.seam:seam-phone-sdk-android-saltospace:$seamVersion" // minSdk 24
}

Repository Configuration

Configure your settings.gradle.kts (or settings.gradle) to access Seam's GitHub Packages repository:

Kotlin DSL (settings.gradle.kts):

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/seampkg/seam-mobile-sdk")
            credentials {
                username = getPropertyOrNull("seamUsername")
                password = getPropertyOrNull("seamPat")
            }
        }
    }
}

// Helper function to read from local.properties
fun getPropertyOrNull(propertyName: String): String? {
    val propertiesFile = file("local.properties")
    if (!propertiesFile.exists()) return null
    val properties = java.util.Properties()
    properties.load(propertiesFile.inputStream())
    return properties.getProperty(propertyName)
}

GitHub Packages Authentication

Create a local.properties file in your project root with your GitHub credentials:

# local.properties (DO NOT commit this file)
seamUsername=YOUR_GITHUB_USERNAME
seamPat=YOUR_SEAM_PROVIDED_PAT

Note: Contact Seam support to obtain a Personal Access Token (PAT) with read:packages scope.

Minimum SDK Requirements

Set your app's minSdk in build.gradle.kts based on the integration modules you're using:

android {
    defaultConfig {
        minSdk = 26 // or higher based on integration requirements:
        // 24 for base SDK + Salto
        // 26 for Latch integration
        // 28 for Assa Abloy integration
    }
}

Quick Start - Complete Integration

SeamAccessView - Your Complete Solution

SeamAccessView is the main entry point composable for the Seam access management interface. This powerful component manages the entire user flow for accessing and managing credentials through the Seam SDK, making it the perfect choice for most applications.

What SeamAccessView handles for you:

  • SDK Initialization: Automatically initializes the Seam SDK with your session token
  • Navigation Management: Handles screen transitions and navigation state
  • UI State Management: Manages loading, error handling, and different application states
  • OTP Authorization: Automatically handles OTP verification flows when required
  • Bluetooth Permissions: Guides users through Bluetooth setup when needed for credential operations

Screen Orchestration: SeamAccessView automatically navigates between these screens based on application state:

  • Credentials List Screen: For viewing available keys with pull-to-refresh functionality
  • OTP Authorization Screen: For completing authentication flows in a WebView
  • Bluetooth Redirect Screen: For handling Bluetooth permission and setup
  • Unlock Overlay: For interacting with individual credentials through a modal interface

Basic Usage

For most use cases, you can use SeamAccessView as a complete solution:

@Composable
fun MyApp() {
    SeamThemeProvider {
        SeamAccessView(
            clientSessionToken = "your-session-token-here"
        )
    }
}

With Custom Navigation

If you need more control over navigation or want to integrate with your existing navigation setup:

@Composable
fun MyApp() {
    val navController = rememberNavController()

    SeamThemeProvider {
        SeamAccessView(
            clientSessionToken = "your-session-token-here",
            context = LocalContext.current,
            navController = navController
        )
    }
}

Parameters:

  • clientSessionToken: Required session token for SDK authentication and initialization
  • context: Android context (defaults to current composition's local context)
  • navController: Navigation controller for screen transitions (defaults to a new instance)

Initialization & Authentication

Client Session Token (CST) Management

Client Session Tokens authenticate your mobile app with Seam's backend. These tokens should be generated by your backend server using Seam's API.

Obtaining a CST

Ask Seam about how to obtain a CST. For more details see Seam's Client Session Token documentation.

Token Storage and Security

If you need to store your CST do it securely using Android's encrypted storage:

class SecureTokenStorage(context: Context) {
    private val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

    private val sharedPreferences = EncryptedSharedPreferences.create(
        context,
        "seam_secure_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    fun saveToken(token: String) {
        sharedPreferences.edit()
            .putString("client_session_token", token)
            .apply()
    }

    fun getToken(): String? {
        return sharedPreferences.getString("client_session_token", null)
    }

    fun clearToken() {
        sharedPreferences.edit()
            .remove("client_session_token")
            .apply()
    }
}

SDK Initialization

When using individual components instead of SeamAccessView, you must manually initialize the Seam SDK:

@Composable
fun MyCredentialsScreen(clientSessionToken: String) {
    val context = LocalContext.current

    LaunchedEffect(clientSessionToken) {
        try {
            // Initialize the SDK. This part is usually done in a ViewModel
            SeamSDK.initialize(
                context = context,
                clientSessionToken = clientSessionToken
            )

            // Activate the SDK after initialization
            SeamSDK.getInstance().activate()
        } catch (error: SeamError) {
            // handle error
        }
    }

    SeamCredentialsView(
        onNavigateToUnlock = { /* Handle navigation */ }
    )
}

Screen Components

When you need more granular control than SeamAccessView provides, you can use individual screen components to build custom flows while still benefiting from Seam's pre-built UI.

SeamCredentialsView

What it does: A composable screen that displays cards for user credentials (keys) with comprehensive state management and user interaction support.

Key Features:

  • State Management: Automatically handles loading, success with data, and empty states
  • Pull-to-Refresh: Built-in refresh functionality for updating credential data
  • Error Handling: Displays appropriate user feedback for error states
  • Navigation Integration: Provides callback for seamless navigation to unlock interfaces
  • Internet Status: Shows connection status and handles offline scenarios

When to use: When you want a complete credentials listing screen but need to customize the navigation or integrate with your own view models and navigation system.

@Composable
fun CredentialsScreen() {
    // viewModel is optional
    val viewModel: KeysViewModel = viewModel()

    // Remember to initialize the SDK before using the view. See previous section for details.
    SeamCredentialsView(
        viewModel = viewModel,
        onNavigateToUnlock = { keyCard ->
            // Navigate to unlock screen
            println("Unlocking key: ${keyCard.name}")
        }
    )
}

SeamUnlockCardView

What it does: A modal bottom sheet composable that provides a complete user interface for unlocking credentials with phase-based state management.

Key Features:

  • Modal Presentation: Displays as an overlay bottom sheet that doesn't disrupt the main UI
  • Unlock Phases: Manages different states (idle, scanning, success, failed) with appropriate UI feedback
  • Theme Integration: Supports customization through SeamUnlockCardStyle theming
  • Automatic State Reset: Handles cleanup when dismissed or navigation occurs
  • Error Recovery: Built-in error states with retry functionality

When to use: When you want to provide unlock functionality in a modal format, either triggered from your custom credential list or integrated into your own navigation flow.

@Composable
fun UnlockModal(keyCard: KeyCard) {

    // The simplest way to use the unlock view is to call it on `onNavigateToUnlock`
    // callback from `SeamCredentialsView`, as it already provides the keyCard

    SeamUnlockCardView(
        keyCard = keyCard,
        onNavigateBack = { showUnlock = false }
    )
}

SeamOtpView

What it does: A full-screen dialog composable that displays an OTP (One-Time Password) verification interface using a WebView for interactive authentication flows.

Key Features:

  • Full-Screen Presentation: Renders as a dialog that overlays the host app's UI completely
  • WebView Integration: Handles OTP verification flows with JavaScript enabled for interactivity
  • Custom Navigation: Includes a styled top bar with back navigation controls
  • URL Handling: Loads the provided OTP URL and manages the verification process
  • Dialog Management: Proper dismissal handling that integrates with your app's navigation

When to use: When your authentication flow requires OTP verification and you want a seamless, full-screen experience that doesn't disrupt your main app navigation.

@Composable
fun OtpScreen() {
    SeamOtpView(
        otpUrl = "https://example.com/otp-verification",
        onNavigateBack = {
            // Handle navigation back
        }
    )
}

UI Components

For maximum flexibility, Seam provides individual UI components that you can combine to build completely custom interfaces. These components are organized into categories based on their functionality and can be mixed and matched according to your needs.

Component Categories:

Common Components

Essential UI components for handling common application states and user interactions.

LoadingContent

Display loading states with customizable text:

@Composable
fun MyLoadingScreen() {
    LoadingContent(
        title = "Loading your mobile keys..."
    )
}

// With default loading text
@Composable
fun SimpleLoading() {
    LoadingContent()
}

ErrorContent

Show error states with retry functionality:

@Composable
fun MyErrorScreen() {
    ErrorContent(
        errorState = SeamErrorState.InternetConnectionRequired,
        onRetry = {
            // Retry the failed operation
        }
    )
}

ErrorBanner

A dismissible banner component that displays error messages at the top of the screen.

@Composable
fun MyScreen() {
    var errorMessage by remember { mutableStateOf<String?>(null) }

    Column {
        ErrorBanner(
            errorMessage = errorMessage,
            onDismiss = { errorMessage = null }
        )

        // Your main content
        Button(
            onClick = { errorMessage = "Something went wrong!" }
        ) {
            Text("Trigger Error")
        }
    }
}

Buttons

Seam provides three button variants:

@Composable
fun ButtonExamples() {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // Primary button (full-width)
        SeamPrimaryButton(
            buttonText = "Unlock Door",
            onClick = { /* Primary action */ }
        )

        // Secondary button (full-width with border)
        SeamSecondaryButton(
            buttonText = "Cancel",
            onClick = { /* Secondary action */ }
        )

        // Custom button with full control
        SeamButton(
            text = "Custom Button",
            backgroundColor = Color.Red,
            textColor = Color.White,
            borderColor = Color.DarkRed,
            isFullWidth = false,
            onClick = { /* Custom action */ }
        )
    }
}

Key Components

Components specifically designed for displaying and managing credential keys.

KeyCardComponent

What it does: A composable that displays a stylized key card with gradient background and comprehensive credential information. This is the visual representation of your mobile keys.

Key Features:

  • Rich Visual Design: Gradient backgrounds with customizable theming through SeamKeyCardStyle
  • Hotel/Location Information: Displays room names, access codes, and checkout dates with proper formatting
  • Brand Logo Support: Optional brand logo display (URL or resource-based) with fallback handling
  • Multiple States: Supports loading, expired, and active states with appropriate visual indicators
  • Interactive/Display Modes: Can be configured for interaction or display-only purposes
  • Accessibility: Proper content descriptions and interaction feedback

Card States:

  • Active: Normal interactive state with full functionality
  • Loading: Shows loading indicator overlay when credential is being processed
  • Expired: Displays error indicator and disables interaction for expired credentials
@Composable
fun KeyCardExample() {
    val keyCard = KeyCard(
        id = "key-1",
        location = "Grand Hotel & Spa",
        name = "1205",
        checkoutDate = LocalDateTime.now().plusDays(2),
        code = "1234"
    )

    KeyCardComponent(
        keyCard = keyCard,
        onPress = {
            // Handle key card tap
            println("Key card pressed: ${keyCard.name}")
        }
    )
}

EmptyKeysState

What it does: A composable that displays an empty state when no keys are available, providing user guidance and refresh functionality.

Key Features:

  • Informative Design: Shows illustration, title, and subtitle to inform users about the empty state
  • Refresh Action: Includes a refresh button to allow users to retry loading keys
  • Consistent Styling: Integrates with Seam theme for consistent visual appearance
  • User Guidance: Clear messaging about why no keys are shown and what users can do

When to use: Display this when credential loading results in no available keys, or when users need to refresh their credential list.

@Composable
fun EmptyStateExample() {
    EmptyKeysState(
        onRefresh = {
            // Refresh keys
            println("Refreshing keys...")
        }
    )
}

Unlock Components

Specialized components for building custom unlock interfaces. These components work together to create comprehensive unlock experiences, from user instructions to visual feedback during the unlock process.

Core Unlock Flow Components:

CircleSpinner

What it does: A customizable animated circular spinner composable with gradient effects and optional content support.

Key Features:

  • Smooth Animation: Continuous 360-degree rotation with linear easing for smooth visual feedback
  • Gradient Effects: Uses sweep gradient with customizable colors from SeamUnlockCardStyle theming
  • Flexible Sizing: Configurable diameter and border width for different use cases
  • Background Ring: Optional subtle background ring to enhance visual contrast
  • Content Support: Can display any composable content in the center (text, icons, etc.)
  • Theme Integration: Colors automatically adapt to your Seam theme configuration

Customization Options:

  • size: Spinner diameter in dp (default: 160)
  • borderWidth: Stroke width in pixels (default: 16f)
  • showBackgroundRing: Display background ring (default: false)
  • content: Optional centered content
@Composable
fun SpinnerExamples() {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        // Basic spinner
        CircleSpinner()

        // Spinner with custom size and background ring
        CircleSpinner(
            size = 120,
            borderWidth = 12f,
            showBackgroundRing = true
        )

        // Spinner with centered content
        CircleSpinner(
            size = 160,
            showBackgroundRing = true
        ) {
            Text(
                text = "Loading...",
                color = MaterialTheme.colorScheme.primary
            )
        }
    }
}

UnlockButton

What it does: A central unlock button that dynamically adapts its appearance and behavior based on the current unlock phase, providing visual feedback throughout the unlock process.

Key Features:

  • Phase-Based States: Different visual representations for each unlock phase
  • Theme Integration: Fully customizable through SeamUnlockCardStyle theming
  • Smooth Transitions: Seamless visual transitions between states
  • Accessibility: Proper content descriptions and interaction feedback

Visual States:

  • IDLE: Gradient button with key icon, ready to initiate unlock
  • SCANNING: Animated CircleSpinner around key icon during unlock attempt
  • SUCCESS: Green background with checkmark icon indicating successful unlock
  • FAILED: No visual representation (error handling is managed by other components)

Styling Options (via theming):

  • Button gradient colors and key icon colors for different states
  • Success color and icon customization
  • Shadow and elevation effects
@Composable
fun UnlockButtonExample() {
    var phase by remember { mutableStateOf(UnlockPhase.IDLE) }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        UnlockButton(
            unlockPhase = phase,
            onPress = {
                when (phase) {
                    UnlockPhase.IDLE -> phase = UnlockPhase.SCANNING
                    UnlockPhase.SCANNING -> phase = UnlockPhase.SUCCESS
                    UnlockPhase.SUCCESS -> phase = UnlockPhase.IDLE
                    UnlockPhase.FAILED -> phase = UnlockPhase.IDLE
                }
            }
        )

        // Control buttons for demo
        Row(
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(onClick = { phase = UnlockPhase.IDLE }) { Text("Idle") }
            Button(onClick = { phase = UnlockPhase.SCANNING }) { Text("Scanning") }
            Button(onClick = { phase = UnlockPhase.SUCCESS }) { Text("Success") }
            Button(onClick = { phase = UnlockPhase.FAILED }) { Text("Failed") }
        }
    }
}

StatusMessage

Display status information during unlock:

@Composable
fun StatusMessageExample() {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        StatusMessage(
            statusText = "Tap to Unlock",
            instructionText = null
        )

        StatusMessage(
            statusText = "Connecting...",
            instructionText = "Please hold your phone near the lock"
        )

        StatusMessage(
            statusText = "Success!",
            instructionText = "Door unlocked successfully"
        )
    }
}

UnlockHeader

Display key card information in unlock interfaces:

@Composable
fun UnlockHeaderExample() {
    val keyCard = KeyCard(
        id = "key-1",
        location = "Grand Hotel & Spa",
        name = "Room 1205",
        checkoutDate = LocalDateTime.now().plusDays(2),
        code = "1234"
    )

    UnlockHeader(
        keyCard = keyCard
    )
}

UnlockContent

Main unlock interface content:

@Composable
fun UnlockContentExample() {
    var phase by remember { mutableStateOf(UnlockPhase.IDLE) }

    UnlockContent(
        unlockPhase = phase,
        onPressPrimaryButton = {
            phase = when (phase) {
                UnlockPhase.IDLE -> UnlockPhase.SCANNING
                UnlockPhase.SCANNING -> UnlockPhase.SUCCESS
                else -> UnlockPhase.IDLE
            }
        }
    )
}

UnlockError

Display error states with retry:

@Composable
fun UnlockErrorExample() {
    UnlockError(
        title = "Connection Failed",
        description = "Unable to connect to the lock. Please check your connection and try again.",
        onTryAgain = {
            // Retry unlock operation
            println("Retrying unlock...")
        }
    )
}

UnlockInstructions

Step-by-step unlock instructions:

@Composable
fun InstructionsExample() {
    UnlockInstructions()
}

Bluetooth Components

Components for handling Bluetooth setup and permissions required for credential operations.

BluetoothRedirectScreen

What it does: A full-screen composable that guides users to enable Bluetooth when it's required for key operations, providing clear instructions and system integration.

Key Features:

  • Clear Guidance: Explains why Bluetooth is needed with informative title and description
  • System Integration: Includes button that directly opens Android Bluetooth settings
  • Flexible UX: Optional skip functionality for non-critical Bluetooth requirements
  • Consistent Design: Follows Seam design patterns with proper spacing and typography
  • Icon Support: Uses appropriate Bluetooth icon for visual clarity

When to use: Display this screen when Bluetooth is disabled but required for credential operations. Commonly used by SeamAccessView automatically, but can be used independently for custom flows.

Skip vs. Required Modes:

  • With Skip: Allows users to proceed without Bluetooth (for non-critical features)
  • Required Mode: Forces users to enable Bluetooth before proceeding (set onSkipClicked to null)
@Composable
fun BluetoothSetup() {
    BluetoothRedirectScreen(
        onSkipClicked = {
            // Handle skip action
            println("User skipped Bluetooth setup")
        }
    )
}

// Without skip option
@Composable
fun BluetoothSetupRequired() {
    BluetoothRedirectScreen(
        onSkipClicked = null // Hide skip button
    )
}

Theming and Customization

Seam Mobile Components are fully customizable using Material 3 theming integrated with Seam's component-specific styling system. You can customize colors, typography, and detailed component styles to match your brand.

Basic Theme Setup with Material 3

The simplest way to theme Seam components is to use SeamComponentsTheme with your Material 3 color schemes:

@Composable
fun MyApp() {
    val lightColors = lightColorScheme(
        primary = Color(0xFF1976D2),
        onPrimary = Color.White,
        secondary = Color(0xFF757575),
        onSecondary = Color.White,
        background = Color.White,
        onBackground = Color(0xFF212121)
    )

    val darkColors = darkColorScheme(
        primary = Color(0xFF90CAF9),
        onPrimary = Color(0xFF003C7E),
        secondary = Color(0xFF424242),
        onSecondary = Color.White,
        background = Color(0xFF121212),
        onBackground = Color.White
    )

    val colorScheme = if (isSystemInDarkTheme()) darkColors else lightColors

    SeamComponentsTheme(
        colorScheme = colorScheme
    ) {
        SeamAccessView(
            clientSessionToken = "your-token"
        )
    }
}

Custom Typography Integration

You can integrate custom typography with Material 3's typography system:

@Composable
fun MyThemedApp() {
    val customTypography = Typography(
        headlineLarge = TextStyle(
            fontFamily = FontFamily.Serif,
            fontWeight = FontWeight.Bold,
            fontSize = 32.sp
        ),
        bodyLarge = TextStyle(
            fontFamily = FontFamily.SansSerif,
            fontWeight = FontWeight.Normal,
            fontSize = 16.sp
        ),
        labelMedium = TextStyle(
            fontFamily = FontFamily.Monospace,
            fontWeight = FontWeight.Medium,
            fontSize = 12.sp,
            letterSpacing = 0.5.sp
        )
    )

    val colorScheme = lightColorScheme(
        primary = Color(0xFF6366F1),
        onPrimary = Color.White
    )

    SeamComponentsTheme(
        colorScheme = colorScheme,
        typography = customTypography
    ) {
        SeamAccessView(
            clientSessionToken = "your-token"
        )
    }
}

Component-Specific Theming

For detailed customization, you can use Seam's component-specific styling with the seamTheme parameter:

@Composable
fun BrandedSeamApp() {
    val hotelBranding = SeamTheme(
        keyCard = SeamKeyCardStyle(
            backgroundGradient = listOf(
                Color(0xFF1E3A8A), // Deep blue
                Color(0xFF3B82F6)  // Bright blue
            ),
            accentColor = Color(0xFFEAB308), // Gold accent
            cornerRadius = 16.dp,
            textColor = Color.White,
            shadowColor = Color(0x33000000),
            shadowYOffset = 8.dp,
            brandLogoUrl = "https://example.com/hotel-logo.png"
        ),
        unlockCard = SeamUnlockCardStyle(
            cardBackground = Color(0xFF1F2937),
            headerBackground = Color(0xFF111827),
            headerTitleColor = Color.White,
            keyIconColorIdle = Color(0xFFEAB308),
            keyIconColorActive = Color.White,
            successColor = Color(0xFF10B981)
        )
    )

    val colorScheme = lightColorScheme(
        primary = Color(0xFF1E3A8A),
        onPrimary = Color.White
    )

    SeamComponentsTheme(
        seamTheme = hotelBranding,
        colorScheme = colorScheme
    ) {
        SeamAccessView(
            clientSessionToken = "your-token"
        )
    }
}

SeamKeyCardStyle Properties

Complete customization options for key card appearance:

SeamKeyCardStyle(
    // Brand Integration
    brandLogoUrl = "https://example.com/logo.png",      // Remote logo URL
    brandLogoRes = R.drawable.brand_logo,               // Local drawable resource
    backgroundTextureRes = R.drawable.card_texture,     // Background texture overlay

    // Visual Design
    backgroundGradient = listOf(Color.Blue, Color.Cyan), // Gradient colors (top to bottom)
    accentColor = Color(0xFFFFD700),                     // Accent bar color
    textColor = Color.White,                             // All text color on card

    // Layout & Effects
    cornerRadius = 20.dp,                                // Card corner radius
    shadowColor = Color(0x44000000),                     // Shadow color
    shadowYOffset = 6.dp,                                // Shadow vertical offset

    // State Colors
    errorColor = Color.Red                               // Error state indicator color
)

SeamUnlockCardStyle Properties

Complete customization options for unlock interface:

SeamUnlockCardStyle(
    // Container Backgrounds
    cardBackground = Color(0xFF1F2937),          // Main card background
    headerBackground = Color(0xFF111827),        // Header section background

    // Header Text Colors
    headerTitleColor = Color.White,              // Main header text color
    headerSubtitleColor = Color(0xFFD1D5DB),     // Subtitle text color

    // Key Icon Colors
    keyIconColorIdle = Color(0xFFEAB308),        // Key icon when idle
    keyIconColorActive = Color.White,            // Key icon when active

    // Button Styling
    keyButtonGradient = listOf(                  // Button gradient colors
        Color(0xFF3B82F6),                       // Top color
        Color(0xFF1E40AF)                        // Bottom color
    ),
    keyButtonShadowColor = Color(0x33000000),    // Button shadow color
    keyButtonShadowRadius = 12.dp,               // Button shadow blur radius

    // Instructional Elements
    instructionTextColor = Color(0xFFD1D5DB),    // Instruction text color
    bulletBackground = Color(0xFF3B82F6),        // Bullet point background
    bulletTextColor = Color.White,               // Bullet point text color

    // State Colors
    successColor = Color(0xFF10B981),            // Success state color
    successIconColor = Color.White,              // Success icon color
    errorColor = Color(0xFFEF4444)               // Error state color
)

Practical Theming Examples

Hotel Chain Branding

@Composable
fun HotelChainTheme() {
    val hotelTheme = SeamTheme(
        keyCard = SeamKeyCardStyle(
            backgroundGradient = listOf(
                Color(0xFF8B5A3C),  // Warm brown
                Color(0xFF6B4226)   // Dark brown
            ),
            accentColor = Color(0xFFD4AF37),        // Gold
            textColor = Color(0xFFF5F5DC),          // Beige
            cornerRadius = 12.dp,
            brandLogoUrl = "https://hotel.com/logo.png",
            shadowColor = Color(0x55000000),
            shadowYOffset = 4.dp
        ),
        unlockCard = SeamUnlockCardStyle(
            cardBackground = Color(0xFF2D1810),      // Dark brown
            headerBackground = Color(0xFF1A0F08),    // Very dark brown
            keyIconColorIdle = Color(0xFFD4AF37),    // Gold
            keyButtonGradient = listOf(
                Color(0xFFD4AF37),                   // Gold
                Color(0xFFB8941F)                    // Dark gold
            ),
            successColor = Color(0xFF22C55E)
        )
    )

    SeamComponentsTheme(
        seamTheme = hotelTheme,
        colorScheme = lightColorScheme(primary = Color(0xFF8B5A3C))
    ) {
        SeamAccessView(clientSessionToken = "token")
    }
}

Corporate Technology Branding

@Composable
fun TechCorpTheme() {
    val techTheme = SeamTheme(
        keyCard = SeamKeyCardStyle(
            backgroundGradient = listOf(
                Color(0xFF0F172A),  // Slate 900
                Color(0xFF1E293B)   // Slate 800
            ),
            accentColor = Color(0xFF06B6D4),        // Cyan
            textColor = Color(0xFFF8FAFC),          // Slate 50
            cornerRadius = 8.dp,                    // Sharp corners
            brandLogoRes = R.drawable.tech_logo,
            shadowColor = Color(0x66000000),
            shadowYOffset = 8.dp
        ),
        unlockCard = SeamUnlockCardStyle(
            cardBackground = Color(0xFF020617),      // Slate 950
            headerBackground = Color(0xFF0F172A),    // Slate 900
            keyIconColorIdle = Color(0xFF06B6D4),    // Cyan
            keyIconColorActive = Color(0xFF67E8F9),  // Cyan 300
            keyButtonGradient = listOf(
                Color(0xFF0891B2),                   // Cyan 600
                Color(0xFF0E7490)                    // Cyan 700
            ),
            successColor = Color(0xFF10B981),        // Emerald 500
            bulletBackground = Color(0xFF06B6D4),
            instructionTextColor = Color(0xFF94A3B8) // Slate 400
        )
    )

    SeamComponentsTheme(
        seamTheme = techTheme,
        colorScheme = darkColorScheme(
            primary = Color(0xFF06B6D4),
            background = Color(0xFF020617)
        )
    ) {
        SeamAccessView(clientSessionToken = "token")
    }
}

Error Handling

Error States

Seam Components handle errors automatically when using SeamAccessView. For custom implementations using individual components, you'll need to handle errors through ViewModels.

Available Error States

sealed class SeamErrorState {
    object InitializationRequired : SeamErrorState()
    object ActivationRequired : SeamErrorState()
    object IntegrationNotFound : SeamErrorState()
    object AlreadyInitialized : SeamErrorState()
    object DeactivationInProgress : SeamErrorState()
    object InvalidCredentialId : SeamErrorState()
    object InternetConnectionRequired : SeamErrorState()
    object InvalidClientSessionToken : SeamErrorState()
    object InvalidUnlockProximity : SeamErrorState()
    object Unknown : SeamErrorState()
}

Using SeamAccessView (Automatic Error Handling)

When using SeamAccessView, errors are handled automatically with built-in UI:

@Composable
fun MyApp() {
    SeamAccessView(
        clientSessionToken = "seam_cst_..."
    )
    // Errors are automatically displayed with retry options
}

Using Individual Components with ViewModels

When using individual components, implement error handling in your ViewModel:

class MySeamViewModel : ViewModel() {
    private val _sdkState = MutableStateFlow<SeamSDKState>(SeamSDKState.Idle)
    val sdkState: StateFlow<SeamSDKState> = _sdkState.asStateFlow()

    sealed class SeamSDKState {
        object Idle : SeamSDKState()
        object Initializing : SeamSDKState()
        object Initialized : SeamSDKState()
        data class Error(val message: String) : SeamSDKState()
    }

    fun initializeSDK(context: Context, token: String) {
        viewModelScope.launch {
            _sdkState.value = SeamSDKState.Initializing
            try {
                SeamSDK.initialize(context, token)
                SeamSDK.getInstance().activate()
                _sdkState.value = SeamSDKState.Initialized
            } catch (e: SeamError) {
                _sdkState.value = SeamSDKState.Error(
                    when (e) {
                        is SeamError.InvalidClientSessionToken -> "Invalid token"
                        is SeamError.InternetConnectionRequired -> "No internet connection"
                        else -> e.message ?: "Unknown error"
                    }
                )
            }
        }
    }
}

// Usage in Composable
@Composable
fun CustomCredentialsScreen(viewModel: MySeamViewModel) {
    val sdkState by viewModel.sdkState.collectAsState()

    when (sdkState) {
        is SeamSDKState.Error -> {
            ErrorContent(
                errorState = SeamErrorState.Unknown,
                onRetry = {
                    viewModel.initializeSDK(context, token)
                }
            )
        }
        SeamSDKState.Initialized -> {
            SeamCredentialsView(
                onNavigateToUnlock = { /* Handle navigation */ }
            )
        }
        else -> {
            LoadingContent()
        }
    }
}

Handling Unlock Errors

class UnlockViewModel : ViewModel() {
    private val _unlockErrors = MutableStateFlow<List<SeamError>>(emptyList())
    val unlockErrors: StateFlow<List<SeamError>> = _unlockErrors.asStateFlow()

    fun unlockCredential(credentialId: String) {
        viewModelScope.launch {
            try {
                SeamSDK.getInstance().unlock(
                    credentialId = credentialId,
                    unlockProximity = UnlockProximity.TOUCH
                )
            } catch (e: SeamError) {
                when (e) {
                    is SeamError.CredentialErrors -> {
                        _unlockErrors.value = e.errors
                    }
                    else -> {
                        _unlockErrors.value = listOf(e)
                    }
                }
            }
        }
    }

    fun dismissError(index: Int) {
        _unlockErrors.value = _unlockErrors.value.filterIndexed { i, _ -> i != index }
    }
}

Custom Error Display

You can use the built-in ErrorContent component or create custom error UI:

Using Built-in ErrorContent

@Composable
fun MyScreen() {
    val sdkState by seamViewModel.sdkState.collectAsState()

    when (sdkState) {
        is SeamSDKState.Error -> {
            ErrorContent(
                errorState = sdkState.errorState,
                onRetry = {
                    // Retry initialization
                    seamViewModel.initializeSeamSDK(context, clientSessionToken)
                }
            )
        }
        // ... other states
    }
}

Custom Key Card Implementation

@Composable
fun CustomKeyList() {
    val keys = listOf(
        KeyCard(id = "1", location = "Hotel A", name = "Room 101", /* ... */),
        KeyCard(id = "2", location = "Hotel B", name = "Room 202", /* ... */)
    )

    LazyColumn {
        items(keys) { keyCard ->
            KeyCardComponent(
                keyCard = keyCard,
                onPress = {
                    // Custom unlock logic
                    handleUnlock(keyCard)
                },
                modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
            )
        }
    }
}

private fun handleUnlock(keyCard: KeyCard) {
    // Your custom unlock implementation
}

Building Custom Unlock Flow

@Composable
fun CustomUnlockFlow() {
    var currentPhase by remember { mutableStateOf(UnlockPhase.IDLE) }
    var showError by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // Header
        Text(
            text = "Unlock Room 1205",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(bottom = 24.dp)
        )

        // Main unlock area
        when {
            showError -> {
                UnlockError(
                    title = "Unlock Failed",
                    description = "Please try again",
                    onTryAgain = {
                        showError = false
                        currentPhase = UnlockPhase.IDLE
                    }
                )
            }
            else -> {
                UnlockButton(
                    unlockPhase = currentPhase,
                    onPress = {
                        performUnlock { success ->
                            if (success) {
                                currentPhase = UnlockPhase.SUCCESS
                            } else {
                                showError = true
                            }
                        }
                    }
                )

                Spacer(modifier = Modifier.height(24.dp))

                StatusMessage(
                    statusText = when (currentPhase) {
                        UnlockPhase.IDLE -> "Tap to Unlock"
                        UnlockPhase.SCANNING -> "Connecting..."
                        UnlockPhase.SUCCESS -> "Success!"
                        UnlockPhase.FAILED -> "Failed"
                    },
                    instructionText = when (currentPhase) {
                        UnlockPhase.SCANNING -> "Hold phone near the lock"
                        else -> null
                    }
                )

                if (currentPhase == UnlockPhase.IDLE) {
                    Spacer(modifier = Modifier.height(24.dp))
                    UnlockInstructions()
                }
            }
        }
    }
}

private fun performUnlock(callback: (Boolean) -> Unit) {
    // Simulate unlock process
    // In real implementation, this would interact with the Seam SDK
    callback(true) // or false for failure
}

Error Handling Patterns

When Using Individual Components with ViewModels

@Composable
fun MyCredentialsScreen() {
    val keysViewModel: KeysViewModel = viewModel()
    val seamErrors by keysViewModel.seamErrors.collectAsState()
    val credentialErrorState by keysViewModel.credentialErrorState.collectAsState()

    Box {
        // Handle credential-specific errors that replace the main content
        when (credentialErrorState) {
            is KeysErrorState.InternetConnectionRequired -> {
                ErrorContent(
                    errorState = SeamErrorState.InternetConnectionRequired,
                    onRetry = { keysViewModel.refreshCredentials() }
                )
            }
            else -> {
                SeamCredentialsView(
                    viewModel = keysViewModel,
                    onNavigateToUnlock = { keyCard ->
                        // Handle navigation
                    }
                )

                // Display transient errors as dismissible banners on top
                seamErrors.forEachIndexed { index, error ->
                    ErrorBanner(
                        error = error,
                        onDismiss = { keysViewModel.dismissError(index) }
                    )
                }
            }
        }
    }
}

General Error Handling Pattern

@Composable
fun ErrorHandlingExample() {
    var errorState by remember { mutableStateOf<String?>(null) }
    var isLoading by remember { mutableStateOf(false) }

    Column {
        when {
            isLoading -> {
                LoadingContent(title = "Processing request...")
            }
            errorState != null -> {
                ErrorContent(
                    errorState = SeamErrorState.Unknown,
                    onRetry = {
                        errorState = null
                        // Retry operation
                    }
                )
            }
            else -> {
                // Normal content with optional error banners
                Text("Your content here")

                // Show dismissible error banners for non-critical errors
                errorState?.let { error ->
                    ErrorBanner(
                        errorMessage = error,
                        onDismiss = { errorState = null }
                    )
                }
            }
        }
    }
}

Component Relationships and Integration

Understanding how components work together can help you build more effective custom interfaces:

Complete Flow Components:

  • SeamAccessView → Uses SeamCredentialsView, SeamOtpView, SeamUnlockCardView, and BluetoothRedirectScreen
  • SeamCredentialsView → Uses KeyCardComponent, EmptyKeysState, LoadingContent, and ErrorBanner
  • SeamUnlockCardView → Uses UnlockContent, UnlockHeader, and UnlockError
  • UnlockContent → Uses UnlockButton, StatusMessage, and UnlockInstructions

Standalone Utility Components:

  • Error handling: ErrorContent, ErrorBanner
  • Loading states: LoadingContent, CircleSpinner
  • Buttons: SeamButton, SeamPrimaryButton, SeamSecondaryButton

Whether you choose SeamAccessView for a complete out-of-the-box solution or mix and match individual components to build custom credential management interfaces, you now have all the information needed to integrate Seam's powerful mobile credential capabilities into your application.

For additional support: