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.
mohemm-flutter-app/lib/ui/profile/family_members.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,
);
}
}