signalr_chat_plugin 1.0.0
signalr_chat_plugin: ^1.0.0 copied to clipboard
A Flutter plugin for integrating SignalR real-time chat.
import 'package:flutter/material.dart';
import 'package:signalr_chat_plugin/signalr_plugin.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const ChatApp());
}
class ChatApp extends StatelessWidget {
const ChatApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
home: const ChatScreen(),
);
}
}
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController();
final SignalRChatPlugin _chatPlugin = SignalRChatPlugin();
final List<ChatMessage> _messages = [];
late String _username;
ConnectionStatus _connectionState = ConnectionStatus.connecting;
@override
void initState() {
super.initState();
_initializeChat();
}
Future<void> _initializeChat() async {
// Initialize SignalR with configuration
await _chatPlugin.initSignalR(
SignalRConnectionOptions(
serverUrl: 'http://your-server/chathub',
reconnectInterval: const Duration(seconds: 3),
maxRetryAttempts: 5,
autoReconnect: true,
onError: _handleError,
),
);
// Set a random username for demo purposes
_username = 'User${DateTime.now().millisecondsSinceEpoch % 1000}';
// Listen to messages
_chatPlugin.messagesStream.listen(_handleNewMessage);
// Listen to connection state changes
_chatPlugin.connectionStateStream.listen(_handleConnectionState);
// Listen to errors
_chatPlugin.errorStream.listen(_handleError);
}
void _handleNewMessage(ChatMessage message) {
setState(() {
_messages.insert(0, message);
});
_scrollToBottom();
}
void _handleConnectionState(ConnectionStatus state) {
setState(() {
_connectionState = state;
});
String message;
Color backgroundColor;
switch (state) {
case ConnectionStatus.connected:
message = 'Connected to chat';
backgroundColor = Colors.green;
break;
case ConnectionStatus.connecting:
message = 'Connecting to chat...';
backgroundColor = Colors.orange;
break;
case ConnectionStatus.reconnecting:
message = 'Reconnecting...';
backgroundColor = Colors.orange;
break;
case ConnectionStatus.disconnected:
message = 'Disconnected from chat';
backgroundColor = Colors.red;
break;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: backgroundColor,
duration: const Duration(seconds: 2),
),
);
}
void _handleError(String error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $error'),
backgroundColor: Colors.red,
),
);
}
Future<void> _sendMessage() async {
final message = _messageController.text.trim();
if (message.isEmpty) return;
_messageController.clear();
await _chatPlugin.sendMessage(_username, message);
}
void _scrollToBottom() {
if (_scrollController.hasClients) {
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
}
Widget _buildConnectionStatus() {
Color color;
IconData icon;
String text;
switch (_connectionState) {
case ConnectionStatus.connected:
color = Colors.green;
icon = Icons.cloud_done;
text = 'Connected';
break;
case ConnectionStatus.connecting:
color = Colors.orange;
icon = Icons.cloud_upload;
text = 'Connecting';
break;
case ConnectionStatus.reconnecting:
color = Colors.orange;
icon = Icons.cloud_upload;
text = 'Reconnecting';
break;
case ConnectionStatus.disconnected:
color = Colors.red;
icon = Icons.cloud_off;
text = 'Disconnected';
break;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: 4),
Text(
text,
style: TextStyle(color: color, fontSize: 12),
),
],
),
);
}
Widget _buildMessageItem(ChatMessage message) {
final isMyMessage = message.sender == _username;
return Align(
alignment: isMyMessage ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isMyMessage ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
Text(
message.sender,
style: TextStyle(
fontSize: 12,
color: isMyMessage ? Colors.white70 : Colors.black54,
),
),
const SizedBox(height: 4),
Text(
message.content,
style: TextStyle(
color: isMyMessage ? Colors.white : Colors.black,
),
),
const SizedBox(height: 4),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_formatTime(message.timestamp),
style: TextStyle(
fontSize: 10,
color: isMyMessage ? Colors.white70 : Colors.black54,
),
),
if (isMyMessage) ...[
const SizedBox(width: 4),
_buildMessageStatus(message.status),
],
],
),
],
),
),
);
}
Widget _buildMessageStatus(MessageStatus status) {
IconData icon;
Color color;
switch (status) {
case MessageStatus.sent:
icon = Icons.check;
color = Colors.white70;
break;
case MessageStatus.delivered:
icon = Icons.done_all;
color = Colors.white;
break;
case MessageStatus.failed:
icon = Icons.error_outline;
color = Colors.red[300]!;
break;
}
return Icon(icon, size: 14, color: color);
}
String _formatTime(DateTime time) {
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SignalR Chat'),
actions: [_buildConnectionStatus()],
),
body: Column(
children: [
Expanded(
child: ListView.builder(
controller: _scrollController,
reverse: true,
itemCount: _messages.length,
itemBuilder: (context, index) => _buildMessageItem(_messages[index]),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Type a message...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
onSubmitted: (_) => _sendMessage(),
),
),
const SizedBox(width: 8),
FloatingActionButton(
onPressed: _sendMessage,
child: const Icon(Icons.send),
),
],
),
),
],
),
);
}
@override
void dispose() {
_messageController.dispose();
_scrollController.dispose();
_chatPlugin.dispose();
super.dispose();
}
}