qriib_meet 0.1.0
qriib_meet: ^0.1.0 copied to clipboard
Flutter APIs for creating Qriib rooms and embedding secure, customizable audio and video meeting experiences.
example/lib/main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:qriib_meet/qriib_meet.dart';
const _apiKey = String.fromEnvironment('QRIIB_API_KEY');
const _secretKey = String.fromEnvironment('QRIIB_SECRET_KEY');
const _projectId = String.fromEnvironment('QRIIB_PROJECT_ID');
void main() => runApp(const QriibMeetExampleApp());
class QriibMeetExampleApp extends StatelessWidget {
const QriibMeetExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Qriib Meet examples',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: const Color(0xFF6750A4)),
useMaterial3: true),
home: const RoomExamplesPage(),
);
}
class RoomExamplesPage extends StatefulWidget {
const RoomExamplesPage({super.key});
@override
State<RoomExamplesPage> createState() => _RoomExamplesPageState();
}
class _RoomExamplesPageState extends State<RoomExamplesPage> {
late final QriibMeetClient _client = QriibMeetClient.withProjectCredentials(
apiKey: _apiKey, secretKey: _secretKey, enableNetworkLogging: false);
final _project = TextEditingController(text: _projectId);
final _room = TextEditingController();
final _moderator = TextEditingController();
final _name = TextEditingController(text: 'Guest');
final _startAt = TextEditingController(text: '2026-12-31T18:30');
var _busy = false;
bool get _configured => _apiKey.isNotEmpty && _secretKey.isNotEmpty;
String get _projectIdValue => _project.text.trim();
String get _roomId => _room.text.trim();
@override
void dispose() {
_client.close();
for (final controller in [_project, _room, _moderator, _name, _startAt]) {
controller.dispose();
}
super.dispose();
}
Future<void> _run(Future<Map<String, dynamic>> Function() request) async {
if (!_configured || _busy) return;
setState(() => _busy = true);
try {
final response = await request();
if (mounted) await _showResponse(response);
} on QriibApiException catch (error) {
_message(error.message);
} on QriibMeetException catch (error) {
_message(error.message);
} catch (_) {
_message('Request failed. Verify credentials, IDs, and room state.');
} finally {
if (mounted) setState(() => _busy = false);
}
}
void _message(String message) {
if (mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
}
}
void _needProject() {
if (_projectIdValue.isEmpty) {
throw const QriibMeetException('Project ID is required.');
}
}
void _needRoom() {
if (_roomId.isEmpty) {
throw const QriibMeetException('Room ID is required.');
}
}
Future<void> _showResponse(Map<String, dynamic> response) => showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('API response'),
content: SingleChildScrollView(
child: SelectableText(
const JsonEncoder.withIndent(' ').convert(response))),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'))
],
),
);
Future<void> _open(Map<String, dynamic> response, bool audioOnly) async {
await openMeetingFromResponse(
context: context,
qriib: _client,
response: response,
userName: _name.text.trim(),
audioOnly: audioOnly,
);
}
Future<void> _quick(bool audioOnly) => _run(() async {
_needProject();
final metadata = QriibRoomMetadata(
roomTitle: audioOnly ? 'Quick audio room' : 'Quick video room',
welcomeMessage: 'Welcome to Qriib Meet');
final response = audioOnly
? await _client.rooms.createQuickAudioRoom(
projectId: _projectIdValue,
clientRoomId: newClientRoomId(),
name: _name.text.trim(),
moderatorId: _moderator.text.trim(),
maxParticipants: 20,
emptyTimeout: 78,
metadata: metadata)
: await _client.rooms.createQuickVideoRoom(
projectId: _projectIdValue,
clientRoomId: newClientRoomId(),
name: _name.text.trim(),
moderatorId: _moderator.text.trim(),
maxParticipants: 20,
emptyTimeout: 78,
metadata: metadata);
await _open(response, audioOnly);
return response;
});
Future<void> _scheduled(bool audioOnly) => _run(() async {
_needProject();
return audioOnly
? _client.rooms.createScheduledAudioRoom(
projectId: _projectIdValue,
clientRoomId: newClientRoomId(),
startAt: _startAt.text.trim(),
maxParticipants: 20,
emptyTimeout: 78,
metadata:
const QriibRoomMetadata(roomTitle: 'Scheduled audio room'))
: _client.rooms.createScheduledVideoRoom(
projectId: _projectIdValue,
clientRoomId: newClientRoomId(),
startAt: _startAt.text.trim(),
maxParticipants: 20,
emptyTimeout: 78,
metadata:
const QriibRoomMetadata(roomTitle: 'Scheduled video room'));
});
Future<void> _join() => _run(() async {
_needRoom();
final response = await _client.rooms.joinRoom(
roomId: _roomId,
userInfo: QriibUserInfo(
name: _name.text.trim(),
role: 'attendee',
isAdmin: false,
isHidden: false));
await _open(response, false);
return response;
});
Future<void> _start(bool audioOnly) => _run(() async {
_needRoom();
final response = await _client.rooms
.startScheduledRoom(_roomId, name: _name.text.trim());
await _open(response, audioOnly);
return response;
});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('Qriib Meet examples')),
body: ListView(padding: const EdgeInsets.all(16), children: [
Text('Rooms API playground',
style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 8),
Text(_configured
? 'Ready to make authenticated requests.'
: 'Run with QRIIB_API_KEY and QRIIB_SECRET_KEY. No secrets are stored in this app.'),
const SizedBox(height: 16),
_field(_project, 'Project ID'),
_field(_moderator, 'Moderator ID (quick rooms)'),
_field(_name, 'Participant name'),
_field(_room, 'Room ID'),
_field(_startAt, 'Start at (ISO-8601)'),
_section('Create rooms', [
_action('Create quick video room', Icons.videocam_outlined,
() => _quick(false)),
_action('Create quick audio room', Icons.headphones_outlined,
() => _quick(true)),
_action('Create scheduled video room', Icons.event_outlined,
() => _scheduled(false)),
_action('Create scheduled audio room',
Icons.event_available_outlined, () => _scheduled(true)),
]),
_section('Join and start', [
_action('Join an existing room', Icons.login_outlined, _join),
_action('Start scheduled video room', Icons.play_circle_outline,
() => _start(false)),
_action('Start scheduled audio room', Icons.play_circle_outline,
() => _start(true)),
]),
_section('Room management', [
_action(
'Create invitation link',
Icons.link_outlined,
() => _run(() {
_needRoom();
return _client.rooms.createInvitationLink(
roomId: _roomId, role: QriibInvitationRole.attendee);
})),
_action(
'Get room status',
Icons.info_outline,
() => _run(() {
_needRoom();
return _client.rooms.getRoomStatus(_roomId);
})),
_action(
'Get active room info',
Icons.meeting_room_outlined,
() => _run(() {
_needRoom();
return _client.rooms.getActiveRoomInfo(_roomId);
})),
_action('Get active rooms info', Icons.groups_outlined,
() => _run(_client.rooms.getActiveRoomsInfo)),
_action(
'Fetch past rooms',
Icons.history_outlined,
() => _run(() {
_needProject();
return _client.rooms.fetchPastRooms(
projectId: _projectIdValue,
from: 0,
limit: 10,
orderBy: QriibPastRoomsOrder.desc);
})),
_action(
'End room',
Icons.stop_circle_outlined,
() => _run(() {
_needRoom();
return _client.rooms.endRoom(_roomId);
}),
destructive: true),
]),
]),
);
Widget _field(TextEditingController controller, String label) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: TextField(
controller: controller,
decoration: InputDecoration(
labelText: label, border: const OutlineInputBorder())));
Widget _section(String title, List<Widget> actions) =>
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const Divider(height: 32),
Text(title, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
...actions
]);
Widget _action(String label, IconData icon, Future<void> Function() action,
{bool destructive = false}) =>
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: SizedBox(
width: double.infinity,
child: FilledButton.tonalIcon(
onPressed: _configured && !_busy ? action : null,
icon: _busy
? const SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(strokeWidth: 2))
: Icon(icon),
style: destructive
? FilledButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.error)
: null,
label: Text(label))));
}