chat_ui_flex 0.2.1
chat_ui_flex: ^0.2.1 copied to clipboard
A flexible and customizable chat UI toolkit for Flutter.
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:chat_ui_flex/chat_ui_flex.dart';
void main() {
runApp(const ChatDemoApp());
}
class ChatDemoApp extends StatefulWidget {
const ChatDemoApp({super.key});
@override
State<ChatDemoApp> createState() => _ChatDemoAppState();
}
class _ChatDemoAppState extends State<ChatDemoApp> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat UI Flex Demo',
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: _isDarkMode ? ThemeMode.dark : ThemeMode.light,
home: HomeScreen(onThemeToggle: _toggleTheme),
);
}
}
class HomeScreen extends StatelessWidget {
final VoidCallback onThemeToggle;
const HomeScreen({super.key, required this.onThemeToggle});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Chat UI Flex Examples'),
actions: [
IconButton(
icon: Icon(
Theme.of(context).brightness == Brightness.dark
? LucideIcons.sun
: LucideIcons.moon,
),
onPressed: onThemeToggle,
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChatScreen(onThemeToggle: onThemeToggle),
),
),
child: const Text('Standard Chat'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
CustomChatScreen(onThemeToggle: onThemeToggle),
),
),
child: const Text('Advanced Customization'),
),
],
),
),
);
}
}
// -----------------------------------------------------------------------------
// STANDARD CHAT IMPLEMENTATION
// -----------------------------------------------------------------------------
class ChatScreen extends StatefulWidget {
final VoidCallback onThemeToggle;
const ChatScreen({super.key, required this.onThemeToggle});
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final List<ChatMessage> _messages = [
ChatMessage(
id: '1',
text: 'Hey there! Checking out the new UI?',
senderName: 'Jane',
timestamp: DateTime.now().subtract(const Duration(minutes: 5)),
isSentByMe: false,
),
ChatMessage(
id: '2',
text: 'Yes! The glassmorphism looks amazing.',
senderName: 'John',
timestamp: DateTime.now().subtract(const Duration(minutes: 4)),
isSentByMe: true,
),
];
bool _isTyping = false;
void _handleSend(String text) {
setState(() {
_messages.insert(
0,
ChatMessage(
id: DateTime.now().toString(),
text: text,
senderName: 'John',
timestamp: DateTime.now(),
isSentByMe: true,
),
);
_isTyping = true;
});
Future<void>.delayed(const Duration(seconds: 2), () {
if (mounted) {
setState(() {
_isTyping = false;
_messages.insert(
0,
ChatMessage(
id: DateTime.now().toString(),
text: "Cannot wait to see what you build with this!",
senderName: 'Jane',
timestamp: DateTime.now(),
isSentByMe: false,
),
);
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
body: Column(
children: [
ChatHeader(onThemeToggle: widget.onThemeToggle),
Expanded(
child: MessageList(messages: _messages, isTyping: _isTyping),
),
InputField(onSend: _handleSend),
],
),
);
}
}
// -----------------------------------------------------------------------------
// CUSTOM CHAT IMPLEMENTATION
// -----------------------------------------------------------------------------
class CustomChatScreen extends StatefulWidget {
final VoidCallback onThemeToggle;
const CustomChatScreen({super.key, required this.onThemeToggle});
@override
State<CustomChatScreen> createState() => _CustomChatScreenState();
}
class _CustomChatScreenState extends State<CustomChatScreen> {
final List<ChatMessage> _messages = [
ChatMessage(
id: '1',
text: 'This is a custom message bubble.',
senderName: 'Bot',
timestamp: DateTime.now().subtract(const Duration(hours: 2)),
isSentByMe: false,
),
ChatMessage(
id: '2',
text: 'And a nice date separator above!',
senderName: 'User',
timestamp: DateTime.now(),
isSentByMe: true,
),
];
void _handleSend(String text) {
setState(() {
_messages.insert(
0,
ChatMessage(
id: DateTime.now().toString(),
text: text,
senderName: 'User',
timestamp: DateTime.now(),
isSentByMe: true,
),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
// Gradient Background
backgroundColor: Colors.indigo.shade50,
body: SafeArea(
child: Column(
children: [
// Custom Header
ChatHeader(
onThemeToggle: widget.onThemeToggle,
backgroundColor: Colors.indigo,
padding: const EdgeInsets.all(16),
elevation: 4,
leading: const CircleAvatar(
backgroundColor: Colors.white,
child: Icon(LucideIcons.bot, color: Colors.indigo),
),
title: const Text(
'Support Bot',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
subtitle: const Text(
'Always here to help',
style: TextStyle(color: Colors.white70, fontSize: 12),
),
actions: [
IconButton(
icon: const Icon(LucideIcons.settings, color: Colors.white),
onPressed: () {},
),
],
),
// Custom Message List
Expanded(
child: MessageList(
messages: _messages,
reverse: true, // Keep standard chat reverse
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 20,
),
messageSpacing: 16,
dateSeparatorBuilder: (context, date) => Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 12),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.grey.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${date.day}/${date.month}/${date.year}",
style: const TextStyle(
fontSize: 10,
color: Colors.black54,
),
),
),
),
messageBuilder: (context, message) {
// Custom Bubble logic
final isMe = message.isSentByMe;
return Align(
alignment: isMe
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.75,
),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isMe ? Colors.indigo : Colors.white,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(16),
topRight: const Radius.circular(16),
bottomLeft: isMe
? const Radius.circular(16)
: Radius.zero,
bottomRight: isMe
? Radius.zero
: const Radius.circular(16),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Text(
message.text,
style: TextStyle(
color: isMe ? Colors.white : Colors.black87,
),
),
),
);
},
),
),
// Custom Input Field
InputField(
onSend: _handleSend,
backgroundColor: Colors.white,
borderRadius: BorderRadius.circular(0), // Rectangular strip
padding: const EdgeInsets.all(0),
hintText: 'Ask something...',
leading: IconButton(
icon: const Icon(LucideIcons.plus, color: Colors.indigo),
onPressed: () {},
),
sendButton: Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Colors.indigo,
shape: BoxShape.circle,
),
child: const Icon(
LucideIcons.arrowUp,
color: Colors.white,
size: 20,
),
),
),
],
),
),
);
}
}