infria 0.1.0 copy "infria: ^0.1.0" to clipboard
infria: ^0.1.0 copied to clipboard

A clean, customizable Flutter UI SDK and runtime controller for AI chat interfaces, RAG, and dynamic client-side function calling.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:infria/infria.dart';
import 'infria_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Inisialisasi INFRIA SDK (Developer Experience setara FlutterFire)
  await InfriaSDK.initializeApp(
    options: DefaultInfriaOptions.currentPlatform,
  );

  runApp(const InfriaHealthcareShowcaseApp());
}

class InfriaHealthcareShowcaseApp extends StatefulWidget {
  const InfriaHealthcareShowcaseApp({super.key});

  @override
  State<InfriaHealthcareShowcaseApp> createState() =>
      _InfriaHealthcareShowcaseAppState();
}

class _InfriaHealthcareShowcaseAppState
    extends State<InfriaHealthcareShowcaseApp> {
  ThemeMode _themeMode = ThemeMode.light;

  void _toggleTheme() {
    setState(() {
      _themeMode =
          _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'INFRIA — Healthcare AI Assistant Showcase',
      debugShowCheckedModeBanner: false,
      themeMode: _themeMode,
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.light,
        colorSchemeSeed: const Color(0xFF0284C7),
        scaffoldBackgroundColor: const Color(0xFFF8FAFC),
      ),
      darkTheme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.dark,
        colorSchemeSeed: const Color(0xFF38BDF8),
        scaffoldBackgroundColor: const Color(0xFF0F172A),
      ),
      home: MainHealthcareDashboard(
        onToggleTheme: _toggleTheme,
        isDark: _themeMode == ThemeMode.dark,
      ),
    );
  }
}

class MainHealthcareDashboard extends StatefulWidget {
  final VoidCallback onToggleTheme;
  final bool isDark;

  const MainHealthcareDashboard({
    super.key,
    required this.onToggleTheme,
    required this.isDark,
  });

  @override
  State<MainHealthcareDashboard> createState() =>
      _MainHealthcareDashboardState();
}

class _MainHealthcareDashboardState extends State<MainHealthcareDashboard> {
  int _currentIndex = 0;
  late final ChatController _controller;

  // Pre-configured Healthcare Quick Suggestions for Inclusive UX
  static const List<QuickSuggestion> _healthcareSuggestions = [
    QuickSuggestion(
      label: '🩺 Cek Antrean Poli',
      message: 'Berapa nomor antrean saya di Poli Gigi sekarang?',
      icon: Icons.access_time_rounded,
    ),
    QuickSuggestion(
      label: '📅 Jadwal Dokter',
      message: 'Tampilkan jadwal dokter spesialis anak hari ini',
      icon: Icons.calendar_today_rounded,
    ),
    QuickSuggestion(
      label: '📋 Syarat Rujukan BPJS',
      message: 'Apa saja dokumen persyaratan untuk berobat menggunakan BPJS?',
      icon: Icons.description_rounded,
    ),
    QuickSuggestion(
      label: '❌ Batalkan Janji Temu',
      message: 'Tolong batalkan janji temu dokter saya dengan ID APPT-891',
      icon: Icons.cancel_outlined,
    ),
  ];

  @override
  void initState() {
    super.initState();

    // 1. Registrasi Dynamic Client-Side Function Callbacks via InfriaSDK (DX Ringkas)
    _registerHealthcareFunctions();

    // 2. Inisialisasi ChatController (Auto-binds ke InfriaSDK.instance secara global)
    _controller = ChatController(
      initialMessages: [
        ChatMessage.assistant(
          id: 'welcome_1',
          content:
              'Halo! Selamat datang di **Asisten Layanan Kesehatan Puskesmas Sehat**.\n\nSaya dapat membantu Anda mengecek nomor antrean poli secara langsung, memeriksa jadwal dokter, hingga syarat rujukan BPJS.',
        ),
      ],
      onError: (error) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Kendala komunikasi: $error'),
              backgroundColor: Colors.red.shade700,
            ),
          );
        }
      },
    );
  }

  void _registerHealthcareFunctions() {
    // Fungsi 1: Cek Antrean Poli Real-time
    InfriaSDK.onFunctionCall(
      'check_queue_status',
      (args) async {
        await Future<void>.delayed(const Duration(milliseconds: 900));
        final poli = args['poliId']?.toString() ?? 'Poli Gigi';
        return {
          'poli': poli,
          'nomor_antrean_anda': 'A-15',
          'sedang_dilayani': 'A-13',
          'sisa_antrean': 2,
          'estimasi_menit': 10,
        };
      },
      description: 'Mengecek nomor antrean pasien di sistem poli kesehatan',
      parameters: const [
        FunctionParameter.string('poliId', description: 'Nama atau ID poli'),
      ],
    );

    // Fungsi 2: Cek Jadwal Dokter
    InfriaSDK.onFunctionCall(
      'get_doctor_schedule',
      (args) async {
        await Future<void>.delayed(const Duration(milliseconds: 700));
        return {
          'dokter': 'dr. Amanda Putri, Sp.A',
          'spesialisasi': 'Spesialis Anak',
          'jadwal': 'Senin & Rabu (09:00 - 12:00 WIB)',
          'kuota_tersedia': 8,
          'ruangan': 'Poli Anak Lantai 2',
        };
      },
      description: 'Mengambil jadwal praktek dokter dari SIMRS',
    );

    // Fungsi 3: Cek Status Rujukan BPJS
    InfriaSDK.onFunctionCall(
      'get_bpjs_referral_status',
      (args) async {
        await Future<void>.delayed(const Duration(milliseconds: 800));
        return {
          'status': 'AKTIF',
          'faskes_asal': 'Puskesmas Sukamaju',
          'faskes_tujuan': 'RSUD Sehat Sentosa',
          'masa_berlaku_sampai': '30 September 2026',
        };
      },
      description: 'Mengecek validitas surat rujukan BPJS Kesehatan',
      parameters: const [
        FunctionParameter.string('patientId', description: 'Nomor Kartu BPJS / NIK Pasien'),
      ],
    );

    // Fungsi 4: Batalkan Janji Temu (Memerlukan Konfirmasi Pengguna)
    InfriaSDK.onFunctionCall(
      'cancel_appointment',
      (args) async {
        final confirmed = await showFunctionConfirmationDialog(
          context: context,
          functionName: 'cancel_appointment',
          description:
              'Membatalkan janji temu dokter untuk ID: ${args['appointmentId'] ?? 'APPT-891'}',
        );

        if (!confirmed) {
          return {'status': 'DIBATALKAN_OLEH_PENGGUNA'};
        }

        await Future<void>.delayed(const Duration(milliseconds: 600));
        return {
          'status': 'BERHASIL_DIBATALKAN',
          'id': args['appointmentId'] ?? 'APPT-891',
        };
      },
      description: 'Membatalkan janji temu dokter yang sudah terjadwal',
      parameters: const [
        FunctionParameter.string('appointmentId', description: 'ID Janji Temu (Booking Code)', isRequired: true),
      ],
      requiresConfirmation: true,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Row(
          children: [
            Icon(Icons.local_hospital_rounded,
                color: Color(0xFF0284C7), size: 20),
            SizedBox(width: 8),
            Text(
              'Puskesmas Sehat AI',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
            ),
          ],
        ),
        actions: [
          IconButton(
            tooltip: 'Ganti Tema',
            icon: Icon(widget.isDark
                ? Icons.light_mode_rounded
                : Icons.dark_mode_rounded),
            onPressed: widget.onToggleTheme,
          ),
          IconButton(
            tooltip: 'Hapus Percakapan',
            icon: const Icon(Icons.delete_outline_rounded),
            onPressed: () {
              _controller.clear(newSession: true);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text('Sesi percakapan baru dimulai'),
                  duration: Duration(seconds: 1),
                ),
              );
            },
          ),
        ],
      ),
      body: IndexedStack(
        index: _currentIndex,
        children: [
          // 1. Dashboard View (with Floating Button & Presentation Modals)
          _buildDashboardView(context),

          // 2. Fullscreen Inclusive Chat View with Quick Suggestions & Feedback
          InfriaChat(
            controller: _controller,
            hintText: 'Tanyakan antrean, jadwal dokter, BPJS...',
            suggestions: _healthcareSuggestions,
            enableFeedback: true,
            onFeedback: (msg, rating) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Feedback ${rating.value} tercatat!'),
                  duration: const Duration(seconds: 1),
                ),
              );
            },
          ),

          // 3. Custom Builder Chat View
          _buildCustomBuilderView(context),
        ],
      ),
      floatingActionButton: _currentIndex == 0
          ? InfriaChatFloatingButton(
              controller: _controller,
              tooltip: 'Tanya Asisten AI (Bottom Sheet)',
              suggestions: _healthcareSuggestions,
              enableFeedback: true,
            )
          : null,
      bottomNavigationBar: NavigationBar(
        selectedIndex: _currentIndex,
        onDestinationSelected: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.dashboard_outlined),
            selectedIcon: Icon(Icons.dashboard_rounded),
            label: 'Dashboard (FAB)',
          ),
          NavigationDestination(
            icon: Icon(Icons.forum_outlined),
            selectedIcon: Icon(Icons.forum_rounded),
            label: 'Asisten Penuh',
          ),
          NavigationDestination(
            icon: Icon(Icons.palette_outlined),
            selectedIcon: Icon(Icons.palette_rounded),
            label: 'Custom Builder',
          ),
        ],
      ),
    );
  }

  Widget _buildDashboardView(BuildContext context) {
    final isDark = widget.isDark;

    return SingleChildScrollView(
      padding: const EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Header Card
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(20),
            decoration: BoxDecoration(
              gradient: LinearGradient(
                colors: isDark
                    ? [const Color(0xFF0F172A), const Color(0xFF1E293B)]
                    : [const Color(0xFF0284C7), const Color(0xFF0369A1)],
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
              ),
              borderRadius: BorderRadius.circular(20),
              boxShadow: [
                BoxShadow(
                  color: (isDark ? Colors.black : const Color(0xFF0284C7))
                      .withAlpha(50),
                  blurRadius: 12,
                  offset: const Offset(0, 4),
                ),
              ],
            ),
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Layanan Kesehatan Digital',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 22,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                SizedBox(height: 8),
                Text(
                  'Asisten AI terintegrasi dengan Dynamic Function Calling untuk pengecekan antrean poli, jadwal dokter, dan status BPJS secara inklusif.',
                  style: TextStyle(color: Colors.white70, fontSize: 14),
                ),
              ],
            ),
          ),
          const SizedBox(height: 24),

          // Stat Cards Row
          Row(
            children: [
              Expanded(
                child: _buildStatCard(
                  title: 'Fungsi Terdaftar',
                  value: '${InfriaSDK.instance.functions.count} Layanan',
                  icon: Icons.electrical_services_rounded,
                  color: Colors.teal,
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildStatCard(
                  title: 'Status AI',
                  value: _controller.isBusy ? 'Memproses...' : 'Siap',
                  icon: Icons.check_circle_rounded,
                  color: _controller.isBusy ? Colors.amber : Colors.green,
                ),
              ),
            ],
          ),
          const SizedBox(height: 28),

          const Text(
            'Uji Coba Tampilan Modal (Presentation Modes)',
            style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 14),

          // Action Tiles
          Card(
            elevation: 0,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(16),
              side: BorderSide(
                color: isDark ? Colors.white12 : Colors.black12,
              ),
            ),
            child: Column(
              children: [
                ListTile(
                  leading: Container(
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.blue.withAlpha(30),
                      shape: BoxShape.circle,
                    ),
                    child: const Icon(Icons.call_to_action_rounded,
                        color: Colors.blue),
                  ),
                  title: const Text('Buka Chat via Bottom Sheet'),
                  subtitle: const Text('Tampilan slide-up dari bawah layar'),
                  trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
                  onTap: () {
                    showInfriaChatBottomSheet(
                      context: context,
                      controller: _controller,
                      suggestions: _healthcareSuggestions,
                      enableFeedback: true,
                    );
                  },
                ),
                const Divider(height: 1),
                ListTile(
                  leading: Container(
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.teal.withAlpha(30),
                      shape: BoxShape.circle,
                    ),
                    child: const Icon(Icons.open_in_browser_rounded,
                        color: Colors.teal),
                  ),
                  title: const Text('Buka Chat via Dialog Popup'),
                  subtitle: const Text('Tampilan modal window di tengah layar'),
                  trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
                  onTap: () {
                    showInfriaChatDialog(
                      context: context,
                      controller: _controller,
                      suggestions: _healthcareSuggestions,
                      enableFeedback: true,
                    );
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildStatCard({
    required String title,
    required String value,
    required IconData icon,
    required Color color,
  }) {
    final isDark = widget.isDark;
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: isDark ? const Color(0xFF1E293B) : Colors.white,
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: isDark ? Colors.white10 : Colors.black12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                title,
                style: TextStyle(
                  color: isDark ? Colors.white70 : Colors.black54,
                  fontSize: 13,
                ),
              ),
              Icon(icon, color: color, size: 20),
            ],
          ),
          const SizedBox(height: 10),
          Text(
            value,
            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ],
      ),
    );
  }

  Widget _buildCustomBuilderView(BuildContext context) {
    return InfriaChat(
      controller: _controller,
      headerBuilder: (context, controller) {
        return Container(
          padding: const EdgeInsets.all(12),
          color: Colors.teal.withAlpha(30),
          child: Row(
            children: [
              const Icon(Icons.medical_services_rounded, color: Colors.teal),
              const SizedBox(width: 8),
              const Text(
                'Kustom UI — Tema Rumah Sakit',
                style: TextStyle(
                    fontWeight: FontWeight.bold, color: Colors.teal),
              ),
              const Spacer(),
              Text(
                '${controller.messageCount} pesan',
                style: const TextStyle(fontSize: 12, color: Colors.teal),
              ),
            ],
          ),
        );
      },
      messageBubbleBuilder: (context, message) {
        final isUser = message.isUser;
        return Container(
          margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 12),
          alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
          child: Container(
            padding: const EdgeInsets.all(14),
            decoration: BoxDecoration(
              color: isUser ? const Color(0xFF0284C7) : const Color(0xFF0F766E),
              borderRadius: BorderRadius.circular(16),
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(
                  isUser ? Icons.person : Icons.health_and_safety_rounded,
                  color: Colors.white,
                  size: 16,
                ),
                const SizedBox(width: 8),
                Flexible(
                  child: Text(
                    message.content,
                    style: const TextStyle(color: Colors.white, fontSize: 14),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}
1
likes
0
points
130
downloads

Publisher

unverified uploader

Weekly Downloads

A clean, customizable Flutter UI SDK and runtime controller for AI chat interfaces, RAG, and dynamic client-side function calling.

Homepage
Repository (GitHub)
View/report issues

Topics

#ai #chat #flutter #healthcare #llm

License

unknown (license)

Dependencies

flutter, http

More

Packages that depend on infria