Compare commits

..

2 Commits

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/consts.dart';
@ -16,11 +18,12 @@ class LoginApiClient {
factory LoginApiClient() => _instance; factory LoginApiClient() => _instance;
Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType) async { Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType, {int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW"; String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW";
Map<String, dynamic> postParams = {}; Map<String, dynamic> postParams = {};
postParams["DeviceToken"] = deviceToken; postParams["DeviceToken"] = deviceToken;
postParams["DeviceType"] = deviceType; postParams["DeviceType"] = deviceType;
postParams["MemberType"] = memberType ?? ApiConsts.selectedMemberType.value;
return await ApiClient().postJsonForObject((json) { return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return (responseData.mohemmGetMobileLoginInfoList?.length ?? 0) > 0 ? (responseData.mohemmGetMobileLoginInfoList!.first) : null; return (responseData.mohemmGetMobileLoginInfoList?.length ?? 0) > 0 ? (responseData.mohemmGetMobileLoginInfoList!.first) : null;
@ -28,7 +31,8 @@ class LoginApiClient {
} }
Future<GenericResponseModel?> insertMobileLoginInfoNEW( Future<GenericResponseModel?> insertMobileLoginInfoNEW(
String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType) async { String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType,
{int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo"; String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo";
Map<String, dynamic> postParams = { Map<String, dynamic> postParams = {
"MobileNumber": mobileNumber, "MobileNumber": mobileNumber,
@ -39,7 +43,8 @@ class LoginApiClient {
"LoginType": loginType, "LoginType": loginType,
"EmployeeName": employeeName, "EmployeeName": employeeName,
"P_SESSION_ID": sessionId, "P_SESSION_ID": sessionId,
"P_EMAIL_ADDRESS": email "P_EMAIL_ADDRESS": email,
"MemberType": memberType ?? ApiConsts.selectedMemberType.value,
}; };
postParams["DeviceToken"] = deviceToken; postParams["DeviceToken"] = deviceToken;
postParams["DeviceType"] = deviceType; postParams["DeviceType"] = deviceType;
@ -169,4 +174,77 @@ class LoginApiClient {
return responseData; return responseData;
}, url, postParams); }, url, postParams);
} }
Future<GenericResponseModel?> memberLoginDependent({
required String nationalId,
required String birthDate,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}MemberLogin_Dependent";
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
"P_DEP_NATIONAL_ID": nationalId,
"P_DEP_BIRTH_DATE": birthDate,
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
if (responseData.logInTokenID != null) {
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
}
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> mohemmSendOTPDependent({
required String nationalId,
required String mobileNumber,
required String logInTokenID,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_SendOTP_Dependent";
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
"OTP_SendType": 1,
"MobileNumber": mobileNumber,
"IsMobileFingerPrint": 0,
"P_DEP_NATIONAL_ID": nationalId,
"P_USER_NAME": nationalId,
"LogInTokenID": logInTokenID,
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> mohemmCheckOTPDependent({
required String logInTokenID,
required String activationCode,
required String nationalId,
required String mobileNumber,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_CheckOTP_Dependent";
Map<String, dynamic> postParams = {
"LogInTokenID": logInTokenID,
"activationCode": activationCode,
"P_USER_NAME": nationalId,
"P_DEP_NATIONAL_ID": nationalId,
"MobileNumber": mobileNumber,
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData;
}, url, postParams);
}
} }

@ -12,9 +12,9 @@ class ApiConsts {
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server // static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server static String ssoBaseUrl = "https://sso.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 = "http://10.20.200.111:1010/";
static String baseUrl = "https://mohemm.hmg.com"; // New Live server // static String baseUrl = "https://mohemm.hmg.com"; // New Live server
//static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT //static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT
static String eLearningBaseUrl = "https://elearning.hmg.com/moodle/api/"; // Elearning server live static String eLearningBaseUrl = "https://elearning.hmg.com/moodle/api/"; // Elearning server live
static String enSATLiveURL = "https://ensat.hmg.com/auth/autologin"; // ENSAT server live static String enSATLiveURL = "https://ensat.hmg.com/auth/autologin"; // ENSAT server live
@ -34,6 +34,11 @@ class ApiConsts {
static String user = baseUrlServices + "api/User/"; static String user = baseUrlServices + "api/User/";
static String cocRest = baseUrlServices + "COCWS.svc/REST/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/";
// Dependent Login Endpoints
static String memberLoginDependent = erpRest + "MemberLogin_Dependent";
static String mohemmSendOTPDependent = erpRest + "Mohemm_SendOTP_Dependent";
static String mohemmCheckOTPDependent = erpRest + "Mohemm_CheckOTP_Dependent";
static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect"; static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect";
@ -85,6 +90,30 @@ class ApiConsts {
static String notificationCenterAppGUID = "VDZreDY5TTNmTDVudzc4aDkwU3ZqcW9OcmI0"; static String notificationCenterAppGUID = "VDZreDY5TTNmTDVudzc4aDkwU3ZqcW9OcmI0";
static String notificationCenterLoginServiceGUID = "D8BD5C76-4B50-4122-81D0-C3AA25DB08A2"; static String notificationCenterLoginServiceGUID = "D8BD5C76-4B50-4122-81D0-C3AA25DB08A2";
static String notificationCenterForgotPasswordServiceGUID = "5CCCC2C9-EEA0-4207-9BE9-EE1FBBFFE8D0"; static String notificationCenterForgotPasswordServiceGUID = "5CCCC2C9-EEA0-4207-9BE9-EE1FBBFFE8D0";
static MemberType selectedMemberType = MemberType.employee;
static bool get ISDependetLogin => selectedMemberType == MemberType.dependent;
static set ISDependetLogin(bool value) {
selectedMemberType = value ? MemberType.dependent : MemberType.employee;
}
static bool get isDependetLogin => ISDependetLogin;
static set isDependetLogin(bool value) => ISDependetLogin = value;
}
enum MemberType {
employee(1),
dependent(2);
final int value;
const MemberType(this.value);
bool get isEmployee => this == MemberType.employee;
bool get isDependent => this == MemberType.dependent;
static MemberType fromValue(int? value) {
if (value == 2) return MemberType.dependent;
return MemberType.employee;
}
} }
class SharedPrefsConsts { class SharedPrefsConsts {
@ -100,3 +129,7 @@ class SharedPrefsConsts {
static String mohemmWifiPassword = "mohemmWifiPassword"; static String mohemmWifiPassword = "mohemmWifiPassword";
static String editItemForSale = "editItemForSale"; static String editItemForSale = "editItemForSale";
} }

@ -14,6 +14,7 @@ import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen_for_dependet.dart';
import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_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'; import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart';
import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart';
@ -97,6 +98,7 @@ class AppRoutes {
static const String newPassword = "/newPassword"; static const String newPassword = "/newPassword";
static const String loginVerification = "/loginVerification"; static const String loginVerification = "/loginVerification";
static const String dashboard = "/dashboard"; static const String dashboard = "/dashboard";
static const String dashboardScreenForDependet = "/dashboardScreenForDependet";
static const String todayAttendance = "/todayAttendance"; static const String todayAttendance = "/todayAttendance";
static const String subMenuScreen = "/submenuScreen"; static const String subMenuScreen = "/submenuScreen";
static const String addEitScreen = "/addeitScreen"; static const String addEitScreen = "/addeitScreen";
@ -231,6 +233,7 @@ class AppRoutes {
verifyLogin: (BuildContext context) => VerifyLoginScreen(), verifyLogin: (BuildContext context) => VerifyLoginScreen(),
verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(), verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(),
dashboard: (BuildContext context) => DashboardScreen(), dashboard: (BuildContext context) => DashboardScreen(),
dashboardScreenForDependet: (BuildContext context) => const DashboardScreenForDependet(),
survey: (BuildContext context) => SurveyScreen(), survey: (BuildContext context) => SurveyScreen(),
advertisement: (BuildContext context) => ITGAdsScreen(), advertisement: (BuildContext context) => ITGAdsScreen(),

@ -400,6 +400,9 @@ class GenericResponseModel {
PortalDirectionData? portalDirectionData; PortalDirectionData? portalDirectionData;
String? Mohemm_ENSAT_GetItgToken_Response; String? Mohemm_ENSAT_GetItgToken_Response;
GetWalaplusDepInfoModel? getWalaplusDepInfoModel;
dynamic getWalaplusDepInfoModelInternal;
GenericResponseModel({ GenericResponseModel({
this.date, this.date,
this.languageID, this.languageID,
@ -672,6 +675,8 @@ class GenericResponseModel {
this.isSMSSent, this.isSMSSent,
this.getFADisposalNtfDetails, this.getFADisposalNtfDetails,
this.Mohemm_ENSAT_GetItgToken_Response, this.Mohemm_ENSAT_GetItgToken_Response,
this.getWalaplusDepInfoModel,
this.getWalaplusDepInfoModelInternal,
}); });
GenericResponseModel.fromJson(Map<String, dynamic> json) { GenericResponseModel.fromJson(Map<String, dynamic> json) {
@ -1466,6 +1471,9 @@ class GenericResponseModel {
isActiveCode = json['isActiveCode']; isActiveCode = json['isActiveCode'];
isSMSSent = json['isSMSSent']; isSMSSent = json['isSMSSent'];
Mohemm_ENSAT_GetItgToken_Response = json['Mohemm_ENSAT_GetItgToken_Response']; Mohemm_ENSAT_GetItgToken_Response = json['Mohemm_ENSAT_GetItgToken_Response'];
getWalaplusDepInfoModel = json['GET_WALAPLUS_DEP_INFO_Model'] != null ? GetWalaplusDepInfoModel.fromJson(json['GET_WALAPLUS_DEP_INFO_Model']) : null;
getWalaplusDepInfoModelInternal = json['GET_WALAPLUS_DEP_INFO_Model_Internal'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -1952,6 +1960,12 @@ class GenericResponseModel {
data['isActiveCode'] = this.isActiveCode; data['isActiveCode'] = this.isActiveCode;
data['isSMSSent'] = this.isSMSSent; data['isSMSSent'] = this.isSMSSent;
data['Mohemm_ENSAT_GetItgToken_Response'] = this.Mohemm_ENSAT_GetItgToken_Response; data['Mohemm_ENSAT_GetItgToken_Response'] = this.Mohemm_ENSAT_GetItgToken_Response;
data['GET_WALAPLUS_DEP_INFO_Model_Internal'] = this.getWalaplusDepInfoModelInternal;
if (this.getWalaplusDepInfoModel != null) {
data['GET_WALAPLUS_DEP_INFO_Model'] = this.getWalaplusDepInfoModel!.toJson();
}
return data; return data;
} }
} }
@ -1977,3 +1991,14 @@ class TicketBookingResult {
TicketBookingResult(this.success, this.clientId); TicketBookingResult(this.success, this.clientId);
} }
class GetWalaplusDepInfoModel {
String? pEmpEmailAddress;
String? pEmpMobileNumber;
GetWalaplusDepInfoModel({this.pEmpEmailAddress, this.pEmpMobileNumber});
factory GetWalaplusDepInfoModel.fromJson(Map<String, dynamic> json) => GetWalaplusDepInfoModel(pEmpEmailAddress: json["P_EMP_EMAIL_ADDRESS"], pEmpMobileNumber: json["P_EMP_MOBILE_NUMBER"]);
Map<String, dynamic> toJson() => {"P_EMP_EMAIL_ADDRESS": pEmpEmailAddress, "P_EMP_MOBILE_NUMBER": pEmpMobileNumber};
}

@ -0,0 +1,232 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/dynamic_forms/dynamic_textfield_widget.dart';
class DashboardScreenForDependet extends StatefulWidget {
const DashboardScreenForDependet({Key? key}) : super(key: key);
@override
State<DashboardScreenForDependet> createState() => _DashboardScreenForDependetState();
}
class _DashboardScreenForDependetState extends State<DashboardScreenForDependet> {
List<OffersListModel> offersList = [];
List<OffersListModel> _filteredOffersList = [];
bool isLoading = false;
String searchQuery = "";
@override
void initState() {
super.initState();
fetchOffers();
}
/// Offers API placeholder - will be connected when the endpoint is provided
Future<void> fetchOffers() async {
setState(() {
isLoading = true;
});
try {
// TODO: Call Dependent Offers API here once provided
// Example:
// offersList = await OffersApiClient().getDependentOffers();
await Future.delayed(const Duration(milliseconds: 300));
_filteredOffersList = offersList;
} catch (e) {
print("Error fetching dependent offers: $e");
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
void _runFilter(String query) {
searchQuery = query;
if (query.isEmpty) {
_filteredOffersList = offersList;
} else {
bool isAr = AppState().isArabic(context);
_filteredOffersList =
offersList.where((offer) {
String title = isAr ? (offer.titleAr ?? "") : (offer.titleEn ?? "");
return title.toLowerCase().contains(query.toLowerCase());
}).toList();
}
setState(() {});
}
void _performLogout() {
Utils.confirmDialog(
context,
AppState().isArabic(context) ? "هل أنت متأكد من تسجيل الخروج؟" : "Are you sure you want to log out?",
onTap: () {
ApiConsts.selectedMemberType = MemberType.employee;
ApiConsts.ISDependetLogin = false;
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route<dynamic> route) => false);
},
);
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
return Scaffold(
backgroundColor: MyColors.backgroundColor,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
surfaceTintColor: Colors.transparent,
automaticallyImplyLeading: false,
title: Row(children: [(isAr ? "عروض التابعين" : "Offers & Discounts").toText20(color: MyColors.darkTextColor, isBold: true)]),
actions: [
// Logout button
Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(color: MyColors.redColor.withOpacity(0.08), borderRadius: BorderRadius.circular(8)),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: _performLogout,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [const Icon(Icons.logout_rounded, size: 18, color: MyColors.redColor), 6.width, (isAr ? "خروج" : "Logout").toText13(color: MyColors.redColor)],
),
),
),
),
],
),
body: RefreshIndicator(
onRefresh: fetchOffers,
color: MyColors.textMixColor,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
padding: const EdgeInsets.all(21),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Search input
DynamicTextFieldWidget(
LocaleKeys.search.tr(),
LocaleKeys.searchItems.tr(),
isEnable: true,
suffixIconData: Icons.search,
isPopup: false,
lines: 1,
isInputTypeNum: false,
isReadOnly: false,
onChange: (String value) {
_runFilter(value);
},
),
20.height,
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
(isAr ? "العروض المتاحة" : "Available Offers").toText18(isBold: true, color: MyColors.darkTextColor),
if (offersList.isNotEmpty) "${_filteredOffersList.length} ${isAr ? "عرض" : "offers"}".toText12(color: MyColors.grey70Color),
],
),
16.height,
// Content Area
if (isLoading)
const Center(child: Padding(padding: EdgeInsets.symmetric(vertical: 60), child: CircularProgressIndicator(color: MyColors.textMixColor)))
else if (_filteredOffersList.isEmpty)
_buildEmptyOffersWidget(isAr)
else
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 162 / 266, crossAxisSpacing: 12, mainAxisSpacing: 12),
itemCount: _filteredOffersList.length,
itemBuilder: (context, index) {
return _buildOfferCard(_filteredOffersList[index], isAr);
},
),
],
),
),
),
);
}
Widget _buildEmptyOffersWidget(bool isAr) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 60, horizontal: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(color: MyColors.textMixColor.withOpacity(0.1), shape: BoxShape.circle),
child: const Icon(Icons.local_offer_outlined, size: 40, color: MyColors.textMixColor),
),
16.height,
(isAr ? "لا توجد عروض متاحة حالياً" : "No Offers Available Currently").toText16(isBold: true, color: MyColors.darkTextColor),
8.height,
(isAr ? "سيتم إتاحة العروض الخاصة بك قريباً" : "Exclusive offers for dependents will be available soon.").toText13(color: MyColors.grey70Color),
24.height,
DefaultButton(isAr ? "تحديث" : "Refresh", fetchOffers, colors: const [MyColors.gradiantStartColor, MyColors.gradiantEndColor]),
],
),
),
);
}
Widget _buildOfferCard(OffersListModel item, bool isAr) {
return Container(
padding: const EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [BoxShadow(color: const Color(0xff000000).withOpacity(.05), blurRadius: 26, offset: const Offset(0, -3))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
AspectRatio(
aspectRatio: 118 / 127,
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child:
(item.bannerImage != null && item.bannerImage!.isNotEmpty)
? Image.network(
item.bannerImage!,
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => Container(color: MyColors.greyF7Color, child: const Icon(Icons.broken_image, color: MyColors.grey70Color)),
)
: Container(color: MyColors.greyF7Color, child: const Icon(Icons.image_outlined, color: MyColors.grey70Color)),
),
),
8.height,
(isAr ? (item.titleAr ?? "") : (item.titleEn ?? "")).toText16(isBold: true, color: MyColors.darkTextColor, maxlines: 1),
if (item.discountDescription != null && item.discountDescription!.isNotEmpty) ...[4.height, item.discountDescription!.toText14(isBold: true, maxlines: 1)],
],
),
);
}
}

@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
// import 'package:huawei_hmsavailability/huawei_hmsavailability.dart'; // import 'package:huawei_hmsavailability/huawei_hmsavailability.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart'; import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:intl/intl.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/notifications.dart'; import 'package:mohem_flutter_app/classes/notifications.dart';
@ -48,6 +49,9 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> { class _LoginScreenState extends State<LoginScreen> {
TextEditingController username = TextEditingController(); TextEditingController username = TextEditingController();
TextEditingController password = TextEditingController(); TextEditingController password = TextEditingController();
TextEditingController nationalId = TextEditingController();
TextEditingController dobController = TextEditingController();
DateTime? selectedDob;
CheckMobileAppVersionModel? _checkMobileAppVersion; CheckMobileAppVersionModel? _checkMobileAppVersion;
MemberLoginListModel? _memberLoginList; MemberLoginListModel? _memberLoginList;
@ -70,6 +74,8 @@ class _LoginScreenState extends State<LoginScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
ApiConsts.selectedMemberType = MemberType.employee;
ApiConsts.ISDependetLogin = false;
// hmsApiAvailability = HmsApiAvailability(); // hmsApiAvailability = HmsApiAvailability();
// checkFirebaseToken(); // checkFirebaseToken();
if (kReleaseMode) { if (kReleaseMode) {
@ -99,6 +105,7 @@ class _LoginScreenState extends State<LoginScreen> {
@override @override
void dispose() { void dispose() {
dobController.dispose();
super.dispose(); super.dispose();
} }
@ -158,7 +165,15 @@ class _LoginScreenState extends State<LoginScreen> {
void checkLoginInfo() async { void checkLoginInfo() async {
try { try {
loginInfo = await LoginApiClient().getMobileLoginInfoNEW(AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken ?? "", Platform.isAndroid ? "android" : "ios"); loginInfo = await LoginApiClient().getMobileLoginInfoNEW(
AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken ?? "",
Platform.isAndroid ? "android" : "ios",
memberType: ApiConsts.selectedMemberType.value,
);
if (ApiConsts.selectedMemberType.isDependent) {
Utils.hideLoading(context);
return;
}
if (loginInfo == null) { if (loginInfo == null) {
await checkPrefs(); await checkPrefs();
_autoLogin = false; _autoLogin = false;
@ -279,6 +294,268 @@ class _LoginScreenState extends State<LoginScreen> {
} }
} }
void _onTabChanged(MemberType type) {
if (ApiConsts.selectedMemberType != type) {
setState(() {
ApiConsts.selectedMemberType = type;
});
checkLoginInfo();
}
}
Widget _buildTabSwitcher() {
bool isEmployee = ApiConsts.selectedMemberType == MemberType.employee;
bool isAr = AppState().isArabic(context);
return Container(
height: 46,
decoration: BoxDecoration(
color: const Color(0xffF2F4F7),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(4),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: isEmployee ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(9),
boxShadow: isEmployee
? [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: null,
),
alignment: Alignment.center,
child: (isAr ? "الموظف" : "Employee").toText14(
color: isEmployee ? MyColors.textMixColor : MyColors.grey70Color,
isBold: isEmployee,
),
).onPress(() {
_onTabChanged(MemberType.employee);
}),
),
Expanded(
child: Container(
decoration: BoxDecoration(
color: !isEmployee ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(9),
boxShadow: !isEmployee
? [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: null,
),
alignment: Alignment.center,
child: (isAr ? "التابع" : "Dependent").toText14(
color: !isEmployee ? MyColors.textMixColor : MyColors.grey70Color,
isBold: !isEmployee,
),
).onPress(() {
_onTabChanged(MemberType.dependent);
}),
),
],
),
);
}
Future<void> _selectDateOfBirth(BuildContext context) async {
DateTime initialDate = selectedDob ?? DateTime(2000, 1, 1);
DateTime firstDate = DateTime(1920, 1, 1);
DateTime lastDate = DateTime.now();
DateTime? picked = await showDatePicker(
context: context,
initialDate: initialDate.isAfter(lastDate) ? lastDate : initialDate,
firstDate: firstDate,
lastDate: lastDate,
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(
primary: MyColors.textMixColor,
onPrimary: Colors.white,
onSurface: MyColors.darkTextColor,
),
),
child: child!,
);
},
);
if (picked != null) {
setState(() {
selectedDob = picked;
dobController.text = DateFormat('yyyy-MM-dd', 'en_US').format(picked);
});
}
}
String getFormattedBirthDate() {
DateTime? date = selectedDob;
if (date == null && dobController.text.trim().isNotEmpty) {
String text = dobController.text.trim();
try {
if (text.contains('-')) {
List<String> parts = text.split('-');
if (parts[0].length == 4) {
// yyyy-MM-dd
date = DateTime.utc(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
} else if (parts[2].length == 4) {
// dd-MM-yyyy
date = DateTime.utc(int.parse(parts[2]), int.parse(parts[1]), int.parse(parts[0]));
}
} else if (text.contains('/')) {
List<String> parts = text.split('/');
if (parts[0].length == 4) {
// yyyy/MM/dd
date = DateTime.utc(int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
} else if (parts[2].length == 4) {
// dd/MM/yyyy
date = DateTime.utc(int.parse(parts[2]), int.parse(parts[1]), int.parse(parts[0]));
}
} else {
DateTime parsed = DateTime.parse(text);
date = DateTime.utc(parsed.year, parsed.month, parsed.day);
}
} catch (_) {
return "";
}
}
if (date != null) {
int utcMillis = DateTime.utc(date.year, date.month, date.day).millisecondsSinceEpoch;
return "/Date($utcMillis)/";
}
return "";
}
void performDependentLogin() async {
if (nationalId.text.trim().isEmpty) {
Utils.showToast(AppState().isArabic(context) ? "الرجاء إدخال رقم الهوية الوطنية" : "Please enter National ID");
return;
}
String birthDateStr = getFormattedBirthDate();
if (birthDateStr.isEmpty) {
Utils.showToast(AppState().isArabic(context) ? "الرجاء تحديد تاريخ الميلاد" : "Please select Date of Birth");
return;
}
Utils.showLoading(context);
try {
GenericResponseModel? depLoginResponse = await LoginApiClient().memberLoginDependent(
nationalId: nationalId.text.trim(),
birthDate: birthDateStr,
context: context,
);
if (depLoginResponse?.errorMessage != null && depLoginResponse!.errorMessage!.isNotEmpty) {
Utils.hideLoading(context);
Utils.showToast(depLoginResponse.errorMessage!);
return;
}
String? logInTokenID = depLoginResponse?.logInTokenID;
String? mobileNumber = depLoginResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber;
if (logInTokenID == null || mobileNumber == null || mobileNumber.isEmpty) {
Utils.hideLoading(context);
Utils.showToast(AppState().isArabic(context) ? "تعذر الحصول على معلومات التابع" : "Unable to retrieve dependent information");
return;
}
// Step 2: Send OTP
GenericResponseModel? sendOtpResponse = await LoginApiClient().mohemmSendOTPDependent(
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
logInTokenID: logInTokenID,
context: context,
);
Utils.hideLoading(context);
if (sendOtpResponse?.messageStatus == 1) {
// Step 3: Open OTP Dialog
_showDependentOtpDialog(logInTokenID, mobileNumber);
} else {
Utils.showToast(sendOtpResponse?.errorMessage ?? (AppState().isArabic(context) ? "فشل إرسال رمز التحقق" : "Failed to send OTP"));
}
} catch (ex) {
print(ex);
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}
void _showDependentOtpDialog(String logInTokenID, String mobileNumber) {
OtpDialog(
context,
1,
int.tryParse(mobileNumber),
(value, TextEditingController _pinPutController) async {
Utils.showLoading(context);
try {
GenericResponseModel? checkOtpResponse = await LoginApiClient().mohemmCheckOTPDependent(
logInTokenID: logInTokenID,
activationCode: value,
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
context: context,
);
Utils.hideLoading(context);
if (checkOtpResponse?.errorMessage != null && checkOtpResponse!.errorMessage!.isNotEmpty) {
Utils.showToast(checkOtpResponse.errorMessage!);
_pinPutController.clear();
otpFieldClear.value = "";
return;
}
Navigator.pop(context);
Utils.showToast(AppState().isArabic(context) ? "تم التحقق بنجاح" : "Verified successfully");
if (ApiConsts.ISDependetLogin) {
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.dashboardScreenForDependet, (route) => false);
}
} catch (ex) {
print(ex);
_pinPutController.clear();
otpFieldClear.value = "";
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
},
() {
Navigator.pop(context);
},
onResendCode: () async {
Utils.showLoading(context);
try {
await LoginApiClient().mohemmSendOTPDependent(
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
logInTokenID: logInTokenID,
context: context,
);
Utils.hideLoading(context);
Utils.showToast(AppState().isArabic(context) ? "تم إعادة إرسال رمز التحقق" : "Verification code resent");
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
},
).displayDialog(context);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isAppOpenBySystem == null) { if (isAppOpenBySystem == null) {
@ -338,23 +615,56 @@ class _LoginScreenState extends State<LoginScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildTabSwitcher(),
20.height,
LocaleKeys.login.tr().toText24(isBold: true), LocaleKeys.login.tr().toText24(isBold: true),
LocaleKeys.pleaseEnterLoginDetails.tr().toText16(), LocaleKeys.pleaseEnterLoginDetails.tr().toText16(),
16.height, 16.height,
InputWidget(LocaleKeys.username.tr(), "123456", username), if (ApiConsts.selectedMemberType == MemberType.employee) ...[
12.height, InputWidget(LocaleKeys.username.tr(), "123456", username),
InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true), 12.height,
9.height, InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true),
Align( 9.height,
alignment: Alignment.centerRight, Align(
child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress( alignment: Alignment.centerRight,
() { child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(
// Navigator.pushNamed(context, AppRoutes.forgotPassword); () {
performForgotPassword(); // Navigator.pushNamed(context, AppRoutes.forgotPassword);
}, performForgotPassword();
},
),
), ),
), 20.height,
20.height, ] else ...[
InputWidget(AppState().isArabic(context) ? "الهوية الوطنية" : "National ID", "1234567890", nationalId, isInputTypeNum: true),
12.height,
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _selectDateOfBirth(context),
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
IgnorePointer(
child: InputWidget(
LocaleKeys.dateOfBirth.tr(),
"YYYY-MM-DD",
dobController,
),
),
Positioned.directional(
textDirection: Directionality.of(context),
end: 16,
child: const Icon(
Icons.calendar_today_outlined,
size: 20,
color: MyColors.grey70Color,
),
),
],
),
),
20.height,
],
// DefaultButton( // DefaultButton(
// "Connect HMG Network", // "Connect HMG Network",
// () async { // () async {
@ -371,7 +681,11 @@ class _LoginScreenState extends State<LoginScreen> {
), ),
DefaultButton(LocaleKeys.login.tr(), () async { DefaultButton(LocaleKeys.login.tr(), () async {
SystemChannels.textInput.invokeMethod('TextInput.hide'); SystemChannels.textInput.invokeMethod('TextInput.hide');
performLogin(); if (ApiConsts.ISDependetLogin) {
performDependentLogin();
} else {
performLogin();
}
}).insideContainer }).insideContainer
], ],
), ),

@ -10,6 +10,7 @@ import 'package:local_auth_android/local_auth_android.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart'; import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/date_uitl.dart'; import 'package:mohem_flutter_app/classes/date_uitl.dart';
import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/config/routes.dart';
@ -386,6 +387,7 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
AppState().getUserName!, AppState().getUserName!,
AppState().getIsHuawei ? AppState().getHuaweiPushToken : mobileLoginInfoListModel!.deviceToken!, AppState().getIsHuawei ? AppState().getHuaweiPushToken : mobileLoginInfoListModel!.deviceToken!,
Platform.isAndroid ? "android" : "ios", Platform.isAndroid ? "android" : "ios",
memberType: MemberType.employee.value,
); );
AppState().setMemberInformationListModel = genericResponseModel!.memberInformationList?.first; AppState().setMemberInformationListModel = genericResponseModel!.memberInformationList?.first;
AppState().setPrivilegeListModel = genericResponseModel!.privilegeList ?? []; AppState().setPrivilegeListModel = genericResponseModel!.privilegeList ?? [];

@ -650,7 +650,8 @@ class _VerifyLoginScreenState extends State<VerifyLoginScreen> {
AppState().memberLoginList?.pMOBILENUMBER ?? "", AppState().memberLoginList?.pMOBILENUMBER ?? "",
AppState().getUserName!, AppState().getUserName!,
AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken!, AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken!,
Platform.isAndroid ? "android" : "ios"); Platform.isAndroid ? "android" : "ios",
memberType: MemberType.employee.value);
if (genericResponseModel?.errorMessage != null) { if (genericResponseModel?.errorMessage != null) {
Utils.showToast(genericResponseModel?.errorMessage ?? ""); Utils.showToast(genericResponseModel?.errorMessage ?? "");
} else { } else {

Loading…
Cancel
Save