Commit 77774bea authored by Sai Srinivas's avatar Sai Srinivas
Browse files

Few Correction and fixes

parents 2a8fa440 3ac9e198
This diff is collapsed.
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../Notifiers/HomeScreenNotifier.dart';
import '../../Notifiers/hrmProvider/contact_provider.dart';
import '../../Utils/app_colors.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../Utils/custom_snackbar.dart';
class ContactListScreen extends StatefulWidget {
const ContactListScreen({super.key});
@override
State<ContactListScreen> createState() => _ContactListScreenState();
}
class _ContactListScreenState extends State<ContactListScreen> {
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
final homeProv = Provider.of<HomescreenNotifier>(context, listen: false);
Future.microtask(() {
final provider = Provider.of<ContactProvider>(context, listen: false);
provider.fetchContactList(homeProv.session, homeProv.empId, 1);
});
}
void _addToContact(String name, String phoneNumber) async {
if (phoneNumber.isEmpty) {
CustomSnackBar.showError(
context: context,
message: "No phone number available",
);
return;
}
try {
// Show loading snackbar
CustomSnackBar.showLoading(
context: context,
message: "Opening contact form for $name...",
);
// Check and request contact permission
var permissionStatus = await Permission.contacts.status;
if (permissionStatus.isDenied) {
final requestedStatus = await Permission.contacts.request();
if (!requestedStatus.isGranted) {
CustomSnackBar.hide(context);
CustomSnackBar.showError(
context: context,
message: "Contact permission is required",
);
return;
}
}
if (permissionStatus.isPermanentlyDenied) {
CustomSnackBar.hide(context);
_showPermissionDialog();
return;
}
// Clean phone number
final cleanPhoneNumber = _cleanPhoneNumber(phoneNumber);
// ✅ Instead of inserting contact, open system form
final newContact = Contact()
..name = Name(first: name)
..phones = [Phone(cleanPhoneNumber)];
// 🟢 This opens the phone’s native “Add contact” screen (prefilled)
await FlutterContacts.openExternalInsert(newContact);
// Hide loading
CustomSnackBar.hide(context);
// Optional confirmation message
CustomSnackBar.showSuccess(
context: context,
message: "Contact form opened for $name",
);
} catch (e) {
debugPrint("❌ Error opening contact form: $e");
CustomSnackBar.hide(context);
CustomSnackBar.showError(
context: context,
message: "Failed to open contact form",
);
}
}
// Helper function to clean phone numbers
String _cleanPhoneNumber(String phoneNumber) {
// Remove all non-digit characters except +
String cleaned = phoneNumber.replaceAll(RegExp(r'[^\d+]'), '');
// Remove country code if it's +91 (India) and number is 10 digits
if (cleaned.startsWith('+91') && cleaned.length == 13) {
cleaned = cleaned.substring(3);
} else if (cleaned.startsWith('91') && cleaned.length == 12) {
cleaned = cleaned.substring(2);
}
// Remove leading 0 if present
if (cleaned.startsWith('0') && cleaned.length == 11) {
cleaned = cleaned.substring(1);
}
return cleaned;
}
void _showPermissionDialog() {
showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text("Permission Required"),
content: const Text(
"Contact permission is permanently denied. Please enable it in app settings to add contacts."),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.pop(context);
openAppSettings();
},
child: const Text("Open Settings"),
),
],
),
);
}
void _openWhatsApp(String phoneNumber) async {
if (phoneNumber.isEmpty) {
CustomSnackBar.showError(
context: context,
message: "No phone number available",
);
return;
}
final whatsappUrl = "https://wa.me/$phoneNumber";
final Uri whatsappUri = Uri.parse(whatsappUrl);
if (await canLaunchUrl(whatsappUri)) {
await launchUrl(whatsappUri);
} else {
CustomSnackBar.showError(
context: context,
message: "Could not open WhatsApp",
);
}
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final bool isSmallScreen = screenWidth < 360;
final bool isTablet = screenWidth > 768;
return Scaffold(
backgroundColor: AppColors.scaffold_bg_color,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
title: Row(
children: [
InkResponse(
onTap: () => Navigator.pop(context, true),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: isSmallScreen ? 22 : isTablet ? 28 : 25,
),
),
const SizedBox(width: 10),
Text(
"Contacts",
style: TextStyle(
fontSize: isSmallScreen ? 16 : isTablet ? 20 : 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
body: Consumer<ContactProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (provider.contactList.isEmpty) {
return const Center(
child: Text(
"No contacts found",
style: TextStyle(
color: Colors.black54,
fontSize: 16,
fontFamily: "Plus Jakarta Sans"),
),
);
}
final filteredContacts = provider.contactList
.where((c) => (c.name ?? '')
.toLowerCase()
.contains(_searchController.text.toLowerCase()))
.toList();
return Padding(
padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 10 : 16),
child: Column(
children: [
const SizedBox(height: 12),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: TextField(
controller: _searchController,
onChanged: (_) => setState(() {}),
decoration: const InputDecoration(
hintText: "Search",
hintStyle: TextStyle(color: Colors.grey),
prefixIcon: Icon(Icons.search, color: Colors.grey),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 14),
),
),
),
const SizedBox(height: 12),
Expanded(
child: ListView.builder(
itemCount: filteredContacts.length,
itemBuilder: (context, index) {
final contact = filteredContacts[index];
final canSwipe = contact.mobileNumber != null &&
contact.mobileNumber!.trim().length == 10;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Slidable(
key: Key(contact.mobileNumber?.toString() ?? '${contact.name}_$index'),
// Left swipe (Add to contact)
startActionPane: canSwipe
? ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.4, // Width percentage
dragDismissible: false,
children: [
// This will automatically take full height of the list item
SlidableAction(
onPressed: (_) async {
_addToContact(
contact.name ?? 'Contact',
contact.mobileNumber ?? '',
);
},
backgroundColor: const Color(0xFFE5FFE5),
foregroundColor: const Color(0xFF38903c),
icon: Icons.contact_page,
label: 'Add to Contact',
autoClose: true,
),
],
)
: null,
// Right swipe (WhatsApp)
endActionPane: canSwipe
? ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.4, // Width percentage
dragDismissible: false,
children: [
SlidableAction(
onPressed: (_) async {
_openWhatsApp(contact.mobileNumber ?? '');
},
backgroundColor: const Color(0xFFE9FFE8),
foregroundColor: const Color(0xFF4CB443),
icon: Icons.chat,
label: 'WhatsApp',
autoClose: true,
),
],
)
: null,
// The main content - this determines the height
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(1),
),
padding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10),
child: Row(
children: [
// 👤 Avatar
Container(
height: 42,
width: 42,
decoration: const BoxDecoration(
color: Color(0xFFE6F6FF),
shape: BoxShape.circle,
),
clipBehavior: Clip.antiAlias,
child: (contact.profileImage != null &&
contact.profileImage!.isNotEmpty)
? Image.network(
contact.profileImage!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.person_outline,
color: Color(0xFF2d2d2d));
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(
child: CircularProgressIndicator(strokeWidth: 2),
);
},
)
: const Icon(Icons.person_outline, color: Color(0xFF2d2d2d)),
),
const SizedBox(width: 12),
// 📝 Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
contact.name ?? "-",
style: const TextStyle(
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black87,
),
),
const SizedBox(height: 3),
Text(
"${contact.designation ?? ''}\n${contact.branchName ?? ''}",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey.shade600,
height: 1.4,
),
),
],
),
),
// Call
if (contact.mobileNumber != null &&
contact.mobileNumber!.trim().length == 10)
IconButton(
icon: const Icon(Icons.call, color: Colors.green, size: 24),
onPressed: () async {
final phone = contact.mobileNumber!.trim();
final Uri callUri = Uri(scheme: 'tel', path: phone);
if (await canLaunchUrl(callUri)) {
await launchUrl(callUri);
} else {
debugPrint("❌ Could not launch dialer for $phone");
CustomSnackBar.showError(
context: context,
message: "Unable to open dialer",
);
}
},
),
],
),
),
),
),
);
},
),
),
],
),
);
},
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/Attendancelist.dart'; import 'package:generp/screens/hrm/Attendancelist.dart';
import 'package:generp/screens/hrm/ContactList.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../Utils/app_colors.dart'; import '../../Utils/app_colors.dart';
import 'AdvanceListScreen.dart'; import 'AdvanceListScreen.dart';
...@@ -27,7 +28,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -27,7 +28,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
"Rewards List", "Rewards List",
"Attendance Request List", "Attendance Request List",
"Advance List", "Advance List",
"Casual Leave List" "Casual Leave List",
]; ];
// Responsive text size function // Responsive text size function
...@@ -236,7 +237,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -236,7 +237,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
return Padding( return Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: getResponsivePadding(context), horizontal: getResponsivePadding(context)-3,
vertical: 10, vertical: 10,
), ),
child: Consumer<HrmAccessiblePagesProvider>( child: Consumer<HrmAccessiblePagesProvider>(
...@@ -336,8 +337,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -336,8 +337,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
horizontal: constraints.maxWidth * 0.06, horizontal: constraints.maxWidth * 0.06,
), ),
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
vertical: isSmallScreen ? 4 : 7, vertical: isSmallScreen ? 4 : 6.5,
horizontal: isSmallScreen ? 3 : 5 horizontal: isSmallScreen ? 2 : 4
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
...@@ -435,6 +436,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -435,6 +436,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
return "Advance Payment"; return "Advance Payment";
case "Casual Leave List": case "Casual Leave List":
return "Track Casual Leave"; return "Track Casual Leave";
case "Employee Contact List":
return "Staff Contact";
default: default:
return ""; return "";
} }
...@@ -459,6 +462,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -459,6 +462,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
return "assets/svg/hrm/advance_list_ic.svg"; return "assets/svg/hrm/advance_list_ic.svg";
case "Casual Leave List": case "Casual Leave List":
return "assets/svg/hrm/casual_leave_history_ic.svg"; return "assets/svg/hrm/casual_leave_history_ic.svg";
case "Employee Contact List":
return "assets/svg/hrm/emp_contact_list.svg";
default: default:
return "assets/svg/hrm/groupIc.svg"; return "assets/svg/hrm/groupIc.svg";
} }
...@@ -534,6 +539,15 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -534,6 +539,15 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
), ),
); );
break; break;
case "Employee Contact List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ContactListScreen(),
),
);
break;
} }
} }
} }
\ No newline at end of file
...@@ -66,5 +66,6 @@ export 'package:generp/Notifiers/hrmProvider/rewardListProvider.dart'; ...@@ -66,5 +66,6 @@ export 'package:generp/Notifiers/hrmProvider/rewardListProvider.dart';
export 'package:generp/Notifiers/hrmProvider/LeaveApplicationListProvider.dart'; export 'package:generp/Notifiers/hrmProvider/LeaveApplicationListProvider.dart';
export 'package:generp/Notifiers/hrmProvider/LeaveApplicationDetailsProvider.dart'; export 'package:generp/Notifiers/hrmProvider/LeaveApplicationDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/CasualLeaveHistoryProvider.dart'; export 'package:generp/Notifiers/hrmProvider/CasualLeaveHistoryProvider.dart';
export 'package:generp/Notifiers/hrmProvider/contact_provider.dart';
export 'package:generp/Notifiers/hrmprovider/orgprovider.dart'; export 'package:generp/Notifiers/hrmprovider/orgprovider.dart';
...@@ -29,6 +29,7 @@ import 'package:generp/Models/financeModels/paymentRequisitionPaymentsListRespon ...@@ -29,6 +29,7 @@ import 'package:generp/Models/financeModels/paymentRequisitionPaymentsListRespon
import 'package:generp/Models/hrmModels/advanceListResponse.dart'; import 'package:generp/Models/hrmModels/advanceListResponse.dart';
import 'package:generp/Models/hrmModels/attendanceRequestListResponse.dart'; import 'package:generp/Models/hrmModels/attendanceRequestListResponse.dart';
import 'package:generp/Models/hrmModels/casualLeaveHistoryResponse.dart'; import 'package:generp/Models/hrmModels/casualLeaveHistoryResponse.dart';
import 'package:generp/Models/hrmModels/contactListResponse.dart';
import 'package:generp/Models/hrmModels/jobDescriptionResponse.dart'; import 'package:generp/Models/hrmModels/jobDescriptionResponse.dart';
import 'package:generp/Models/hrmModels/leaveApplicationDetailsResponse.dart'; import 'package:generp/Models/hrmModels/leaveApplicationDetailsResponse.dart';
import 'package:generp/Models/hrmModels/leaveApplicationLIstResponse.dart'; import 'package:generp/Models/hrmModels/leaveApplicationLIstResponse.dart';
...@@ -3655,6 +3656,7 @@ class ApiCalling { ...@@ -3655,6 +3656,7 @@ class ApiCalling {
if (res != null) { if (res != null) {
print(data); print(data);
debugPrint("Submit New Leads ${res.body}"); debugPrint("Submit New Leads ${res.body}");
return crmNewLeadsProspectsSubmitResponse.fromJson( return crmNewLeadsProspectsSubmitResponse.fromJson(
jsonDecode(res.body), jsonDecode(res.body),
); );
...@@ -3819,7 +3821,7 @@ class ApiCalling { ...@@ -3819,7 +3821,7 @@ class ApiCalling {
final res = await post(data, crmLeadListFilterSubmitUrl, {}); final res = await post(data, crmLeadListFilterSubmitUrl, {});
if (res != null) { if (res != null) {
print("Lead Filter:$data"); print("Lead Filter:$data");
debugPrint(res.body); debugPrint(" Lead list data :${res.body}");
return SubmitLeadListFilterResponse.fromJson(jsonDecode(res.body)); return SubmitLeadListFilterResponse.fromJson(jsonDecode(res.body));
} else { } else {
debugPrint("Null Response"); debugPrint("Null Response");
...@@ -5637,6 +5639,33 @@ class ApiCalling { ...@@ -5637,6 +5639,33 @@ class ApiCalling {
} }
} }
// Contact list
static Future<ContactListResponse?> contactListAPI(
session,
empId,
pageNumber
) async {
debugPrint("🔥🔥🔥🔥🔥🔥🔥Response");
try {
Map<String, String> data = {
'session_id': (session).toString(),
'emp_id': (empId).toString(),
};
final res = await post(data, EmployeeContactListUrl, {});
if (res != null) {
print("Request Data: $data");
debugPrint("🔥🔥🔥🔥🔥🔥🔥 Response: ${res.body}");
return ContactListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('API Error: $e');
return null;
}
}
// static Future<CommonResponse?> TpcIssueListApprovalAPI( // static Future<CommonResponse?> TpcIssueListApprovalAPI(
// empId, // empId,
// session, // session,
......
...@@ -205,7 +205,7 @@ const LeaveRequestAdditionUrl ="${baseUrl_test}add_leave_request"; ...@@ -205,7 +205,7 @@ const LeaveRequestAdditionUrl ="${baseUrl_test}add_leave_request";
const LeaveRequestRejectAprroveUrl ="${baseUrl_test}leaves_approve_reject"; const LeaveRequestRejectAprroveUrl ="${baseUrl_test}leaves_approve_reject";
const CasuaLeaveHistoryUrl ="${baseUrl_test}casual_leave_history"; const CasuaLeaveHistoryUrl ="${baseUrl_test}casual_leave_history";
const EmployeeContactListUrl ="${baseUrl_test}employee_contact_list";
const AdvanceListUrl ="${baseUrl_test}advance_list"; const AdvanceListUrl ="${baseUrl_test}advance_list";
......
...@@ -574,6 +574,14 @@ packages: ...@@ -574,6 +574,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.1" version: "3.4.1"
flutter_contacts:
dependency: "direct main"
description:
name: flutter_contacts
sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae"
url: "https://pub.dev"
source: hosted
version: "1.1.9+2"
flutter_download_manager: flutter_download_manager:
dependency: "direct main" dependency: "direct main"
description: description:
......
...@@ -93,6 +93,7 @@ dependencies: ...@@ -93,6 +93,7 @@ dependencies:
file_picker: ^8.0.0 file_picker: ^8.0.0
flutter_html: ^3.0.0 flutter_html: ^3.0.0
photo_view: ^0.14.0 photo_view: ^0.14.0
flutter_contacts: ^1.1.9+2
dev_dependencies: dev_dependencies:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment