livecaller_flutter_sdk 1.1.5 copy "livecaller_flutter_sdk: ^1.1.5" to clipboard
livecaller_flutter_sdk: ^1.1.5 copied to clipboard

Livecaller flutter sdk

example/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:livecaller_flutter_sdk/livecaller_flutter_sdk.dart';

/// Your widget id from the Livecaller dashboard.
///
/// Replace with your own value before running.
const String kLivecallerWidgetId = 'YOUR_WIDGET_ID';

/// A JWT issued by *your* backend identifying the current visitor.
///
/// In a real app you would fetch this after the user signs in to your
/// system — do not hard-code production tokens in source.
const String kAuthToken = 'YOUR_VISITOR_JWT';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Start the persistent background listener BEFORE any UI. After the user
  // has opened chat once, the SDK reuses the connection config it persisted
  // during the last session, so a no-arg call is enough on subsequent cold
  // starts. Safe to call when no session exists yet — it just no-ops.
  await LivecallerApp.startBackgroundEvents();

  // App-wide debug listeners — fire on every event regardless of route.
  // These subscriptions live for the process lifetime, so we don't cancel.
  LivecallerEvents.instance.on(
    LivecallerEventType.conversationWasStarted,
    (conversation) => debugPrint('Conversation started: ${conversation['id']}'),
  );
  LivecallerEvents.instance.on(
    LivecallerEventType.conversationMessageReceived,
    (message) => debugPrint('Message received: ${message['content']}'),
  );
  LivecallerEvents.instance.on(
    LivecallerEventType.conversationWasClosed,
    (conversation) => debugPrint('Conversation closed: ${conversation['id']}'),
  );
  LivecallerEvents.instance.on(
    LivecallerEventType.conversationAuthFailed,
    (data) => debugPrint(
      'Conversation auth failed: ${data['conversationId']} '
      '— host should re-login.',
    ),
  );

  runApp(const ProviderScope(child: ExampleApp()));
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Livecaller SDK Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  Future<void> _login(BuildContext context) async {
    await LivecallerApp.userLogin(context, kAuthToken);
    if (!context.mounted) return;
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(const SnackBar(content: Text('Logged in')));
  }

  Future<void> _logout(BuildContext context) async {
    await LivecallerApp.userLogout(context);
    await LivecallerApp.stopBackgroundEvents();
    if (!context.mounted) return;
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(const SnackBar(content: Text('Logged out')));
  }

  void _openChat(BuildContext context) {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      useSafeArea: true,
      backgroundColor: Colors.transparent,
      barrierColor: Colors.black54,
      builder: (_) => DraggableScrollableSheet(
        initialChildSize: 1,
        minChildSize: 0.6,
        maxChildSize: 1.0,
        builder: (_, __) => Container(
          decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
          ),
          child: Column(
            children: [
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 12),
                child: Container(
                  width: 40,
                  height: 4,
                  decoration: BoxDecoration(
                    color: Colors.grey.shade400,
                    borderRadius: BorderRadius.circular(4),
                  ),
                ),
              ),
              const Expanded(
                child: LivecallerApp(
                  widgetId: kLivecallerWidgetId,
                  language: 'en',
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Livecaller SDK Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton.icon(
              onPressed: () => _login(context),
              icon: const Icon(Icons.login),
              label: const Text('Login'),
            ),
            const SizedBox(height: 12),
            ElevatedButton.icon(
              onPressed: () => _logout(context),
              icon: const Icon(Icons.logout),
              label: const Text('Logout'),
            ),
            const SizedBox(height: 24),
            ElevatedButton.icon(
              onPressed: () => _openChat(context),
              icon: const Icon(Icons.chat),
              label: const Text('Open Chat'),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(
                  horizontal: 32,
                  vertical: 16,
                ),
              ),
            ),
            const SizedBox(height: 12),
            ElevatedButton.icon(
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const NotificationsPage()),
              ),
              icon: const Icon(Icons.notifications),
              label: const Text('Notifications'),
            ),
          ],
        ),
      ),
    );
  }
}

/// Demonstrates subscribing to Livecaller events from a host screen that is
/// *not* the chat widget itself. The background listener keeps events
/// flowing while the user browses the host app.
class NotificationsPage extends StatefulWidget {
  const NotificationsPage({super.key});

  @override
  State<NotificationsPage> createState() => _NotificationsPageState();
}

class _NotificationsPageState extends State<NotificationsPage> {
  final List<StreamSubscription<LivecallerEvent>> _subs = [];

  @override
  void initState() {
    super.initState();
    _subs.addAll([
      LivecallerEvents.instance.on(
        LivecallerEventType.conversationWasStarted,
        (_) => _notify('Conversation started', Colors.blue),
      ),
      LivecallerEvents.instance.on(
        LivecallerEventType.conversationMessageReceived,
        (message) {
          final content = message['content']?.toString() ?? '(no body)';
          _notify('New message: $content', Colors.green);
        },
      ),
      LivecallerEvents.instance.on(
        LivecallerEventType.conversationWasClosed,
        (_) => _notify('Conversation closed', Colors.orange),
      ),
      LivecallerEvents.instance.on(
        LivecallerEventType.conversationAuthFailed,
        (_) => _notify('Auth failed — please log in again', Colors.red),
      ),
    ]);
  }

  void _notify(String text, Color color) {
    if (!mounted) return;
    ScaffoldMessenger.of(context)
      ..clearSnackBars()
      ..showSnackBar(
        SnackBar(
          content: Text(text, style: const TextStyle(color: Colors.white)),
          backgroundColor: color,
          behavior: SnackBarBehavior.floating,
          duration: const Duration(seconds: 3),
        ),
      );
  }

  @override
  void dispose() {
    for (final s in _subs) {
      s.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Notifications'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: const Center(
        child: Padding(
          padding: EdgeInsets.all(24),
          child: Text(
            'Livecaller notifications appear here as snackbars while you '
            'stay on this page — even though the chat widget is closed.',
            textAlign: TextAlign.center,
          ),
        ),
      ),
    );
  }
}