baxcloud_chat_uikit_sdk 0.1.0
baxcloud_chat_uikit_sdk: ^0.1.0 copied to clipboard
BaxCloud Chat UI Kit — realtime messaging with ready-made layouts (standard, compact, live overlay) and bring-your-own design hooks.
example/lib/main.dart
import 'package:baxcloud_chat_uikit_sdk/baxcloud_chat_uikit_sdk.dart';
import 'package:baxcloud_core/baxcloud_core.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Demo modes shown in the example app.
enum _DemoMode {
chatScreen,
liveComments,
compactPanel,
customByo,
}
/// Two-device test:
/// 1. Device A → user1, room `demo-chat`
/// 2. Device B → user2, same room
/// 3. Send messages on either device
void main() async {
WidgetsFlutterBinding.ensureInitialized();
const projectId = String.fromEnvironment('BAXCLOUD_PROJECT_ID');
const apiKey = String.fromEnvironment('BAXCLOUD_API_KEY');
final prefs = await SharedPreferences.getInstance();
final savedUserId = prefs.getString('bax_chat_user_id') ??
const String.fromEnvironment('BAXCLOUD_USER_ID', defaultValue: 'user1');
final savedUserName = prefs.getString('bax_chat_user_name') ??
const String.fromEnvironment('BAXCLOUD_USER_NAME', defaultValue: 'User 1');
final savedRoom = prefs.getString('bax_chat_room') ?? 'demo-chat';
final config = BaxConfig(projectId: projectId, apiKey: apiKey);
if (config.isValid) {
await BaxChats.initialize(
config: config,
localUser: BaxcloudUser(userId: savedUserId, name: savedUserName),
);
}
runApp(ChatExampleApp(
config: config,
initialUserId: savedUserId,
initialUserName: savedUserName,
initialRoom: savedRoom,
));
}
class ChatExampleApp extends StatelessWidget {
final BaxConfig config;
final String initialUserId;
final String initialUserName;
final String initialRoom;
const ChatExampleApp({
super.key,
required this.config,
required this.initialUserId,
required this.initialUserName,
required this.initialRoom,
});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BaxCloud Chat',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF059669)),
useMaterial3: true,
),
home: BaxChatScope(
config: config,
child: ChatHomePage(
config: config,
initialUserId: initialUserId,
initialUserName: initialUserName,
initialRoom: initialRoom,
),
),
);
}
}
class ChatHomePage extends StatefulWidget {
final BaxConfig config;
final String initialUserId;
final String initialUserName;
final String initialRoom;
const ChatHomePage({
super.key,
required this.config,
required this.initialUserId,
required this.initialUserName,
required this.initialRoom,
});
@override
State<ChatHomePage> createState() => _ChatHomePageState();
}
class _ChatHomePageState extends State<ChatHomePage> {
late final TextEditingController _userIdController;
late final TextEditingController _userNameController;
late final TextEditingController _roomController;
_DemoMode _mode = _DemoMode.chatScreen;
bool _inChat = false;
bool _saving = false;
bool _joinLeaveEnabled = true;
bool _typingEnabled = false;
String? _lastError;
@override
void initState() {
super.initState();
_userIdController = TextEditingController(text: widget.initialUserId);
_userNameController = TextEditingController(text: widget.initialUserName);
_roomController = TextEditingController(text: widget.initialRoom);
}
@override
void dispose() {
_userIdController.dispose();
_userNameController.dispose();
_roomController.dispose();
super.dispose();
}
BaxChatUiConfig get _uiConfig {
switch (_mode) {
case _DemoMode.chatScreen:
return BaxChatUiConfig.standard(
showJoinMessages: _joinLeaveEnabled,
showLeaveMessages: _joinLeaveEnabled,
showSystemMessages: _joinLeaveEnabled,
enableTypingIndicators: _typingEnabled,
joinedTextTemplate: '{name} joined the chat',
leftTextTemplate: '{name} left the chat',
showTimestamps: true,
edgeFade: BaxChatEdgeFade.none,
);
case _DemoMode.liveComments:
return BaxChatUiConfig.liveOverlay(
showJoinMessages: _joinLeaveEnabled,
showLeaveMessages: false,
// Stage chrome provides leave — avoid a second X mid-screen.
showLeaveButton: false,
enableTypingIndicators: _typingEnabled,
joinedTextTemplate: '{name} joined the stream',
edgeFade: BaxChatEdgeFade.top,
edgeFadeExtent: 64,
);
case _DemoMode.compactPanel:
return BaxChatUiConfig.compact(
showJoinMessages: false,
showLeaveMessages: false,
showLeaveButton: true,
enableTypingIndicators: _typingEnabled,
bubbleBorderRadius: 10,
localBubbleColor: const Color(0xFF059669),
);
case _DemoMode.customByo:
return BaxChatUiConfig.standard(
showHeader: false,
backgroundColor: const Color(0xFF111827),
localBubbleColor: const Color(0xFF7C3AED),
bubbleBorderRadius: 20,
enableTypingIndicators: _typingEnabled,
showJoinMessages: _joinLeaveEnabled,
showLeaveMessages: _joinLeaveEnabled,
joinedTextBuilder: (name) => '👋 $name is here',
leftTextBuilder: (name) => '👋 $name left',
);
}
}
BaxcloudUser get _user => BaxcloudUser(
userId: _userIdController.text.trim(),
name: _userNameController.text.trim(),
);
Future<void> _saveProfile() async {
setState(() {
_saving = true;
_lastError = null;
});
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('bax_chat_user_id', _userIdController.text.trim());
await prefs.setString('bax_chat_user_name', _userNameController.text.trim());
await prefs.setString('bax_chat_room', _roomController.text.trim());
if (widget.config.isValid) {
await BaxChats.updateLocalUser(_user);
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Profile saved')),
);
}
} catch (e) {
setState(() => _lastError = '$e');
} finally {
if (mounted) setState(() => _saving = false);
}
}
void _joinChat() {
if (!widget.config.isValid) {
setState(() => _lastError = 'Set BAXCLOUD_PROJECT_ID and BAXCLOUD_API_KEY');
return;
}
final room = _roomController.text.trim();
final userId = _userIdController.text.trim();
if (room.isEmpty || userId.isEmpty) {
setState(() => _lastError = 'Room and user id are required');
return;
}
setState(() {
_lastError = null;
_inChat = true;
});
}
Widget _buildChatBody() {
final view = BaxChatView(
roomName: _roomController.text.trim(),
user: _user,
isHost: true,
uiConfig: _uiConfig,
onLeave: () => setState(() => _inChat = false),
inChatBuilder: _mode == _DemoMode.customByo
? (context, session) {
return Column(
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(16, 48, 16, 12),
color: const Color(0xFF1F2937),
child: Row(
children: [
const Expanded(
child: Text(
'My custom chat chrome',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
),
TextButton(
onPressed: session.leave,
child: const Text('Leave'),
),
],
),
),
Expanded(child: session.chatSurface),
],
);
}
: null,
);
if (_mode == _DemoMode.liveComments) {
return Stack(
fit: StackFit.expand,
children: [
// Fake live video / stage background
const DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF7C2D12),
Color(0xFF1E1B4B),
Color(0xFF0F172A),
],
),
),
),
const Align(
alignment: Alignment(0.35, -0.35),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.live_tv, color: Colors.white70, size: 56),
SizedBox(height: 8),
Text(
'LIVE STAGE (demo)',
style: TextStyle(
color: Colors.white70,
fontWeight: FontWeight.w600,
letterSpacing: 1.2,
),
),
],
),
),
// Live comments docked to the left (typical stream chat).
Positioned(
left: 0,
bottom: 0,
width: MediaQuery.sizeOf(context).width * 0.72,
height: MediaQuery.sizeOf(context).height * 0.48,
child: view,
),
Positioned(
top: 0,
right: 0,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8),
child: Material(
color: Colors.black.withValues(alpha: 0.45),
shape: const CircleBorder(),
clipBehavior: Clip.antiAlias,
child: IconButton(
tooltip: 'Leave',
onPressed: () => setState(() => _inChat = false),
icon: const Icon(Icons.close_rounded, color: Colors.white),
),
),
),
),
),
],
);
}
if (_mode == _DemoMode.compactPanel) {
return ColoredBox(
color: const Color(0xFF0F172A),
child: SafeArea(
child: Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: MediaQuery.sizeOf(context).width * 0.78,
child: Material(
elevation: 8,
color: const Color(0xFF0B1220),
child: view,
),
),
),
),
);
}
return view;
}
@override
Widget build(BuildContext context) {
if (_inChat) {
return Scaffold(
body: _buildChatBody(),
);
}
return Scaffold(
appBar: AppBar(title: const Text('BaxCloud Chat Example')),
body: ListView(
padding: const EdgeInsets.all(20),
children: [
Text(
'Demo modes',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
'Pick a preset, then join the same room on two devices (user1 / user2).',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
..._DemoMode.values.map((mode) {
final selected = _mode == mode;
return Card(
margin: const EdgeInsets.only(bottom: 8),
color: selected
? Theme.of(context).colorScheme.primaryContainer
: null,
child: ListTile(
selected: selected,
title: Text(_modeTitle(mode)),
subtitle: Text(_modeSubtitle(mode)),
onTap: () => setState(() => _mode = mode),
),
);
}),
const SizedBox(height: 8),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Show join / leave messages'),
subtitle: const Text(
'Uses customizable templates (i18n-ready)',
),
value: _joinLeaveEnabled,
onChanged: (v) => setState(() => _joinLeaveEnabled = v),
),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Enable typing indicators'),
subtitle: const Text(
'Off by default — opt in to show “is typing…”',
),
value: _typingEnabled,
onChanged: (v) => setState(() => _typingEnabled = v),
),
const SizedBox(height: 16),
TextField(
controller: _userIdController,
decoration: const InputDecoration(
labelText: 'Your user id',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _userNameController,
decoration: const InputDecoration(
labelText: 'Display name',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: _roomController,
decoration: const InputDecoration(
labelText: 'Room name',
border: OutlineInputBorder(),
),
),
if (_lastError != null) ...[
const SizedBox(height: 16),
Text(_lastError!, style: const TextStyle(color: Colors.red)),
],
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _saving ? null : _saveProfile,
icon: _saving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.save_outlined),
label: const Text('Save profile'),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: _joinChat,
icon: const Icon(Icons.chat_bubble_outline),
label: const Text('Open demo'),
),
],
),
);
}
String _modeTitle(_DemoMode mode) {
switch (mode) {
case _DemoMode.chatScreen:
return 'Chat screen';
case _DemoMode.liveComments:
return 'Live streaming comments';
case _DemoMode.compactPanel:
return 'Compact side panel';
case _DemoMode.customByo:
return 'Bring your own chrome';
}
}
String _modeSubtitle(_DemoMode mode) {
switch (mode) {
case _DemoMode.chatScreen:
return 'Full-screen standard chat with header + timestamps';
case _DemoMode.liveComments:
return 'Left-aligned overlay comments; leave via top-right X';
case _DemoMode.compactPanel:
return 'Dense chat docked right — leave via floating X';
case _DemoMode.customByo:
return 'inChatBuilder wraps kit surface with custom header';
}
}
}