clariodesk 0.1.3 copy "clariodesk: ^0.1.3" to clipboard
clariodesk: ^0.1.3 copied to clipboard

ClarioDesk Flutter SDK — in-app customer support chat, bug reporting with auto screenshots, and feedback for mobile apps. Prebuilt UI or headless API.

example/lib/main.dart

import 'dart:io' show Platform;

import 'package:clariodesk/clariodesk.dart';
import 'package:clariodesk/widgets.dart';
import 'package:cupertino_http/cupertino_http.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'inbox_screen.dart';
import 'new_conversation_screen.dart';

// API key is injected at build time via --dart-define so it never lands in
// source. See README.md for the run command. The default base URL points at
// the production API on clariodesk.com so the example works against the real
// dashboard you ship.
const _apiKey = String.fromEnvironment('CLARIODESK_API_KEY');
const _baseUrl = String.fromEnvironment(
  'CLARIODESK_BASE_URL',
  defaultValue: 'https://api.clariodesk.com',
);

// On Apple platforms, dart:io HttpClient sometimes hangs against HTTP/2
// origins (Cloudflare/Fly's edge) under iOS simulators. NSURLSession (what
// Safari uses) doesn't have this problem, so we route the SDK through
// cupertino_http on iOS/macOS. Android keeps the default IOClient.
http.Client? _platformHttpClient() {
  if (kIsWeb) return null;
  if (Platform.isIOS || Platform.isMacOS) {
    return CupertinoClient.fromSessionConfiguration(
      URLSessionConfiguration.defaultSessionConfiguration(),
    );
  }
  return null;
}

Future<void> main() async {
  if (_apiKey.isEmpty) {
    runApp(const _MissingKeyApp());
    return;
  }
  // init() is async on the device-signed protocol — the first launch
  // generates the hardware key + registers with the backend. Awaiting is
  // optional (subsequent calls auto-await the in-flight bootstrap) but the
  // example does so to surface registration failures up front.
  await ClarioDesk.init(
    apiKey: _apiKey,
    baseUrl: Uri.parse(_baseUrl),
    httpClient: _platformHttpClient(),
  );
  runApp(const ClarioDeskExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    final scheme = ColorScheme.fromSeed(seedColor: const Color(0xFFD7FF3D));
    return MaterialApp(
      title: 'ClarioDesk Example',
      theme: ThemeData(colorScheme: scheme, useMaterial3: true),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFFD7FF3D),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const _RootScreen(),
    );
  }
}

// Inbox is the home screen — the Device-Is-Identity protocol makes the SDK
// fully online after `init()` (no login required). The appbar exposes the
// optional flows:
//
//   • "New" / "Bug" — start a conversation or file a bug.
//   • "Pre-built UI" — opens the drop-in ClarioDeskWidgets inbox over the same
//     headless SDK these screens drive by hand (for side-by-side comparison).
//   • "Identify" — attach an externalId/email label to the device. Pure
//     metadata; the dashboard agent sees who they're talking to. Optional.
//   • "Reset device" — best-effort revokes server-side, wipes the local
//     hardware key, then re-bootstraps a fresh device. Useful for testing the
//     registration flow. The `_session` counter keys the inbox so it
//     re-subscribes (and re-primes) after the wipe.
class _RootScreen extends StatefulWidget {
  const _RootScreen();

  @override
  State<_RootScreen> createState() => _RootScreenState();
}

class _RootScreenState extends State<_RootScreen> {
  bool _resetting = false;
  int _session = 0;

  Future<void> _showIdentifyForm() async {
    await showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      builder: (_) => Padding(
        padding:
            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
        child: const _IdentifyForm(),
      ),
    );
    if (mounted) setState(() {}); // refresh "identified" badge in appbar
  }

  Future<void> _resetAndRebootstrap() async {
    setState(() => _resetting = true);
    try {
      await ClarioDesk.reset();
      await ClarioDesk.init(
        apiKey: _apiKey,
        baseUrl: Uri.parse(_baseUrl),
        httpClient: _platformHttpClient(),
      );
      if (mounted) setState(() => _session++);
    } finally {
      if (mounted) setState(() => _resetting = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ClarioDesk Example'),
        actions: [
          IconButton(
            tooltip: 'New conversation',
            icon: const Icon(Icons.add),
            onPressed: _resetting
                ? null
                : () => Navigator.of(context).push(MaterialPageRoute<void>(
                      builder: (_) => const NewConversationScreen(),
                    )),
          ),
          IconButton(
            tooltip: 'Report a bug',
            icon: const Icon(Icons.bug_report_outlined),
            onPressed: _resetting
                ? null
                : () => Navigator.of(context).push(MaterialPageRoute<void>(
                      builder: (_) => const BugReportScreen(),
                    )),
          ),
          PopupMenuButton<String>(
            tooltip: 'More',
            onSelected: (v) {
              switch (v) {
                case 'prebuilt':
                  ClarioDeskWidgets.openInbox(context);
                case 'identify':
                  _showIdentifyForm();
                case 'reset':
                  _resetAndRebootstrap();
              }
            },
            itemBuilder: (_) => [
              const PopupMenuItem(
                value: 'prebuilt',
                child: Text('Open pre-built UI'),
              ),
              PopupMenuItem(
                value: 'identify',
                child: Text(ClarioDesk.isIdentified
                    ? 'Update user label'
                    : 'Identify (optional)'),
              ),
              const PopupMenuItem(
                value: 'reset',
                child: Text('Reset device'),
              ),
            ],
          ),
        ],
      ),
      body: InboxScreen(key: ValueKey(_session)),
    );
  }
}

class _IdentifyForm extends StatefulWidget {
  const _IdentifyForm();

  @override
  State<_IdentifyForm> createState() => _IdentifyFormState();
}

class _IdentifyFormState extends State<_IdentifyForm> {
  final _externalId = TextEditingController(text: 'demo-user-1');
  final _email = TextEditingController(text: 'demo@example.com');
  bool _busy = false;
  String? _error;

  @override
  void dispose() {
    _externalId.dispose();
    _email.dispose();
    super.dispose();
  }

  Future<void> _submit() async {
    setState(() {
      _busy = true;
      _error = null;
    });
    try {
      await ClarioDesk.identify(
        externalId: _externalId.text.trim(),
        email: _email.text.trim().isEmpty ? null : _email.text.trim(),
      ).timeout(
        const Duration(seconds: 10),
        onTimeout: () => throw Exception(
            'identify timed out after 10s — check network / API URL'),
      );
      if (mounted) Navigator.of(context).pop();
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Tell us who you are',
                style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 4),
            // Demo-facing copy: end-user-friendly because the example is a
            // reference customers might paste into their own app. The technical
            // reality (device-bound key, label is pure metadata) lives in the
            // SDK README, not in front of a person filing a bug.
            Text(
              'Optional. Helps the support team recognize you across tickets '
              'and reply with context. You can file tickets either way.',
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _externalId,
              decoration: const InputDecoration(
                labelText: 'External user id',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 12),
            TextField(
              controller: _email,
              decoration: const InputDecoration(
                labelText: 'Email (optional)',
                border: OutlineInputBorder(),
              ),
            ),
            if (_error != null) ...[
              const SizedBox(height: 12),
              Text(_error!, style: const TextStyle(color: Colors.red)),
            ],
            const SizedBox(height: 16),
            FilledButton(
              onPressed: _busy ? null : _submit,
              child: Text(_busy ? 'Identifying…' : 'Identify'),
            ),
          ],
        ),
      ),
    );
  }
}

class _MissingKeyApp extends StatelessWidget {
  const _MissingKeyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(Icons.key_off, size: 48),
                const SizedBox(height: 16),
                Text('Missing CLARIODESK_API_KEY',
                    style: Theme.of(context).textTheme.titleLarge),
                const SizedBox(height: 8),
                const Text('Pass your key at run time:',
                    textAlign: TextAlign.center),
                const SizedBox(height: 8),
                const SelectableText(
                  'flutter run --dart-define=CLARIODESK_API_KEY=pk_live_…',
                  style: TextStyle(fontFamily: 'monospace'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
0
likes
140
points
206
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

ClarioDesk Flutter SDK — in-app customer support chat, bug reporting with auto screenshots, and feedback for mobile apps. Prebuilt UI or headless API.

Homepage
Repository (GitHub)
View/report issues

Topics

#support #customer-support #chat #bug-reporting #feedback

License

(pending) (license)

Dependencies

centrifuge, connectivity_plus, crypto, device_info_plus, file_selector, flutter, flutter_secure_storage, http, image_picker, package_info_plus, share_plus, url_launcher

More

Packages that depend on clariodesk

Packages that implement clariodesk