gyanmeet_flutter_sdk 1.6.0 copy "gyanmeet_flutter_sdk: ^1.6.0" to clipboard
gyanmeet_flutter_sdk: ^1.6.0 copied to clipboard

PlatformAndroid
unlisted

Gyanmeet meeting SDK for Flutter — a self-contained video meeting widget with host controls, chat, polls, batch exams, certificate verification, and optional on-device AI proctoring.

example/lib/main.dart

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 'class_join_tab.dart';
import 'dev_join_tab.dart';
import 'register_tab.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gyanmeet Flutter SDK Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF366D5A)),
      ),
      home: const HomeScreen(),
    );
  }
}

/// Dev harness for the SDK.
///
/// Four ways in, all sharing one backend URL:
///   • Dev join — mint a token straight from a room code with an API key.
///   • Register — the applicant registration form, which hands back a claim code.
///   • Class join — passport + claim code, the way a real applicant joins.
///   • Certificate — public verification, no session at all.
///
/// The API key path is dev-only: in production the consumer's backend mints the
/// token and the app never holds a key.
class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

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

class _HomeScreenState extends State<HomeScreen> {
  final _backend = TextEditingController(
    text: 'https://israel-api.gyanmeet.com/api',
  );

  // The applicant routes resolve the organization from the request, not from
  // the body. A browser supplies it via its Origin header because the web app
  // is served from an org subdomain; a native app has none, so it is sent
  // explicitly as X-Organization-Subdomain.
  final _orgSubdomain = TextEditingController(text: 'israel');

  String? _token;

  // Reference image for the identity gate. Loaded from assets for testing; in
  // production the consumer fetches it (backend photo, etc.). Optional — with
  // none, the gate enrolls from the selfie taken at join.
  Uint8List? _referenceImage;

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

  // Set when the reference asset is absent. The gate still runs — with no
  // reference it enrolls from the selfie instead of verifying against a photo.
  String? _referenceError;

  Future<void> _loadReference() async {
    try {
      final data = await rootBundle.load('assets/reference/person_a.png');
      if (mounted) {
        setState(() {
          _referenceImage = data.buffer.asUint8List();
          _referenceError = null;
        });
      }
    } catch (e) {
      debugPrint('reference load failed: $e');
      if (mounted) {
        setState(
          () => _referenceError =
              'No reference photo at example/assets/reference/person_a.png — '
              'the identity gate will enroll from the selfie instead of '
              'verifying against a reference.',
        );
      }
    }
  }

  @override
  void dispose() {
    _backend.dispose();
    _orgSubdomain.dispose();
    super.dispose();
  }

  void _onToken(String token) => setState(() => _token = token);

  @override
  Widget build(BuildContext context) {
    if (_token != null) return _meeting(_token!);

    return DefaultTabController(
      length: 4,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Gyanmeet SDK Example'),
          bottom: const TabBar(
            isScrollable: true,
            tabAlignment: TabAlignment.start,
            tabs: [
              Tab(icon: Icon(Icons.bolt), text: 'Dev join'),
              Tab(icon: Icon(Icons.how_to_reg), text: 'Register'),
              Tab(icon: Icon(Icons.login), text: 'Class join'),
              Tab(icon: Icon(Icons.verified), text: 'Certificate'),
            ],
          ),
        ),
        body: Column(
          children: [
            Padding(
              padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
              child: Column(
                children: [
                  TextField(
                    controller: _backend,
                    decoration: const InputDecoration(
                      labelText: 'Backend URL',
                      border: OutlineInputBorder(),
                    ),
                    // Every tab reads this controller on submit, so a rebuild
                    // is enough to keep the certificate tab's client in step.
                    onChanged: (_) => setState(() {}),
                  ),
                  const SizedBox(height: 8),
                  TextField(
                    controller: _orgSubdomain,
                    decoration: const InputDecoration(
                      labelText: 'Organization subdomain',
                      helperText:
                          'Used by Register and Class join. Leave blank only '
                          'if the backend sets PILOT_ORGANIZATION_ID.',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ],
              ),
            ),
            if (_referenceError != null)
              Padding(
                padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
                child: Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: Colors.orange.withValues(alpha: 0.15),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Row(
                    children: [
                      const Icon(Icons.warning_amber, color: Colors.orange),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          _referenceError!,
                          style: const TextStyle(fontSize: 12),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            Expanded(
              child: TabBarView(
                children: [
                  DevJoinTab(backend: _backend, onToken: _onToken),
                  RegisterTab(backend: _backend, orgSubdomain: _orgSubdomain),
                  ClassJoinTab(
                    backend: _backend,
                    orgSubdomain: _orgSubdomain,
                    onToken: _onToken,
                  ),
                  GyanmeetCertificateVerification(
                    backendUrl: _backend.text.trim(),
                    primaryColor: '#366D5A',
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _meeting(String token) {
    return GyanmeetMeeting(
      backendUrl: _backend.text.trim(),
      token: token,
      config: const GyanmeetConfig(
        videoMode: VideoMode.hostsOnly,
        showPreJoin: true,
        enableIdentityVerificationGate: true,
        darkMode: true,
        showParticipantsTab: false,
        showPolls: false,
        enableBreak: true,
        enableExam: true,
        // faceComparisonInterval: Duration(seconds: 10),
      ),
      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}',
      ),
      // onAiViolationWarning / onAiViolationRemoved are deliberately left
      // unset: the SDK then draws the warning pill and the removal notice
      // itself, which is what a consumer with no proctoring UI of its own gets.
    );
  }
}
1
likes
130
points
178
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Gyanmeet meeting SDK for Flutter — a self-contained video meeting widget with host controls, chat, polls, batch exams, certificate verification, and optional on-device AI proctoring.

Repository (GitHub)
View/report issues

License

(pending) (license)

Dependencies

dio, flutter, google_mlkit_face_detection, image, livekit_client, livekit_components, path_provider, provider, tflite_flutter

More

Packages that depend on gyanmeet_flutter_sdk