import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:speech_to_text/speech_recognition_result.dart'; import 'package:speech_to_text/speech_to_text.dart'; enum ChatMode { home, chatActive } class ChatMessage { final String text; final bool isUser; final DateTime timestamp; ChatMessage({required this.text, required this.isUser, required this.timestamp}); } class ChatBotScreen extends StatefulWidget { const ChatBotScreen({Key? key}) : super(key: key); @override State createState() => _ChatBotScreenState(); } class _ChatBotScreenState extends State { ChatMode _currentMode = ChatMode.home; final List _messages = []; final TextEditingController _messageController = TextEditingController(); final ScrollController _scrollController = ScrollController(); AtlasInitiateChatResponse? atlasChatTokenResponse; // Speech to text final SpeechToText _speechToText = SpeechToText(); bool _speechEnabled = false; @override void initState() { // TODO: implement initState super.initState(); print("Chat Bot Launched"); getChatSession(); _initSpeech(); } /// Initialize speech to text void _initSpeech() async { _speechEnabled = await _speechToText.initialize(); setState(() {}); } /// Start listening for speech void _startListening() async { await _speechToText.listen(onResult: _onSpeechResult); setState(() {}); } /// Stop listening for speech void _stopListening() async { await _speechToText.stop(); setState(() {}); } /// Callback when speech is recognized void _onSpeechResult(SpeechRecognitionResult result) { setState(() { _messageController.text = result.recognizedWords; // Move cursor to end of text _messageController.selection = TextSelection.fromPosition(TextPosition(offset: _messageController.text.length)); }); } void getChatSession() async { try { GenericResponseModel? res = await DashboardApiClient().getChatBotSession(); if (res != null) { atlasChatTokenResponse = res.atlasInitiateChatResponse; } } catch (ex) { Utils.handleException(ex, context, null); } } @override void dispose() { atlasChatTokenResponse = null; _messageController.dispose(); _scrollController.dispose(); super.dispose(); } void _sendMessage() async { String text = _messageController.text.trim(); if (text.isEmpty || atlasChatTokenResponse == null) return; // setState(() { // _messages.add(ChatMessage(text: text, isUser: true, timestamp: DateTime.now())); // _currentMode = ChatMode.chatActive; // _messageController.clear(); // }); // // // Simulate bot response // Future.delayed(const Duration(milliseconds: 500), () { // if (mounted) { // setState(() { // _messages.add(ChatMessage(text: "Hello there! How may I assist you today?", isUser: false, timestamp: DateTime.now())); // }); // _scrollToBottom(); // } // }); try { setState(() { _messages.add(ChatMessage(text: text, isUser: true, timestamp: DateTime.now())); _currentMode = ChatMode.chatActive; _messageController.clear(); }); GenericResponseModel? res = await DashboardApiClient().sendChatBotMessage(atlasChatTokenResponse: atlasChatTokenResponse!, atlasChatText: text); if (res != null) { res.atlasContinueChatResponseDetails; if (mounted) { setState(() { _messages.add( ChatMessage( text: res.atlasContinueChatResponseDetails!.aiResponse!.structuredData!.answer ?? "", isUser: false, timestamp: res.atlasContinueChatResponseDetails!.aiResponse!.createdAt ?? DateTime.now(), ), ); }); } _scrollToBottom(); } } catch (ex) { Utils.handleException(ex, context, null); } _scrollToBottom(); } void _scrollToBottom() { Future.delayed(const Duration(milliseconds: 100), () { if (_scrollController.hasClients) { _scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); } }); } void _sendSuggestion(String text) { _messageController.text = text; _sendMessage(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MyColors.backgroundColor, appBar: AppBar( title: const Text("ChatBot", style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2B353E))), backgroundColor: MyColors.backgroundColor, centerTitle: false, ), body: Column(children: [Expanded(child: _currentMode == ChatMode.home ? _buildHomeMode() : _buildChatMode()), _buildBottomInputBar()]), ); } Widget _buildHomeMode() { return Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 24.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text("Hello, ask me", textAlign: TextAlign.center, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2E303A))), const Text("anything...", textAlign: TextAlign.center, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2E303A))), const SizedBox(height: 50), Wrap( alignment: WrapAlignment.center, spacing: 8, runSpacing: 12, children: [ _buildSuggestionChip("Nostalgia Perfume", () => _sendSuggestion("Nostalgia Perfume")), _buildSuggestionChip("Al Nafoura", () => _sendSuggestion("Al Nafoura")), _buildSuggestionChip("Al Nafoura", () => _sendSuggestion("Al Nafoura")), _buildSuggestionChip("Al Jadi", () => _sendSuggestion("Al Jadi")), ], ), ], ), ), ); } Widget _buildChatMode() { return ListView.separated( controller: _scrollController, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), itemCount: _messages.length, itemBuilder: (BuildContext context, int index) { ChatMessage message = _messages[index]; return _buildMessageBubble(message); }, separatorBuilder: (BuildContext context, int index) { return const SizedBox(height: 15); }, ); } Widget _buildMessageBubble(ChatMessage message) { bool isUser = message.isUser; return Row( mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (isUser) const Spacer(flex: 2), Flexible( flex: 7, child: Column( crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isUser ? Colors.white : null, gradient: isUser ? null : const LinearGradient(colors: [Color(0xFF32D892), Color(0xFF259CB8)], begin: Alignment.centerLeft, end: Alignment.centerRight), borderRadius: BorderRadius.circular(15), boxShadow: isUser ? null : [BoxShadow(color: const Color(0xFF000000).withValues(alpha: 0.0784), offset: const Offset(0, 0), blurRadius: 13, spreadRadius: 0)], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text(message.text, softWrap: true, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: isUser ? const Color(0xFF2B353E) : MyColors.white)), Align( alignment: Alignment.centerRight, child: Text( _formatTimestamp(message.timestamp), style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, letterSpacing: -0.4, color: isUser ? const Color(0xFF2B353E) : MyColors.whiteColor), ), ), ], ), ), ], ), ), if (!isUser) const Spacer(flex: 2), ], ); } String _formatTimestamp(DateTime timestamp) { String hour = timestamp.hour.toString().padLeft(2, '0'); String minute = timestamp.minute.toString().padLeft(2, '0'); String month = timestamp.month.toString().padLeft(2, '0'); String day = timestamp.day.toString().padLeft(2, '0'); int year = timestamp.year; return "$month/$day/$year $hour:$minute AM"; } Widget _buildSuggestionChip(String label, VoidCallback onTap) { return Material( color: Colors.white, elevation: 3, shadowColor: const Color(0xFF000015).withValues(alpha: 0.0784), borderRadius: BorderRadius.circular(10), child: InkWell( borderRadius: BorderRadius.circular(10), onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: 0.2)), ), ), ); } Widget _buildBottomInputBar() { return Container( decoration: const BoxDecoration(color: MyColors.backgroundColor), child: SafeArea( top: false, child: Row( children: [ // Plus button // IconButton(padding: EdgeInsets.zero, icon: const Icon(Icons.add, color: MyColors.darkTextColor, size: 34), onPressed: () {}), // const SizedBox(width: 12), // Text field Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), height: 48, decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(10), border: BoxBorder.all(width: 1, color: const Color(0xFFE5E5E5))), child: Row( children: [ Expanded( child: TextField( controller: _messageController, onSubmitted: (_) => _sendMessage(), maxLines: 1, textAlignVertical: TextAlignVertical.center, decoration: const InputDecoration( hintText: 'Type a message..', hintStyle: TextStyle(color: MyColors.hintTextColor, fontSize: 14, fontWeight: FontWeight.normal), border: InputBorder.none, isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 10), ), ), ), GestureDetector( onTap: _speechEnabled ? (_speechToText.isNotListening ? _startListening : _stopListening) : null, child: SvgPicture.asset("assets/icons/microphone.svg", colorFilter: ColorFilter.mode(_speechToText.isListening ? Colors.red : MyColors.darkTextColor, BlendMode.srcIn)), ), ], ), ), ), const SizedBox(width: 6), // Send button GestureDetector( onTap: _sendMessage, child: Container(width: 48, height: 48, decoration: const BoxDecoration(color: MyColors.white, shape: BoxShape.circle), child: Center(child: SvgPicture.asset("assets/icons/send.svg"))), ), ], ), ), ).paddingOnly(left: 16, right: 16, bottom: 16); } }