Implementation
static String generateChatScreen(String projectName) {
return '''
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:$projectName/features/chatbot/bloc/chatbot_bloc.dart';
import 'package:$projectName/features/chatbot/bloc/chatbot_event.dart';
import 'package:$projectName/features/chatbot/bloc/chatbot_state.dart';
import 'package:$projectName/features/chatbot/models/chat_message_model.dart';
import 'package:$projectName/features/chatbot/services/chatbot_service.dart';
class ChatbotScreen extends StatefulWidget {
const ChatbotScreen({Key? key}) : super(key: key);
@override
State<ChatbotScreen> createState() => _ChatbotScreenState();
}
class _ChatbotScreenState extends State<ChatbotScreen> {
final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController();
@override
void dispose() {
_messageController.dispose();
_scrollController.dispose();
super.dispose();
}
void _scrollToBottom() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
}
void _sendMessage(BuildContext context) {
if (_messageController.text.trim().isEmpty) return;
final message = _messageController.text.trim();
_messageController.clear();
context.read<ChatbotBloc>().add(SendMessageEvent(message));
_scrollToBottom();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return BlocProvider(
create: (context) => ChatbotBloc(
chatbotService: ChatbotService(),
)..add(InitializeChatEvent()),
child: Scaffold(
appBar: AppBar(
title: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.primary,
theme.colorScheme.primary.withOpacity(0.7),
],
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: theme.colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Icon(
Icons.smart_toy_outlined,
color: theme.colorScheme.onPrimary,
size: 24,
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'AI Assistant',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: Colors.greenAccent,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.greenAccent.withOpacity(0.5),
blurRadius: 4,
spreadRadius: 1,
),
],
),
),
const SizedBox(width: 6),
Text(
'Online',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
],
),
],
),
elevation: 0,
backgroundColor: theme.colorScheme.surface,
),
body: BlocConsumer<ChatbotBloc, ChatbotState>(
listener: (context, state) {
if (state is ChatbotMessageSent || state is ChatbotResponseReceived) {
_scrollToBottom();
}
if (state is ChatbotError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: theme.colorScheme.error,
behavior: SnackBarBehavior.floating,
),
);
}
},
builder: (context, state) {
return Column(
children: [
Expanded(
child: state.messages.isEmpty
? _buildEmptyState(theme)
: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(16),
itemCount: state.messages.length,
itemBuilder: (context, index) {
final message = state.messages[index];
return _buildMessageBubble(
context,
message,
theme,
);
},
),
),
if (state is ChatbotLoading) _buildTypingIndicator(theme),
_buildMessageInput(context, theme, state is ChatbotLoading),
],
);
},
),
),
);
}
Widget _buildEmptyState(ThemeData theme) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.primary.withOpacity(0.1),
theme.colorScheme.primary.withOpacity(0.05),
],
),
shape: BoxShape.circle,
),
child: Icon(
Icons.chat_bubble_outline,
size: 60,
color: theme.colorScheme.primary.withOpacity(0.5),
),
),
const SizedBox(height: 24),
Text(
'Start a conversation',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 48),
child: Text(
'Ask me anything! I\\'m here to help you with information, ideas, and solutions.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
const SizedBox(height: 32),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
_buildSuggestionChip('Tell me a joke', theme),
_buildSuggestionChip('Explain quantum physics', theme),
_buildSuggestionChip('Creative writing tips', theme),
],
),
],
),
);
}
Widget _buildSuggestionChip(String text, ThemeData theme) {
return ActionChip(
label: Text(text),
onPressed: () {
_messageController.text = text;
},
backgroundColor: theme.colorScheme.surfaceVariant,
labelStyle: TextStyle(
color: theme.colorScheme.onSurfaceVariant,
fontSize: 12,
),
side: BorderSide.none,
);
}
Widget _buildMessageBubble(
BuildContext context,
ChatMessage message,
ThemeData theme,
) {
final isUser = message.isUser;
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment:
isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isUser) ...[
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.primary,
theme.colorScheme.primary.withOpacity(0.7),
],
),
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: theme.colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Icon(
Icons.smart_toy_outlined,
size: 20,
color: theme.colorScheme.onPrimary,
),
),
const SizedBox(width: 8),
],
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
gradient: isUser
? LinearGradient(
colors: [
theme.colorScheme.primary,
theme.colorScheme.primary.withOpacity(0.8),
],
)
: null,
color: isUser ? null : theme.colorScheme.surfaceVariant,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(16),
topRight: const Radius.circular(16),
bottomLeft: Radius.circular(isUser ? 16 : 4),
bottomRight: Radius.circular(isUser ? 4 : 16),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Text(
message.text,
style: TextStyle(
color: isUser
? theme.colorScheme.onPrimary
: theme.colorScheme.onSurfaceVariant,
fontSize: 15,
height: 1.4,
),
),
),
),
if (isUser) ...[
const SizedBox(width: 8),
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: theme.colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Icon(
Icons.person,
size: 20,
color: theme.colorScheme.onPrimary,
),
),
],
],
),
);
}
Widget _buildTypingIndicator(ThemeData theme) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.primary,
theme.colorScheme.primary.withOpacity(0.7),
],
),
borderRadius: BorderRadius.circular(18),
),
child: Icon(
Icons.smart_toy_outlined,
size: 20,
color: theme.colorScheme.onPrimary,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildDot(theme, 0),
const SizedBox(width: 4),
_buildDot(theme, 1),
const SizedBox(width: 4),
_buildDot(theme, 2),
],
),
),
],
),
);
}
Widget _buildDot(ThemeData theme, int index) {
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 600),
builder: (context, value, child) {
final delay = index * 0.2;
final animValue = (value - delay).clamp(0.0, 1.0);
final scale = 0.5 + (animValue * 0.5);
return Transform.scale(
scale: scale,
child: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
shape: BoxShape.circle,
),
),
);
},
onEnd: () {
if (mounted) {
setState(() {});
}
},
);
}
Widget _buildMessageInput(
BuildContext context,
ThemeData theme,
bool isLoading,
) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: theme.colorScheme.surface,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: Row(
children: [
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: 'Type your message...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(24),
borderSide: BorderSide.none,
),
filled: true,
fillColor: theme.colorScheme.surfaceVariant,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
prefixIcon: Icon(
Icons.edit_outlined,
color: theme.colorScheme.onSurfaceVariant,
size: 20,
),
),
maxLines: null,
textInputAction: TextInputAction.send,
onSubmitted: (_) => _sendMessage(context),
enabled: !isLoading,
),
),
const SizedBox(width: 8),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
theme.colorScheme.primary,
theme.colorScheme.primary.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: theme.colorScheme.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
icon: Icon(
Icons.send_rounded,
color: theme.colorScheme.onPrimary,
),
onPressed: isLoading ? null : () => _sendMessage(context),
),
),
],
),
),
);
}
}
''';
}