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

Flutter SDK for SIP-based voice calling with license key validation. Built on top of sip_ua for WebRTC-powered calls.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:jasnita_call_sdk/jasnita_call_sdk.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Jasnita Call SDK Demo',
      theme: ThemeData.dark(useMaterial3: true),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  static const _config = JasnitaCallConfig(
    licenseKey: 'C5fPHbaa0Br8OtSPKSlRfqpxHDN4QlvUl2in0qB9S9xZIJ5SjH5mNjiaq4tX',
    baseUrl: 'https://robocall.omnicode.id',
    phone: '123',
    name: 'Demo User',
    showMute: true,
    showHold: true,
    customerId: 'jasnita_demo',
  );

  // Ganti preset di sini: dark | lightBlue | light | teal
  // Atau gunakan JasnitaCallTheme(...) untuk warna custom.
  static const _theme = JasnitaCallTheme.teal;

  final _controller = CallController();
  final _numberController = TextEditingController();

  CallState _callState = CallState.idle;
  bool _initializing = false;
  String? _error;

  @override
  void initState() {
    super.initState();
    _controller.stateStream.listen((s) {
      if (mounted) setState(() => _callState = s);
    });
    // Rebuild when number text changes so canCall updates correctly.
    _numberController.addListener(() => setState(() {}));
    // Auto-initialize on screen open.
    WidgetsBinding.instance.addPostFrameCallback((_) => _initialize());
  }

  Future<void> _initialize() async {
    setState(() {
      _initializing = true;
      _error = null;
    });
    try {
      await _controller.initialize(_config);
    } on JasnitaPermissionException catch (e) {
      setState(() => _error = 'Permission: ${e.message}');
    } on JasnitaLicenseException catch (e) {
      setState(() => _error = 'License error: ${e.message}');
    } on JasnitaApiException catch (e) {
      setState(() => _error = 'API error: ${e.message}');
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _initializing = false);
    }
  }

  Future<void> _call() async {
    final number = _numberController.text;
    if (number.isEmpty) return;
    if (!mounted) return;
    unawaited(Navigator.of(context).push(MaterialPageRoute(
      builder: (_) => JasnitaCallScreen(controller: _controller, theme: _theme),
    )));
    await _controller.call(number);
  }

  void _appendDigit(String d) {
    final newText = _numberController.text + d;
    _numberController.value = TextEditingValue(
      text: newText,
      selection: TextSelection.collapsed(offset: newText.length),
    );
  }

  void _deleteDigit() {
    final t = _numberController.text;
    if (t.isEmpty) return;
    final newText = t.substring(0, t.length - 1);
    _numberController.value = TextEditingValue(
      text: newText,
      selection: TextSelection.collapsed(offset: newText.length),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    final status = _callState.status;
    final canCall =
        status == CallStatus.registered && _numberController.text.isNotEmpty;

    return JasnitaIncomingCallOverlay(
      controller: _controller,
      theme: _theme,
      onCallScreenBuilder: (_) =>
          JasnitaCallScreen(controller: _controller, theme: _theme),
      child: Scaffold(
        backgroundColor: const Color(0xFF1A1A2E),
        appBar: AppBar(
          backgroundColor: const Color(0xFF1A1A2E),
          title: const Text('Jasnita Call SDK'),
          centerTitle: true,
        ),
        body: SafeArea(
          child: SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: 24),
            child: Column(
              children: [
                const SizedBox(height: 12),
                _StatusBadge(status: status),
                const SizedBox(height: 12),
                if (_error != null) ...[
                  Container(
                    padding: const EdgeInsets.all(10),
                    decoration: BoxDecoration(
                      color: Colors.red.withValues(alpha: 0.15),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Text(_error!,
                        style:
                            const TextStyle(color: Colors.red, fontSize: 13)),
                  ),
                  const SizedBox(height: 8),
                ],
                ElevatedButton(
                  onPressed: _initializing ? null : _initialize,
                  style: ElevatedButton.styleFrom(
                    minimumSize: const Size.fromHeight(44),
                  ),
                  child: _initializing
                      ? const SizedBox(
                          height: 18,
                          width: 18,
                          child: CircularProgressIndicator(strokeWidth: 2))
                      : const Text('Initialize SDK'),
                ),
                const SizedBox(height: 24),
                // ── Number input ──
                Container(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 20, vertical: 4),
                  decoration: BoxDecoration(
                    color: Colors.white.withValues(alpha: 0.06),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: TextField(
                    controller: _numberController,
                    keyboardType: TextInputType.phone,
                    textAlign: TextAlign.center,
                    cursorColor: Colors.white54,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 24,
                      fontWeight: FontWeight.w300,
                      letterSpacing: 2,
                    ),
                    decoration: const InputDecoration(
                      border: InputBorder.none,
                      hintText: 'Masukkan nomor...',
                      hintStyle: TextStyle(
                        color: Colors.white30,
                        fontSize: 24,
                        fontWeight: FontWeight.w300,
                        letterSpacing: 1,
                      ),
                    ),
                  ),
                ),
                const SizedBox(height: 20),
                // ── Dialpad ──
                JasnitaDialpad(
                  onDigit: _appendDigit,
                  onDelete: _deleteDigit,
                  keySize: 72,
                ),
                const SizedBox(height: 20),
                // ── Call button ──
                SizedBox(
                  width: 72,
                  height: 72,
                  child: Material(
                    color: canCall ? Colors.green : Colors.grey.shade800,
                    shape: const CircleBorder(),
                    child: InkWell(
                      customBorder: const CircleBorder(),
                      onTap: canCall ? _call : null,
                      child:
                          const Icon(Icons.call, color: Colors.white, size: 32),
                    ),
                  ),
                ),
                const SizedBox(height: 16),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _StatusBadge extends StatelessWidget {
  final CallStatus status;

  const _StatusBadge({required this.status});

  static const _colors = {
    CallStatus.idle: Colors.grey,
    CallStatus.connecting: Colors.orange,
    CallStatus.readyToRegister: Colors.orange,
    CallStatus.registered: Colors.green,
    CallStatus.dialing: Colors.blue,
    CallStatus.ringing: Colors.blue,
    CallStatus.incoming: Colors.purple,
    CallStatus.active: Colors.teal,
    CallStatus.ended: Colors.grey,
    CallStatus.error: Colors.red,
  };

  static const _labels = {
    CallStatus.idle: 'Belum terhubung',
    CallStatus.connecting: 'Menghubungkan...',
    CallStatus.readyToRegister: 'Registrasi...',
    CallStatus.registered: 'Siap',
    CallStatus.dialing: 'Memanggil',
    CallStatus.ringing: 'Berdering',
    CallStatus.incoming: 'Panggilan masuk',
    CallStatus.active: 'Sedang telepon',
    CallStatus.ended: 'Panggilan selesai',
    CallStatus.error: 'Error',
  };

  @override
  Widget build(BuildContext context) {
    final color = _colors[status] ?? Colors.grey;
    final label = _labels[status] ?? status.name;
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(
          width: 8,
          height: 8,
          decoration: BoxDecoration(color: color, shape: BoxShape.circle),
        ),
        const SizedBox(width: 6),
        Text(label,
            style: TextStyle(
                color: color, fontWeight: FontWeight.w500, fontSize: 13)),
      ],
    );
  }
}
0
likes
160
points
22
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for SIP-based voice calling with license key validation. Built on top of sip_ua for WebRTC-powered calls.

Homepage

License

BSD-3-Clause (license)

Dependencies

flutter, flutter_webrtc, http, permission_handler, shared_preferences, sip_ua

More

Packages that depend on jasnita_call_sdk