socrits 0.1.2
socrits: ^0.1.2 copied to clipboard
Socrits SDK for Flutter - video & voice calls, call invitations (ring/accept), and in-app chat. Drop-in prebuilt UI kit plus a low-level core client.
example/lib/main.dart
// Minimal call app on the Socrits SDK — connect, ring, accept, video.
//
// Run: flutter run (fill in your tunnel base URL + API key + a user id)
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:livekit_client/livekit_client.dart' as lk;
import 'package:socrits/socrits.dart';
final navKey = GlobalKey<NavigatorState>();
void main() => runApp(const SocritsExampleApp());
class SocritsExampleApp extends StatelessWidget {
const SocritsExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
navigatorKey: navKey,
title: 'Socrits example',
theme: ThemeData.dark(useMaterial3: true),
home: const ConnectScreen(),
);
}
class ConnectScreen extends StatefulWidget {
const ConnectScreen({super.key});
@override
State<ConnectScreen> createState() => _ConnectScreenState();
}
class _ConnectScreenState extends State<ConnectScreen> {
// NOTE: for a trial only. In production your BACKEND mints the token
// (POST /v1/session with the API key) — never ship the key in the app.
final _baseUrl = TextEditingController(text: 'https://YOUR-console-tunnel.trycloudflare.com');
final _apiKey = TextEditingController(text: 'sk_live_...');
final _userId = TextEditingController();
final _target = TextEditingController();
Socrits? _sc;
String _status = 'not connected';
bool _connected = false;
Future<String> _fetchToken() async {
final res = await http.post(
Uri.parse('${_baseUrl.text.trim()}/v1/session'),
headers: {'Content-Type': 'application/json', 'x-api-key': _apiKey.text.trim()},
body: jsonEncode({'userId': _userId.text.trim(), 'userName': _userId.text.trim()}),
);
return (jsonDecode(res.body) as Map<String, dynamic>)['token'] as String;
}
Future<void> _connect() async {
setState(() => _status = 'connecting…');
try {
final token = await _fetchToken();
final sc = Socrits(baseUrl: _baseUrl.text.trim());
sc.onIncomingCall = _showIncoming;
sc.onCallConnected = (room, mode) => navKey.currentState?.push(
MaterialPageRoute(builder: (_) => CallScreen(room: room)),
);
sc.onCallRejected = (_) => setState(() => _status = 'call declined');
sc.onCallEnded = (reason) => setState(() => _status = reason == 'timeout' ? 'no answer' : 'call ended');
await sc.connect(userToken: token);
setState(() {
_sc = sc;
_connected = true;
_status = 'online as ${_userId.text.trim()}';
});
} catch (e) {
setState(() => _status = 'error: $e');
}
}
void _showIncoming(IncomingCall call) {
showDialog(
context: navKey.currentContext!,
builder: (ctx) => AlertDialog(
title: const Text('Incoming call'),
content: Text('${call.fromUserName} · ${call.mode.name}'),
actions: [
TextButton(
onPressed: () {
_sc?.reject(call);
Navigator.pop(ctx);
},
child: const Text('Decline'),
),
FilledButton(
onPressed: () {
Navigator.pop(ctx);
_sc?.accept(call);
},
child: const Text('Accept'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Socrits example')),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
TextField(controller: _baseUrl, decoration: const InputDecoration(labelText: 'Base URL (console tunnel)')),
TextField(controller: _apiKey, decoration: const InputDecoration(labelText: 'API key (trial only)')),
TextField(controller: _userId, decoration: const InputDecoration(labelText: 'Your user ID')),
const SizedBox(height: 12),
FilledButton(onPressed: _connected ? null : _connect, child: const Text('Connect')),
const SizedBox(height: 6),
Text(_status, style: const TextStyle(fontFamily: 'monospace')),
const Divider(height: 40),
TextField(controller: _target, decoration: const InputDecoration(labelText: 'Call user ID')),
const SizedBox(height: 12),
Row(
children: [
FilledButton.icon(
onPressed: _connected ? () => _sc?.invite(toUserId: _target.text.trim(), mode: CallMode.video) : null,
icon: const Icon(Icons.videocam),
label: const Text('Video'),
),
const SizedBox(width: 12),
OutlinedButton.icon(
onPressed: _connected ? () => _sc?.invite(toUserId: _target.text.trim(), mode: CallMode.voice) : null,
icon: const Icon(Icons.call),
label: const Text('Voice'),
),
],
),
],
),
),
);
}
}
// The whole call screen is now just the prebuilt widget.
class CallScreen extends StatelessWidget {
const CallScreen({super.key, required this.room});
final lk.Room room;
@override
Widget build(BuildContext context) => Scaffold(
backgroundColor: Colors.black,
body: SafeArea(
child: SocritsCallView(room: room, onHangUp: () => Navigator.pop(context)),
),
);
}