jasnita_call_sdk 0.1.3
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)),
],
);
}
}