You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
600 lines
22 KiB
Dart
600 lines
22 KiB
Dart
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:mohem_flutter_app/api/profile_api_client.dart';
|
|
import 'package:mohem_flutter_app/app_state/app_state.dart';
|
|
import 'package:mohem_flutter_app/classes/utils.dart';
|
|
import 'package:mohem_flutter_app/config/routes.dart';
|
|
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
|
|
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
|
|
import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart';
|
|
import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart';
|
|
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
|
|
import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart';
|
|
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
|
|
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class FamilyMembers extends StatefulWidget {
|
|
const FamilyMembers({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
_FamilyMembersState createState() => _FamilyMembersState();
|
|
}
|
|
|
|
class _FamilyMembersState extends State<FamilyMembers> {
|
|
List<GetEmployeeContactsList> getEmployeeContactsList = <GetEmployeeContactsList>[];
|
|
bool fromMazaya = false;
|
|
bool _argsLoaded = false;
|
|
bool _isLoading = false;
|
|
|
|
final TextEditingController _searchController = TextEditingController();
|
|
String _searchQuery = "";
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
getEmployeeContacts();
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
if (!_argsLoaded) {
|
|
Object? args = ModalRoute.of(context)?.settings.arguments;
|
|
if (args is Map && args['fromMazaya'] == true) {
|
|
fromMazaya = true;
|
|
}
|
|
_argsLoaded = true;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
bool _isAccessAllowed(GetEmployeeContactsList contact) {
|
|
String? flag = contact.mAZAYAACCESSFLG ?? contact.mazayaAccess?.toString();
|
|
return flag?.trim().toUpperCase() == 'Y';
|
|
}
|
|
|
|
Future<void> _toggleMazayaAccess(GetEmployeeContactsList contact) async {
|
|
bool current = _isAccessAllowed(contact);
|
|
String action = current ? "Revoke" : "Grant";
|
|
|
|
try {
|
|
Utils.showLoading(context);
|
|
await ProfileApiClient().processContactMazayaAccess(
|
|
accessAction: action,
|
|
contactRelationshipId: contact.cONTACTRELATIONSHIPID,
|
|
contactPersonId: contact.cONTACTPERSONID,
|
|
personId: contact.pERSONID,
|
|
);
|
|
|
|
List<GetEmployeeContactsList> rawList = await ProfileApiClient().getEmployeeContacts();
|
|
getEmployeeContactsList = rawList;
|
|
|
|
Utils.hideLoading(context);
|
|
|
|
if (mounted) {
|
|
setState(() {});
|
|
if (action == "Grant") {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute<dynamic>(
|
|
builder: (BuildContext cxt) => MazayaAccessResultScreen(
|
|
isGranted: true,
|
|
dependentName: contact.cONTACTNAME ?? "",
|
|
dependentInitials: _getInitials(contact.cONTACTNAME),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
Utils.showToast(LocaleKeys.revokeMazayaAccess.tr());
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
Utils.hideLoading(context);
|
|
Utils.handleException(ex, context, null);
|
|
}
|
|
}
|
|
|
|
String _getInitials(String? name) {
|
|
if (name == null || name.trim().isEmpty) return "D";
|
|
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
|
|
if (parts.isEmpty) return "D";
|
|
if (parts.length == 1) {
|
|
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
|
|
}
|
|
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
|
|
}
|
|
|
|
List<GetEmployeeContactsList> get _filteredList {
|
|
if (_searchQuery.trim().isEmpty) return getEmployeeContactsList;
|
|
String q = _searchQuery.trim().toLowerCase();
|
|
return getEmployeeContactsList.where((GetEmployeeContactsList item) {
|
|
String name = (item.cONTACTNAME ?? "").toLowerCase();
|
|
String rel = (item.rELATIONSHIP ?? "").toLowerCase();
|
|
String personId = (item.pERSONID?.toString() ?? "").toLowerCase();
|
|
String contactPersonId = (item.cONTACTPERSONID?.toString() ?? "").toLowerCase();
|
|
String contactType = (item.cONTACTTYPE ?? "").toLowerCase();
|
|
return name.contains(q) ||
|
|
rel.contains(q) ||
|
|
personId.contains(q) ||
|
|
contactPersonId.contains(q) ||
|
|
contactType.contains(q);
|
|
}).toList();
|
|
}
|
|
|
|
void getEmployeeContacts() async {
|
|
setState(() {
|
|
_isLoading = true;
|
|
});
|
|
try {
|
|
List<GetEmployeeContactsList> rawList = await ProfileApiClient().getEmployeeContacts();
|
|
getEmployeeContactsList = rawList;
|
|
} catch (ex) {
|
|
Utils.handleException(ex, context, null);
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
GetMenuEntriesList _getContactMenuEntries(BuildContext context) {
|
|
List<GetMenuEntriesList> menuData = context.watch<DashboardProviderModel>().getMenuEntriesList ?? <GetMenuEntriesList>[];
|
|
List<GetMenuEntriesList> arr = menuData.where((GetMenuEntriesList e) => e.requestType == 'CONTACT').toList();
|
|
return arr.isNotEmpty ? arr.first : GetMenuEntriesList();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool isAr = AppState().isArabic(context);
|
|
GetMenuEntriesList menuEntries = _getContactMenuEntries(context);
|
|
bool canAddFamilyMember = menuEntries.addButton == 'Y' || menuEntries.updateButton == 'Y';
|
|
bool canUpdateFamilyMember = menuEntries.updateButton == 'Y';
|
|
bool canDeleteFamilyMember = menuEntries.deleteButton == 'Y';
|
|
|
|
List<GetEmployeeContactsList> displayList = _filteredList;
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF8FAFC),
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(56),
|
|
child: SafeArea(
|
|
child: Container(
|
|
height: 56,
|
|
color: const Color(0xFFF8FAFC),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Row(
|
|
children: <Widget>[
|
|
InkWell(
|
|
borderRadius: BorderRadius.circular(20),
|
|
onTap: () => Navigator.pop(context),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: Icon(
|
|
Icons.arrow_back_ios_new_rounded,
|
|
size: 20,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
(fromMazaya
|
|
? LocaleKeys.myDependents.tr()
|
|
: LocaleKeys.profile_familyDetails.tr())
|
|
.toText20(
|
|
isBold: true,
|
|
color: const Color(0xFF1E293B),
|
|
),
|
|
const Spacer(),
|
|
InkWell(
|
|
borderRadius: BorderRadius.circular(20),
|
|
onTap: () => Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: Icon(
|
|
Icons.home_outlined,
|
|
size: 24,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: Column(
|
|
children: <Widget>[
|
|
// Search Bar
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 12),
|
|
child: Container(
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: const Color(0xFFE2E8F0)),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
|
child: Row(
|
|
children: <Widget>[
|
|
const Icon(Icons.search, color: Color(0xFF64748B), size: 20),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _searchController,
|
|
onChanged: (String val) {
|
|
setState(() {
|
|
_searchQuery = val;
|
|
});
|
|
},
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
isDense: true,
|
|
contentPadding: EdgeInsets.zero,
|
|
hintText: fromMazaya
|
|
? LocaleKeys.searchDependents.tr()
|
|
: LocaleKeys.searchFamilyMembers.tr(),
|
|
hintStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 14),
|
|
),
|
|
),
|
|
),
|
|
if (_searchQuery.isNotEmpty)
|
|
InkWell(
|
|
onTap: () {
|
|
_searchController.clear();
|
|
setState(() {
|
|
_searchQuery = "";
|
|
});
|
|
},
|
|
child: const Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 4),
|
|
child: Icon(Icons.close, color: Color(0xFF94A3B8), size: 18),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Dependents list
|
|
Expanded(
|
|
child: _isLoading
|
|
? Center(
|
|
child: Image.asset(
|
|
"assets/images/logos/loading_mohemm_logo.gif",
|
|
height: 96.0,
|
|
width: 96.0,
|
|
),
|
|
)
|
|
: displayList.isEmpty
|
|
? Utils.getNoDataWidget(context)
|
|
: ListView.separated(
|
|
padding: const EdgeInsets.fromLTRB(20, 4, 20, 20),
|
|
separatorBuilder: (BuildContext cxt, int index) => const SizedBox(height: 12),
|
|
itemCount: displayList.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return _buildDependentCard(
|
|
displayList[index],
|
|
isAr,
|
|
canUpdateFamilyMember,
|
|
canDeleteFamilyMember,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
// Bottom action button: + Add another user
|
|
DefaultButton(
|
|
fromMazaya
|
|
? LocaleKeys.addAnotherUser.tr()
|
|
: LocaleKeys.addNewFamilyMember.tr(),
|
|
(canAddFamilyMember || fromMazaya)
|
|
? () async {
|
|
await Navigator.pushNamed(
|
|
context,
|
|
AppRoutes.addDependentVerify,
|
|
arguments: <String, dynamic>{'fromMazaya': fromMazaya},
|
|
);
|
|
getEmployeeContacts();
|
|
}
|
|
: null,
|
|
iconData: Icons.add,
|
|
isTextExpanded: false,
|
|
height: 48,
|
|
borderRadius: 12,
|
|
).insideContainer,
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDependentCard(
|
|
GetEmployeeContactsList item,
|
|
bool isAr,
|
|
bool canUpdate,
|
|
bool canDelete,
|
|
) {
|
|
bool isAllowed = _isAccessAllowed(item);
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: const Color(0xFFF1F5F9)),
|
|
boxShadow: const <BoxShadow>[
|
|
BoxShadow(
|
|
color: Color(0x0A000000),
|
|
blurRadius: 8,
|
|
offset: Offset(0, 3),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
// Top row: Avatar, Info, and Status Badge
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
// Initials Circle
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFFE6F5F3),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
_getInitials(item.cONTACTNAME),
|
|
style: const TextStyle(
|
|
color: Color(0xFF168A89),
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
|
|
// Name & Details
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: <Widget>[
|
|
Text(
|
|
item.cONTACTNAME ?? "",
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF1E293B),
|
|
),
|
|
),
|
|
const SizedBox(height: 3),
|
|
Row(
|
|
children: <Widget>[
|
|
Flexible(
|
|
child: (item.rELATIONSHIP ?? "").toText12(
|
|
maxLine: 1,
|
|
color: const Color(0xFF168A89),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Top right pill (shown in Mazaya flow)
|
|
if (fromMazaya) ...<Widget>[
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: isAllowed ? const Color(0xFFE8F8F0) : const Color(0xFFFDE8E8),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
isAllowed ? LocaleKeys.active.tr() : LocaleKeys.noAccess.tr(),
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w400,
|
|
color: isAllowed ? const Color(0xFF168A89) : const Color(0xFFDC2626),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 14),
|
|
const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)),
|
|
const SizedBox(height: 12),
|
|
|
|
// Bottom Action Row
|
|
if (fromMazaya) ...<Widget>[
|
|
// Mazaya Access Buttons: Revoke or Allow
|
|
Center(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(8),
|
|
onTap: () => _toggleMazayaAccess(item),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
Icon(
|
|
isAllowed ? Icons.close : Icons.check,
|
|
size: isAllowed ? 16 : 18,
|
|
color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
isAllowed
|
|
? LocaleKeys.revokeMazayaAccess.tr()
|
|
: LocaleKeys.allowMazayaAccess.tr(),
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
|
|
decoration: TextDecoration.underline,
|
|
decorationColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
] else ...<Widget>[
|
|
// Profile Flow: Update and Remove action buttons
|
|
Row(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(10),
|
|
onTap: canUpdate
|
|
? () {
|
|
int? relationId = item.cONTACTRELATIONSHIPID;
|
|
if (relationId != null) {
|
|
showUpdateAlertDialog(context, relationId, 2, "UPDATE");
|
|
}
|
|
}
|
|
: null,
|
|
child: Container(
|
|
height: 38,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(
|
|
color: canUpdate ? const Color(0xFFCBD5E1) : const Color(0xFFE2E8F0),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
Icon(
|
|
Icons.edit_outlined,
|
|
size: 15,
|
|
color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8),
|
|
),
|
|
const SizedBox(width: 6),
|
|
LocaleKeys.update.tr().toText12(
|
|
color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(10),
|
|
onTap: canDelete
|
|
? () {
|
|
int? relationId = item.cONTACTRELATIONSHIPID;
|
|
if (relationId != null) {
|
|
showRemoveAlertDialog(context, relationId);
|
|
}
|
|
}
|
|
: null,
|
|
child: Container(
|
|
height: 38,
|
|
decoration: BoxDecoration(
|
|
color: canDelete ? const Color(0xFFFFF5F5) : Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(
|
|
color: canDelete ? const Color(0xFFFFCDD2) : const Color(0xFFE2E8F0),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
Icon(
|
|
Icons.delete_outline_rounded,
|
|
size: 15,
|
|
color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8),
|
|
),
|
|
const SizedBox(width: 6),
|
|
LocaleKeys.remove.tr().toText12(
|
|
color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void showUpdateAlertDialog(BuildContext context, int relationId, int flag, String actionType) {
|
|
Widget cancelButton = TextButton(
|
|
child: LocaleKeys.cancel.tr().toText14(),
|
|
onPressed: () => Navigator.pop(context),
|
|
);
|
|
Widget continueButton = TextButton(
|
|
child: LocaleKeys.ok.tr().toText14(),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
Navigator.pushNamed(
|
|
context,
|
|
AppRoutes.addUpdateFamilyMember,
|
|
arguments: FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE"),
|
|
).then((_) => getEmployeeContacts());
|
|
},
|
|
);
|
|
AlertDialog alert = AlertDialog(
|
|
title: LocaleKeys.confirm.tr().toText16(isBold: true),
|
|
content: LocaleKeys.updateMember.tr().toText14(),
|
|
actions: <Widget>[cancelButton, continueButton],
|
|
);
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) => alert,
|
|
);
|
|
}
|
|
|
|
void showRemoveAlertDialog(BuildContext context, int relationId) {
|
|
Widget cancelButton = TextButton(
|
|
child: LocaleKeys.cancel.tr().toText14(),
|
|
onPressed: () => Navigator.pop(context),
|
|
);
|
|
Widget continueButton = TextButton(
|
|
child: LocaleKeys.ok.tr().toText14(),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
Navigator.pushNamed(context, AppRoutes.deleteFamilyMember, arguments: relationId).then((_) => getEmployeeContacts());
|
|
},
|
|
);
|
|
AlertDialog alert = AlertDialog(
|
|
title: LocaleKeys.confirm.tr().toText16(isBold: true),
|
|
content: LocaleKeys.removeThisMember.tr().toText14(),
|
|
actions: <Widget>[cancelButton, continueButton],
|
|
);
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) => alert,
|
|
);
|
|
}
|
|
}
|