diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 6d2f83f..1cf76f6 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -30,6 +30,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/icons/microphone.svg b/assets/icons/microphone.svg
new file mode 100644
index 0000000..0cbd4aa
--- /dev/null
+++ b/assets/icons/microphone.svg
@@ -0,0 +1,12 @@
+
diff --git a/assets/icons/send.svg b/assets/icons/send.svg
new file mode 100644
index 0000000..37f97e2
--- /dev/null
+++ b/assets/icons/send.svg
@@ -0,0 +1,3 @@
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 01197de..5288a28 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -56,7 +56,9 @@
NSLocationWhenInUseUsageDescription
This App requires access to your location to mark your attendance.
NSMicrophoneUsageDescription
- This app requires microphone access to for call.
+ This app requires microphone access for calls and speech-to-text input.
+ NSSpeechRecognitionUsageDescription
+ This app requires speech recognition to convert your voice to text.
NSPhotoLibraryUsageDescription
This app requires photo library access to select image as document & upload it.
UIApplicationSupportsIndirectInputEvents
diff --git a/lib/api/dashboard_api_client.dart b/lib/api/dashboard_api_client.dart
index 2bddb42..fa4d00a 100644
--- a/lib/api/dashboard_api_client.dart
+++ b/lib/api/dashboard_api_client.dart
@@ -414,4 +414,32 @@ class DashboardApiClient {
// postParams,
// );
// }
+
+ Future getChatBotSession() async {
+ String url = "${ApiConsts.erpRest}Atlas_InitiateChat";
+ Map postParams = {};
+ postParams.addAll(AppState().postParamsJson);
+ return await ApiClient().postJsonForObject(
+ (json) {
+ GenericResponseModel responseData = GenericResponseModel.fromJson(json);
+ return responseData;
+ },
+ url,
+ postParams,
+ );
+ }
+
+ Future sendChatBotMessage({required AtlasInitiateChatResponse atlasChatTokenResponse, required String atlasChatText}) async {
+ String url = "${ApiConsts.erpRest}Atlas_ContinueChat";
+ Map postParams = {"Atlas_AccessToken": atlasChatTokenResponse.atlasAccessToken, "Atlas_ChatSessionID": atlasChatTokenResponse.atlasChatSessionId, "Atlas_ChatText": atlasChatText};
+ postParams.addAll(AppState().postParamsJson);
+ return await ApiClient().postJsonForObject(
+ (json) {
+ GenericResponseModel responseData = GenericResponseModel.fromJson(json);
+ return responseData;
+ },
+ url,
+ postParams,
+ );
+ }
}
diff --git a/lib/classes/colors.dart b/lib/classes/colors.dart
index 1b0dd06..4c88da9 100644
--- a/lib/classes/colors.dart
+++ b/lib/classes/colors.dart
@@ -67,4 +67,5 @@ class MyColors {
static const Color lightGreyIconColor = Color(0xff919191);
static const Color selectedBorderColor = Color(0xff37A4BE);
static const Color mazayaRedColor = Color(0xffED1C2B);
+ static const Color hintTextColor = Color(0xff9AA4B2);
}
diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart
index dbec87d..29d22d7 100644
--- a/lib/classes/consts.dart
+++ b/lib/classes/consts.dart
@@ -9,9 +9,9 @@ class ApiConsts {
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://hmgwebservices.com"; // Live server
- static String baseUrl = "https://mohemm.hmg.com"; // New Live server
+ // static String baseUrl = "https://mohemm.hmg.com"; // New Live server
//
- // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
+ static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.20.200.111:1010/";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd
diff --git a/lib/config/routes.dart b/lib/config/routes.dart
index 4653880..49913b5 100644
--- a/lib/config/routes.dart
+++ b/lib/config/routes.dart
@@ -10,6 +10,7 @@ import 'package:mohem_flutter_app/ui/chat/favorite_users_screen.dart';
import 'package:mohem_flutter_app/ui/chat/group_chat_detaied_screen.dart';
import 'package:mohem_flutter_app/ui/chat/group_members.dart';
import 'package:mohem_flutter_app/ui/chat/manage_group.dart';
+import 'package:mohem_flutter_app/ui/chatbot/chatbot_screen.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart';
@@ -32,6 +33,7 @@ import 'package:mohem_flutter_app/ui/misc/request_submit_screen.dart';
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart';
import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_listview_screen.dart';
import 'package:mohem_flutter_app/ui/my_attendance/services_menu_list_screen.dart';
+
// import 'package:mohem_flutter_app/ui/my_attendance/my_attendance_screen.dart';
import 'package:mohem_flutter_app/ui/my_team/create_request.dart';
import 'package:mohem_flutter_app/ui/my_team/employee_details.dart';
@@ -54,6 +56,7 @@ import 'package:mohem_flutter_app/ui/profile/profile_screen.dart';
import 'package:mohem_flutter_app/ui/screens/announcements/announcement_details.dart';
import 'package:mohem_flutter_app/ui/screens/announcements/announcements.dart';
import 'package:mohem_flutter_app/ui/screens/child_education/child_education_assistance.dart';
+
// import 'package:mohem_flutter_app/ui/my_attendance/work_from_home_screen.dart';
import 'package:mohem_flutter_app/ui/screens/eit/add_eit.dart';
import 'package:mohem_flutter_app/ui/screens/event_activity/event_activity.dart';
@@ -191,6 +194,7 @@ class AppRoutes {
static const String chat = "/chat";
static const String chatDetailed = "/chatDetailed";
static const String chatFavoriteUsers = "/chatFavoriteUsers";
+ static const String chatBotHome = "/chatBotHome";
//Group Chat
static const String manageGroup = "/manageGroup";
@@ -223,8 +227,8 @@ class AppRoutes {
newPassword: (BuildContext context) => NewPasswordScreen(),
forgotPassword: (BuildContext context) => ForgotPasswordScreen(),
todayAttendance: (BuildContext context) => TodayAttendanceScreen2(),
- //eit
+ //eit
addEitScreen: (BuildContext context) => AddEITScreen(),
//Work List
@@ -235,7 +239,6 @@ class AppRoutes {
worklistSettings: (BuildContext context) => WorklistSettings(),
// Leave Balance
-
leaveBalance: (BuildContext context) => LeaveBalance(),
addLeaveBalance: (BuildContext context) => AddLeaveBalanceScreen(),
@@ -312,6 +315,7 @@ class AppRoutes {
chat: (BuildContext context) => ChatHome(),
chatDetailed: (BuildContext context) => ChatDetailScreen(),
chatFavoriteUsers: (BuildContext context) => ChatFavoriteUsersScreen(),
+ chatBotHome: (BuildContext context) => ChatBotScreen(),
//Group Chat
manageGroup: (BuildContext context) => ManageGroupScreen(),
@@ -328,6 +332,6 @@ class AppRoutes {
unsafeDeviceScreen: (BuildContext context) => const UnsafeDeviceScreen(),
appUpdateScreen: (BuildContext context) => const AppUpdateScreen(),
childEducation: (BuildContext context) => ChildEducationAssistance(),
- activityScreen:(BuildContext context) => const EventActivityScreen()
+ activityScreen: (BuildContext context) => const EventActivityScreen(),
};
}
diff --git a/lib/models/generic_response_model.dart b/lib/models/generic_response_model.dart
index df94c8b..337572c 100644
--- a/lib/models/generic_response_model.dart
+++ b/lib/models/generic_response_model.dart
@@ -398,6 +398,9 @@ class GenericResponseModel {
bool? isActiveCode;
bool? isSMSSent;
PortalDirectionData? portalDirectionData;
+ AtlasInitiateChatResponse? atlasInitiateChatResponse;
+ AtlasContinueChatResponseDetails? atlasContinueChatResponseDetails;
+
GenericResponseModel({
this.date,
@@ -669,7 +672,10 @@ class GenericResponseModel {
this.ePharmacyGetItemOnHandList,
this.isActiveCode,
this.isSMSSent,
- this.getFADisposalNtfDetails
+ this.getFADisposalNtfDetails,
+ this.atlasInitiateChatResponse,
+ this.atlasContinueChatResponseDetails,
+
});
GenericResponseModel.fromJson(Map json) {
@@ -943,7 +949,6 @@ class GenericResponseModel {
getFADisposalNtfDetails = json['GetFADisposalNtfDetails'] != null ? GetFaDisposalNtfDetails.fromJson(json['GetFADisposalNtfDetails']) : null;
-
if (json['GetEarningsList'] != null) {
getEarningsList = [];
json['GetEarningsList'].forEach((v) {
@@ -1464,6 +1469,10 @@ class GenericResponseModel {
ePharmacyGetItemOnHandList = json['ePharmacy_GetItemOnHandList'];
isActiveCode = json['isActiveCode'];
isSMSSent = json['isSMSSent'];
+ atlasInitiateChatResponse = json["Atlas_InitiateChat_Response"] == null ? null : AtlasInitiateChatResponse.fromJson(json["Atlas_InitiateChat_Response"]);
+ atlasContinueChatResponseDetails = json["Atlas_ContinueChat_ResponseDetails"] == null ? null : AtlasContinueChatResponseDetails.fromJson(json["Atlas_ContinueChat_ResponseDetails"]);
+
+
}
Map toJson() {
@@ -1561,7 +1570,7 @@ class GenericResponseModel {
if (this.getActionHistoryList != null) {
data['GetActionHistoryList'] = this.getActionHistoryList!.map((v) => v.toJson()).toList();
}
- data['GetFADisposalNtfDetails'] =this.getFADisposalNtfDetails;
+ data['GetFADisposalNtfDetails'] = this.getFADisposalNtfDetails;
data['GetAddressDffStructureList'] = this.getAddressDffStructureList;
data['GetAddressNotificationBodyList'] = this.getAddressNotificationBodyList;
@@ -1949,6 +1958,9 @@ class GenericResponseModel {
data['ePharmacy_GetItemOnHandList'] = this.ePharmacyGetItemOnHandList;
data['isActiveCode'] = this.isActiveCode;
data['isSMSSent'] = this.isSMSSent;
+ data["Atlas_InitiateChat_Response"] = atlasInitiateChatResponse?.toJson();
+ data["Atlas_ContinueChat_ResponseDetails"] = atlasContinueChatResponseDetails?.toJson();
+
return data;
}
}
@@ -1974,3 +1986,138 @@ class TicketBookingResult {
TicketBookingResult(this.success, this.clientId);
}
+
+class AtlasInitiateChatResponse {
+ String? atlasAccessToken;
+ String? atlasChatSessionId;
+ String? atlasTokenType;
+
+ AtlasInitiateChatResponse({this.atlasAccessToken, this.atlasChatSessionId, this.atlasTokenType});
+
+ factory AtlasInitiateChatResponse.fromRawJson(String str) => AtlasInitiateChatResponse.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory AtlasInitiateChatResponse.fromJson(Map json) =>
+ AtlasInitiateChatResponse(atlasAccessToken: json["Atlas_AccessToken"], atlasChatSessionId: json["Atlas_ChatSessionID"], atlasTokenType: json["Atlas_TokenType"]);
+
+ Map toJson() => {"Atlas_AccessToken": atlasAccessToken, "Atlas_ChatSessionID": atlasChatSessionId, "Atlas_TokenType": atlasTokenType};
+}
+
+
+
+class AtlasContinueChatResponseDetails {
+ AiResponse? aiResponse;
+ QueryClassification? queryClassification;
+ String? sessionId;
+ AiResponse? userMessage;
+
+ AtlasContinueChatResponseDetails({
+ this.aiResponse,
+ this.queryClassification,
+ this.sessionId,
+ this.userMessage,
+ });
+
+ factory AtlasContinueChatResponseDetails.fromRawJson(String str) => AtlasContinueChatResponseDetails.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory AtlasContinueChatResponseDetails.fromJson(Map json) => AtlasContinueChatResponseDetails(
+ aiResponse: json["ai_response"] == null ? null : AiResponse.fromJson(json["ai_response"]),
+ queryClassification: json["query_classification"] == null ? null : QueryClassification.fromJson(json["query_classification"]),
+ sessionId: json["session_id"],
+ userMessage: json["user_message"] == null ? null : AiResponse.fromJson(json["user_message"]),
+ );
+
+ Map toJson() => {
+ "ai_response": aiResponse?.toJson(),
+ "query_classification": queryClassification?.toJson(),
+ "session_id": sessionId,
+ "user_message": userMessage?.toJson(),
+ };
+}
+
+class AiResponse {
+ DateTime? createdAt;
+ String? id;
+ StructuredData? structuredData;
+ String? text;
+
+ AiResponse({
+ this.createdAt,
+ this.id,
+ this.structuredData,
+ this.text,
+ });
+
+ factory AiResponse.fromRawJson(String str) => AiResponse.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory AiResponse.fromJson(Map json) => AiResponse(
+ createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
+ id: json["id"],
+ structuredData: json["structured_data"] == null ? null : StructuredData.fromJson(json["structured_data"]),
+ text: json["text"],
+ );
+
+ Map toJson() => {
+ "created_at": createdAt?.toIso8601String(),
+ "id": id,
+ "structured_data": structuredData?.toJson(),
+ "text": text,
+ };
+}
+
+class StructuredData {
+ String? answer;
+ List? sources;
+ String? type;
+
+ StructuredData({
+ this.answer,
+ this.sources,
+ this.type,
+ });
+
+ factory StructuredData.fromRawJson(String str) => StructuredData.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory StructuredData.fromJson(Map json) => StructuredData(
+ answer: json["answer"],
+ sources: json["sources"] == null ? [] : List.from(json["sources"]!.map((x) => x)),
+ type: json["type"],
+ );
+
+ Map toJson() => {
+ "answer": answer,
+ "sources": sources == null ? [] : List.from(sources!.map((x) => x)),
+ "type": type,
+ };
+}
+
+class QueryClassification {
+ int? confidence;
+ String? type;
+
+ QueryClassification({
+ this.confidence,
+ this.type,
+ });
+
+ factory QueryClassification.fromRawJson(String str) => QueryClassification.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory QueryClassification.fromJson(Map json) => QueryClassification(
+ confidence: json["confidence"],
+ type: json["type"],
+ );
+
+ Map toJson() => {
+ "confidence": confidence,
+ "type": type,
+ };
+}
\ No newline at end of file
diff --git a/lib/ui/chatbot/chatbot_screen.dart b/lib/ui/chatbot/chatbot_screen.dart
new file mode 100644
index 0000000..5b5989d
--- /dev/null
+++ b/lib/ui/chatbot/chatbot_screen.dart
@@ -0,0 +1,334 @@
+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);
+ }
+}
diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart
index 3122d17..e87bafe 100644
--- a/lib/ui/landing/dashboard_screen.dart
+++ b/lib/ui/landing/dashboard_screen.dart
@@ -315,103 +315,103 @@ class _DashboardScreenState extends State with WidgetsBindingOb
child: Consumer(
builder: (BuildContext context, DashboardProviderModel model, Widget? child) {
return (model.isAttendanceTrackingLoading
- ? GetAttendanceTrackingShimmer()
- : Container(
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(15),
- gradient: const LinearGradient(
- transform: GradientRotation(.46),
- begin: Alignment.topRight,
- end: Alignment.bottomLeft,
- colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor],
- ),
- ),
- child: Stack(
- alignment: Alignment.center,
- children: [
- if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"),
- Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Expanded(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true),
- if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white),
- if (model.isTimeRemainingInSeconds != 0)
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
+ ? GetAttendanceTrackingShimmer()
+ : Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(15),
+ gradient: const LinearGradient(
+ transform: GradientRotation(.46),
+ begin: Alignment.topRight,
+ end: Alignment.bottomLeft,
+ colors: [MyColors.gradiantEndColor, MyColors.gradiantStartColor],
+ ),
+ ),
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ if (model.isTimeRemainingInSeconds == 0) SvgPicture.asset("assets/images/thumb.svg"),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ LocaleKeys.markAttendance.tr().toText14(color: Colors.white, isBold: true),
+ if (model.isTimeRemainingInSeconds == 0) DateTime.now().toString().split(" ")[0].toText12(color: Colors.white),
+ if (model.isTimeRemainingInSeconds != 0)
+ Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ 9.height,
+ Directionality(
+ textDirection: ui.TextDirection.ltr,
+ child: CountdownTimer(
+ endTime: model.endTime,
+ onEnd: null,
+ endWidget: "00:00:00".toText14(color: Colors.white, isBold: true),
+ textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold),
+ ),
+ ),
+ LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white),
+ 9.height,
+ ClipRRect(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ child: LinearProgressIndicator(
+ value: model.progress,
+ minHeight: 8,
+ valueColor: const AlwaysStoppedAnimation(Colors.white),
+ backgroundColor: const Color(0xff196D73),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ).paddingOnly(top: 12, right: 15, left: 12),
+ ),
+ Row(
children: [
- 9.height,
- Directionality(
- textDirection: ui.TextDirection.ltr,
- child: CountdownTimer(
- endTime: model.endTime,
- onEnd: null,
- endWidget: "00:00:00".toText14(color: Colors.white, isBold: true),
- textStyle: const TextStyle(color: Colors.white, fontSize: 14, letterSpacing: -0.48, fontWeight: FontWeight.bold),
- ),
+ Expanded(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ LocaleKeys.checkIn.tr().toText12(color: Colors.white),
+ (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14(
+ color: Colors.white,
+ isBold: true,
+ ),
+ 4.height,
+ ],
+ ).paddingOnly(left: 12, right: 12),
),
- LocaleKeys.timeLeftToday.tr().toText12(color: Colors.white),
- 9.height,
- ClipRRect(
- borderRadius: const BorderRadius.all(Radius.circular(20)),
- child: LinearProgressIndicator(
- value: model.progress,
- minHeight: 8,
- valueColor: const AlwaysStoppedAnimation(Colors.white),
- backgroundColor: const Color(0xff196D73),
+ Container(
+ margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0),
+ width: 45,
+ height: 45,
+ padding: const EdgeInsets.only(left: 10, right: 10),
+ decoration: BoxDecoration(
+ color: const Color(0xff259EA4),
+ borderRadius: BorderRadius.only(
+ bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15),
+ bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0),
+ ),
),
- ),
+ child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"),
+ ).onPress(() {
+ showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true));
+ }),
],
),
- ],
- ).paddingOnly(top: 12, right: 15, left: 12),
- ),
- Row(
- children: [
- Expanded(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- LocaleKeys.checkIn.tr().toText12(color: Colors.white),
- (model.attendanceTracking!.pSwipeIn == null ? "--:--" : model.attendanceTracking!.pSwipeIn).toString().toText14(
- color: Colors.white,
- isBold: true,
- ),
- 4.height,
- ],
- ).paddingOnly(left: 12, right: 12),
+ ],
),
- Container(
- margin: EdgeInsets.only(top: AppState().isArabic(context) ? 6 : 0),
- width: 45,
- height: 45,
- padding: const EdgeInsets.only(left: 10, right: 10),
- decoration: BoxDecoration(
- color: const Color(0xff259EA4),
- borderRadius: BorderRadius.only(
- bottomRight: AppState().isArabic(context) ? const Radius.circular(0) : const Radius.circular(15),
- bottomLeft: AppState().isArabic(context) ? const Radius.circular(15) : const Radius.circular(0),
- ),
- ),
- child: SvgPicture.asset(model.isTimeRemainingInSeconds == 0 ? "assets/images/biometrics.svg" : "assets/images/biometrics.svg"),
- ).onPress(() {
- showMyBottomSheet(context, callBackFunc: () {}, child: MarkAttendanceWidget(model, isFromDashboard: true));
- }),
],
),
- ],
- ),
- ],
- ),
- ).onPress(() {
- Navigator.pushNamed(context, AppRoutes.todayAttendance);
- }))
+ ).onPress(() {
+ Navigator.pushNamed(context, AppRoutes.todayAttendance);
+ }))
.animatedSwither();
},
),
@@ -460,70 +460,70 @@ class _DashboardScreenState extends State with WidgetsBindingOb
flex: 2,
child: RichText(
text:
- AppState().isArabic(context)
- ? TextSpan(
- children: [
- TextSpan(
- text: 'اطلع على مميزات',
- style: TextStyle(
- fontSize: 16,
- letterSpacing: -0.2,
- fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
- fontWeight: FontWeight.w700,
- height: 24 / 16,
- color: Color(0xFF5D5E5E),
- ),
- ),
- TextSpan(
- text: ' مزايا',
- style: TextStyle(
- fontSize: 16,
- fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
- fontWeight: FontWeight.w700,
- letterSpacing: -0.2,
- height: 24 / 16,
- color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
- ),
- ),
- ],
- )
- : TextSpan(
- children: [
- TextSpan(
- text: LocaleKeys.explore.tr() + ' ',
- style: const TextStyle(
- fontSize: 16,
- letterSpacing: -0.2,
- fontFamily: 'Poppins',
- fontWeight: FontWeight.w700,
- height: 24 / 16,
- color: Color(0xFF5D5E5E),
- ),
- ),
- TextSpan(
- text: LocaleKeys.mazaya.tr(),
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w700,
- fontFamily: 'Poppins',
- letterSpacing: -0.2,
- height: 24 / 16,
- color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
- ),
- ),
- TextSpan(
- text: ' ' + LocaleKeys.benefits.tr(),
- style: const TextStyle(
- fontSize: 16,
- letterSpacing: -0.2,
- fontFamily: 'Poppins',
- fontWeight: FontWeight.w700,
- height: 24 / 16,
- color: Color(0xFF5D5E5E),
- ),
- ),
- ],
- ),
+ AppState().isArabic(context)
+ ? TextSpan(
+ children: [
+ TextSpan(
+ text: 'اطلع على مميزات',
+ style: TextStyle(
+ fontSize: 16,
+ letterSpacing: -0.2,
+ fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
+ fontWeight: FontWeight.w700,
+ height: 24 / 16,
+ color: Color(0xFF5D5E5E),
+ ),
+ ),
+ TextSpan(
+ text: ' مزايا',
+ style: TextStyle(
+ fontSize: 16,
+ fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
+ fontWeight: FontWeight.w700,
+ letterSpacing: -0.2,
+ height: 24 / 16,
+ color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
+ ),
+ ),
+ ],
+ )
+ : TextSpan(
+ children: [
+ TextSpan(
+ text: LocaleKeys.explore.tr() + ' ',
+ style: const TextStyle(
+ fontSize: 16,
+ letterSpacing: -0.2,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w700,
+ height: 24 / 16,
+ color: Color(0xFF5D5E5E),
+ ),
+ ),
+ TextSpan(
+ text: LocaleKeys.mazaya.tr(),
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w700,
+ fontFamily: 'Poppins',
+ letterSpacing: -0.2,
+ height: 24 / 16,
+ color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
+ ),
+ ),
+ TextSpan(
+ text: ' ' + LocaleKeys.benefits.tr(),
+ style: const TextStyle(
+ fontSize: 16,
+ letterSpacing: -0.2,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w700,
+ height: 24 / 16,
+ color: Color(0xFF5D5E5E),
+ ),
+ ),
+ ],
+ ),
),
),
const Expanded(flex: 1, child: SizedBox()),
@@ -576,12 +576,10 @@ class _DashboardScreenState extends State with WidgetsBindingOb
LocaleKeys.discounts.tr().toText24(isBold: true),
6.width,
Container(
- padding: const EdgeInsets.only(left: 8, right: 8),
- decoration: BoxDecoration(
- color: MyColors.yellowColor,
- borderRadius: BorderRadius.circular(10),
- ),
- child: LocaleKeys.newString.tr().toText10(isBold: true)),
+ padding: const EdgeInsets.only(left: 8, right: 8),
+ decoration: BoxDecoration(color: MyColors.yellowColor, borderRadius: BorderRadius.circular(10)),
+ child: LocaleKeys.newString.tr().toText10(isBold: true),
+ ),
],
),
],
@@ -589,7 +587,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb
),
LocaleKeys.viewAllOffers.tr().toText12(isUnderLine: true).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
- })
+ }),
],
).paddingOnly(left: 21, right: 21),
Consumer(
@@ -597,59 +595,54 @@ class _DashboardScreenState extends State with WidgetsBindingOb
return SizedBox(
height: 103 + 33,
child: ListView.separated(
- shrinkWrap: true,
- physics: const BouncingScrollPhysics(),
- padding: const EdgeInsets.only(left: 21, right: 21, top: 13),
- scrollDirection: Axis.horizontal,
- itemBuilder: (BuildContext cxt, int index) {
- return model.isOffersLoading
- ? const OffersShimmerWidget()
- : InkWell(
- onTap: () {
- navigateToDetails(data.getOffersList[index]);
- },
- child: SizedBox(
- width: 73,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Container(
- width: 73,
- height: 73,
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: const BorderRadius.all(
- Radius.circular(100),
- ),
- border: Border.all(color: MyColors.lightGreyE3Color, width: 1),
- ),
- child: ClipRRect(
- borderRadius: const BorderRadius.all(
- Radius.circular(50),
+ shrinkWrap: true,
+ physics: const BouncingScrollPhysics(),
+ padding: const EdgeInsets.only(left: 21, right: 21, top: 13),
+ scrollDirection: Axis.horizontal,
+ itemBuilder: (BuildContext cxt, int index) {
+ return model.isOffersLoading
+ ? const OffersShimmerWidget()
+ : InkWell(
+ onTap: () {
+ navigateToDetails(data.getOffersList[index]);
+ },
+ child: SizedBox(
+ width: 73,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: 73,
+ height: 73,
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: const BorderRadius.all(Radius.circular(100)),
+ border: Border.all(color: MyColors.lightGreyE3Color, width: 1),
),
- child: Hero(
- tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString()!,
- transitionOnUserGestures: true,
- child: Image.network(
- data.getOffersList[index].logo ?? "",
- fit: BoxFit.contain,
+ child: ClipRRect(
+ borderRadius: const BorderRadius.all(Radius.circular(50)),
+ child: Hero(
+ tag: "ItemImage" + data.getOffersList[index].offersDiscountId.toString()!,
+ transitionOnUserGestures: true,
+ child: Image.network(data.getOffersList[index].logo ?? "", fit: BoxFit.contain),
),
),
),
- ),
- 4.height,
- Expanded(
- child: AppState().isArabic(context)
- ? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1)
- : data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1),
- ),
- ],
+ 4.height,
+ Expanded(
+ child:
+ AppState().isArabic(context)
+ ? data.getOffersList[index].titleAr!.toText12(isCenter: true, maxLine: 1)
+ : data.getOffersList[index].titleEn!.toText12(isCenter: true, maxLine: 1),
+ ),
+ ],
+ ),
),
- ),
- );
- },
- separatorBuilder: (BuildContext cxt, int index) => 8.width,
- itemCount: 9),
+ );
+ },
+ separatorBuilder: (BuildContext cxt, int index) => 8.width,
+ itemCount: 9,
+ ),
);
},
),
@@ -772,28 +765,28 @@ class _DashboardScreenState extends State with WidgetsBindingOb
SvgPicture.asset(
"assets/icons/chat/chat.svg",
color:
- !checkIfPrivilegedForChat()
- ? MyColors.lightGreyE3Color
- : currentIndex == 4
- ? MyColors.grey3AColor
- : cProvider.disbaleChatForThisUser
- ? MyColors.lightGreyE3Color
- : MyColors.grey98Color,
+ !checkIfPrivilegedForChat()
+ ? MyColors.lightGreyE3Color
+ : currentIndex == 4
+ ? MyColors.grey3AColor
+ : cProvider.disbaleChatForThisUser
+ ? MyColors.lightGreyE3Color
+ : MyColors.grey98Color,
).paddingAll(4),
Consumer(
builder: (BuildContext cxt, ChatProviderModel data, Widget? child) {
return !checkIfPrivilegedForChat()
? const SizedBox()
: Positioned(
- right: 0,
- top: 0,
- child: Container(
- padding: const EdgeInsets.only(left: 4, right: 4),
- alignment: Alignment.center,
- decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)),
- child: data.chatUConvCounter.toString().toText10(color: Colors.white),
- ),
- );
+ right: 0,
+ top: 0,
+ child: Container(
+ padding: const EdgeInsets.only(left: 4, right: 4),
+ alignment: Alignment.center,
+ decoration: BoxDecoration(color: cProvider.disbaleChatForThisUser ? MyColors.pinkDarkColor : MyColors.redColor, borderRadius: BorderRadius.circular(17)),
+ child: data.chatUConvCounter.toString().toText10(color: Colors.white),
+ ),
+ );
},
),
],
@@ -824,6 +817,19 @@ class _DashboardScreenState extends State with WidgetsBindingOb
},
),
),
+ floatingActionButton: FloatingActionButton.large(
+ onPressed: () {
+ Navigator.pushNamed(context, AppRoutes.chatBotHome);
+ },
+ backgroundColor: Colors.transparent,
+ elevation: 0,
+ focusElevation: 0,
+ hoverElevation: 0,
+ highlightElevation: 0,
+
+ shape: const CircleBorder(),
+ child: SvgPicture.asset("assets/icons/chatbot.svg", width: 100, height: 100),
+ ),
),
);
}
@@ -864,4 +870,4 @@ class _DashboardScreenState extends State with WidgetsBindingOb
}
return false;
}
-}
\ No newline at end of file
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 6abe6fc..1234352 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -142,6 +142,8 @@ dependencies:
webview_flutter: ^4.13.0
nfc_manager: ^3.2.0
+ speech_to_text: ^7.3.0
+
# saf: ^1.0.3+4