gyanmeet_flutter_sdk 1.2.1
gyanmeet_flutter_sdk: ^1.2.1 copied to clipboard
Gyanmeet meeting SDK for Flutter — a self-contained video meeting widget with host controls, chat, polls, and optional on-device AI proctoring.
example/lib/main.dart
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:gyanmeet_flutter_sdk/gyanmeet_flutter_sdk.dart';
import 'dev_join.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Gyanmeet Flutter SDK Example',
debugShowCheckedModeBanner: false,
home: JoinScreen(),
);
}
}
// Dev harness: enter API base + API key + room code, mint a token via the
// dev join flow, then embed GyanmeetMeeting with that token. In production the
// consumer's backend mints the token; the app never holds an API key.
class JoinScreen extends StatefulWidget {
const JoinScreen({super.key});
@override
State<JoinScreen> createState() => _JoinScreenState();
}
class _JoinScreenState extends State<JoinScreen> {
final _backend = TextEditingController(text: 'https://api.gyanmeet.com/api');
// Dev-only prefill for quick local testing — do NOT commit a real API key.
final _apiKey = TextEditingController(
text: 'bbc64569648d2ed797c95f6cb7d192d3345759d1d81924a0027ec74e9c8aa173');
final _roomCode = TextEditingController(text: 'N49-YT2-PFW');
final _name = TextEditingController(text: 'Flutter Tester');
final _email = TextEditingController(text: 'flutter@example.com');
String? _token;
bool _minting = false;
String? _error;
String? _compareResult;
bool _comparing = false;
Future<void> _compareFaces(
String assetA, String assetB, String label) async {
setState(() {
_comparing = true;
_compareResult = null;
});
try {
final a = await _assetToFile(assetA, assetA.split('/').last);
final b = await _assetToFile(assetB, assetB.split('/').last);
const cfg = GyanmeetConfig();
final ok = await FaceComparator.instance.init(cfg.faceModelAssetPath!);
if (!ok) {
setState(() => _compareResult = 'model load failed');
return;
}
final baseEmb = await FaceComparator.instance.embedFromFile(a);
if (baseEmb == null) {
setState(() => _compareResult = 'no face in image A');
return;
}
final sim =
await FaceComparator.instance.similarityToBaseFromFile(baseEmb, b);
if (sim == null) {
setState(() => _compareResult = 'no face in image B');
return;
}
final same = sim >= cfg.faceMatchThreshold;
final msg =
'$label: similarity=${sim.toStringAsFixed(4)} threshold=${cfg.faceMatchThreshold} '
'=> ${same ? "SAME person" : "DIFFERENT person"}';
debugPrint('[FACE COMPARE] $msg');
setState(() => _compareResult = msg);
} catch (e) {
setState(() => _compareResult = 'error: $e');
} finally {
if (mounted) setState(() => _comparing = false);
}
}
Future<File> _assetToFile(String asset, String name) async {
final data = await rootBundle.load(asset);
final f = File('${Directory.systemTemp.path}/$name');
await f.writeAsBytes(data.buffer.asUint8List(), flush: true);
return f;
}
// Reference image for the identity gate / manual-enroll. Loaded from assets
// for testing; in production the consumer fetches it (backend photo, etc.).
Uint8List? _referenceImage;
@override
void initState() {
super.initState();
_loadReference();
}
Future<void> _loadReference() async {
try {
final data = await rootBundle.load('assets/reference/person_a.png');
if (mounted) {
setState(() => _referenceImage = data.buffer.asUint8List());
}
} catch (e) {
debugPrint('reference load failed: $e');
}
}
@override
void dispose() {
_backend.dispose();
_apiKey.dispose();
_roomCode.dispose();
_name.dispose();
_email.dispose();
super.dispose();
}
Future<void> _mintAndJoin() async {
setState(() {
_minting = true;
_error = null;
});
try {
final token = await devMintToken(
apiBase: _backend.text.trim(),
apiKey: _apiKey.text.trim(),
roomCode: _roomCode.text.trim(),
name: _name.text.trim(),
email: _email.text.trim(),
);
if (!mounted) return;
setState(() {
_token = token;
_minting = false;
});
} catch (e) {
if (!mounted) return;
setState(() {
_error = '$e';
_minting = false;
});
}
}
@override
Widget build(BuildContext context) {
if (_token != null) {
return ScaffoldMessenger(
child: Builder(builder: (context) {
return GyanmeetMeeting(
backendUrl: _backend.text.trim(),
token: _token!,
config: const GyanmeetConfig(
showPreJoin: true,
enableIdentityVerificationGate: true,
),
identityReferenceImage: _referenceImage,
primaryColor: '#366D5A',
onJoined: (id, role) => debugPrint('joined $id as $role'),
onLeave: () => setState(() => _token = null),
onEnded: () => setState(() => _token = null),
onError: (e) => debugPrint('error: $e'),
onFacePresenceChanged: (isPresent) =>
debugPrint('Face present: $isPresent'),
onGazeViolation: (reason) =>
debugPrint('Gaze violation: $reason'),
onIdentityMismatch: (similarity) =>
debugPrint('Identity mismatch! similarity=$similarity'),
onProctoringViolation: (v) => debugPrint(
'PROCTORING VIOLATION: ${v.type} reason=${v.reason} sim=${v.similarity}'),
);
}),
);
}
return Scaffold(
appBar: AppBar(title: const Text('Gyanmeet SDK Example')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
_field(_backend, 'Backend URL'),
_field(_apiKey, 'API key (dev only)'),
_field(_roomCode, 'Room code'),
_field(_name, 'Display name'),
_field(_email, 'Email'),
const SizedBox(height: 20),
if (_error != null)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Text(_error!,
style: const TextStyle(color: Color(0xFFDC2626))),
),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _minting ? null : _mintAndJoin,
child: _minting
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2))
: const Text('Join meeting'),
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: _comparing
? null
: () => _compareFaces('assets/reference/messi.jpg',
'assets/reference/neymar.jpg', 'messi vs neymar'),
child: const Text('Compare messi vs neymar (diff)'),
),
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: _comparing
? null
: () => _compareFaces('assets/reference/neymar.jpg',
'assets/reference/neymar2.jpg', 'neymar vs neymar2'),
child: _comparing
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2))
: const Text('Compare neymar vs neymar2 (same)'),
),
),
if (_compareResult != null)
Padding(
padding: const EdgeInsets.only(top: 12),
child: Text(_compareResult!,
style: const TextStyle(fontWeight: FontWeight.w600)),
),
],
),
),
);
}
Widget _field(TextEditingController c, String label) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: TextField(
controller: c,
decoration: InputDecoration(labelText: label),
),
);
}