Files
LearnIT/lib/features/ai_tutor/presentation/widgets/chat_input.dart

416 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../../../../core/services/rag_service.dart';
/// Enhanced chat input widget with suggestions and mode selection
class ChatInput extends StatefulWidget {
final TextEditingController controller;
final VoidCallback onSend;
final ValueChanged<TutorMode>? onModeChanged;
final TutorMode currentMode;
final bool isLoading;
final List<String> suggestions;
final VoidCallback? onClear;
const ChatInput({
super.key,
required this.controller,
required this.onSend,
this.onModeChanged,
this.currentMode = TutorMode.explanation,
this.isLoading = false,
this.suggestions = const [],
this.onClear,
});
@override
State<ChatInput> createState() => _ChatInputState();
}
class _ChatInputState extends State<ChatInput> {
bool _showSuggestions = false;
bool _isExpanded = false;
final FocusNode _focusNode = FocusNode();
@override
void initState() {
super.initState();
_focusNode.addListener(_onFocusChange);
}
@override
void dispose() {
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
super.dispose();
}
void _onFocusChange() {
setState(() {
_showSuggestions = _focusNode.hasFocus && widget.suggestions.isNotEmpty;
});
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, -5),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Mode selector
_buildModeSelector(context),
const SizedBox(height: 12),
// Input field with send button
_buildInputField(context),
// Suggestions
if (_showSuggestions) ...[
const SizedBox(height: 8),
_buildSuggestions(context),
],
],
),
).animate().slideY(begin: 1.0, end: 0.0, duration: const Duration(milliseconds: 300));
}
Widget _buildModeSelector(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey[200]!,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Modo de Tutoria',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
),
const SizedBox(height: 8),
Row(
children: TutorMode.values.map((mode) => _buildModeButton(mode)).toList(),
),
],
),
);
}
Widget _buildModeButton(TutorMode mode) {
final isSelected = widget.currentMode == mode;
final modeInfo = _getModeInfo(mode);
return Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: InkWell(
onTap: () => widget.onModeChanged?.call(mode),
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
decoration: BoxDecoration(
gradient: isSelected
? LinearGradient(
colors: [
modeInfo['color'] as Color,
modeInfo['colorDark'] as Color,
],
)
: null,
color: isSelected ? null : Colors.transparent,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected ? Colors.transparent : Colors.grey[300]!,
),
),
child: Column(
children: [
Icon(
modeInfo['icon'] as IconData,
size: 20,
color: isSelected ? Colors.white : Colors.grey[600],
),
const SizedBox(height: 4),
Text(
modeInfo['label'] as String,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: isSelected ? Colors.white : Colors.grey[600],
),
textAlign: TextAlign.center,
),
],
),
),
),
),
);
}
Widget _buildInputField(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [
Colors.grey[100]!,
Colors.grey[50]!,
],
),
border: Border.all(
color: Colors.grey[300]!,
),
),
child: Row(
children: [
// Text field
Expanded(
child: TextField(
controller: widget.controller,
focusNode: _focusNode,
maxLines: _isExpanded ? 5 : 1,
style: const TextStyle(
fontSize: 16,
color: Color(0xFF2D3748),
),
decoration: InputDecoration(
hintText: 'Faça sua pergunta sobre o conteúdo...',
hintStyle: TextStyle(
color: Colors.grey[500],
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
onSubmitted: (_) => _handleSend(),
),
),
// Action buttons
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Expand/Collapse button
if (widget.controller.text.isNotEmpty) ...[
IconButton(
onPressed: () => setState(() => _isExpanded = !_isExpanded),
icon: Icon(
_isExpanded ? Icons.compress : Icons.expand,
color: Colors.grey[600],
size: 20,
),
tooltip: _isExpanded ? 'Reduzir' : 'Expandir',
),
],
// Clear button
if (widget.controller.text.isNotEmpty)
IconButton(
onPressed: () {
widget.controller.clear();
widget.onClear?.call();
setState(() {
_isExpanded = false;
_showSuggestions = false;
});
},
icon: Icon(
Icons.clear,
color: Colors.grey[600],
size: 20,
),
tooltip: 'Limpar',
),
// Send button
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: widget.controller.text.isNotEmpty
? const LinearGradient(
colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: widget.controller.text.isNotEmpty ? null : Colors.grey[300],
borderRadius: BorderRadius.circular(24),
boxShadow: widget.controller.text.isNotEmpty
? [
BoxShadow(
color: const Color(0xFF82C9BD).withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: null,
),
child: IconButton(
onPressed: widget.controller.text.isNotEmpty && !widget.isLoading
? _handleSend
: null,
icon: widget.isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: Icon(
Icons.send,
color: Colors.white,
size: 20,
),
),
),
],
),
),
],
),
);
}
Widget _buildSuggestions(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey[200]!,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.lightbulb,
size: 16,
color: Colors.grey[600],
),
const SizedBox(width: 6),
Text(
'Sugestões',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[700],
),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 4,
children: widget.suggestions.take(6).map((suggestion) {
return InkWell(
onTap: () {
widget.controller.text = suggestion;
_focusNode.requestFocus();
},
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF82C9BD).withOpacity(0.1),
const Color(0xFF6BA5A0).withOpacity(0.1),
],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color(0xFF82C9BD).withOpacity(0.3),
),
),
child: Text(
suggestion,
style: TextStyle(
fontSize: 12,
color: const Color(0xFF82C9BD),
fontWeight: FontWeight.w500,
),
),
),
);
}).toList(),
),
],
),
).animate().fadeIn(duration: const Duration(milliseconds: 200));
}
Map<String, dynamic> _getModeInfo(TutorMode mode) {
switch (mode) {
case TutorMode.explanation:
return {
'label': 'Explicação',
'icon': Icons.school,
'color': const Color(0xFF82C9BD),
'colorDark': const Color(0xFF6BA5A0),
};
case TutorMode.tutor:
return {
'label': 'Tutor',
'icon': Icons.psychology,
'color': const Color(0xFFF68D2D),
'colorDark': const Color(0xFFE67E22),
};
case TutorMode.exploration:
return {
'label': 'Exploração',
'icon': Icons.explore,
'color': const Color(0xFF9C27B0),
'colorDark': const Color(0xFF7B1FA2),
};
}
}
void _handleSend() {
if (widget.controller.text.trim().isNotEmpty && !widget.isLoading) {
widget.onSend();
setState(() {
_isExpanded = false;
_showSuggestions = false;
});
}
}
}