Commit 642da22c authored by Sai Srinivas's avatar Sai Srinivas Committed by Sai Srinivas
Browse files

gen erp 08-10

parent d2c9404a
......@@ -141,6 +141,7 @@ class Editproductlistprovider extends ChangeNotifier {
TextEditingController addEditProductPriceController = TextEditingController();
TextEditingController addEditQuantityController = TextEditingController();
TextEditingController addEditTotalAmountController = TextEditingController();
TextEditingController addEditRemarkController = TextEditingController();
List<Products> _productsList = [];
List<LeadProducts> _leadProductsList = [];
Products? _selectedProducts;
......@@ -150,12 +151,14 @@ class Editproductlistprovider extends ChangeNotifier {
List<TextEditingController> editProductPriceControllers = [];
List<TextEditingController> editQuantityControllers = [];
List<TextEditingController> editTotalAmountControllers = [];
List<TextEditingController> editRemarkControllers = [];
final List<String?> _selectedProductIds = [];
final List<String?> _selectedValues = [];
bool _submitLoading = false;
String? qtyError;
String? priceError;
String? productError;
String? remarkError;
bool get editProductDetailsClicked => _editProductDetailsClicked;
bool get editContactDetailsClicked => _editContactDetailsClicked;
......@@ -234,6 +237,7 @@ class Editproductlistprovider extends ChangeNotifier {
editProductPriceControllers.clear();
editQuantityControllers.clear();
editTotalAmountControllers.clear();
editRemarkControllers.clear();
_selectedProductIds.clear();
_selectedValues.clear();
......@@ -253,6 +257,9 @@ class Editproductlistprovider extends ChangeNotifier {
.toString(),
),
);
editRemarkControllers.add(
TextEditingController(text: product.remarks?.toString() ?? ''),
);
_selectedProductIds.add(product.productId);
_selectedValues.add(product.productName);
}
......@@ -281,6 +288,7 @@ class Editproductlistprovider extends ChangeNotifier {
editProductPriceControllers.removeAt(index);
editQuantityControllers.removeAt(index);
editTotalAmountControllers.removeAt(index);
editRemarkControllers.removeAt(index);
_selectedProductIds.removeAt(index);
_selectedValues.removeAt(index);
notifyListeners();
......@@ -314,6 +322,7 @@ class Editproductlistprovider extends ChangeNotifier {
"product_id": _selectedProductIds[i]!,
"price": editProductPriceControllers[i].text,
"qty": editQuantityControllers[i].text,
"remarks": editRemarkControllers[i].text,
};
insertData.add(rowData);
}
......@@ -401,6 +410,7 @@ class Editproductlistprovider extends ChangeNotifier {
type,
leadProductId,
productId,
prductRemark,
) async {
try {
if (!validateform(context)) {
......@@ -419,6 +429,7 @@ class Editproductlistprovider extends ChangeNotifier {
productId,
addEditQuantityController.text,
addEditProductPriceController.text,
addEditRemarkController.text,
);
if (data != null && data.error == "0") {
_submitLoading = false;
......@@ -477,10 +488,12 @@ class Editproductlistprovider extends ChangeNotifier {
_quotationDetailsClicked = false;
addEditProductPriceController.clear();
addEditQuantityController.clear();
addEditRemarkController.clear();
addEditTotalAmountController.clear();
editProductPriceControllers.clear();
editQuantityControllers.clear();
editTotalAmountControllers.clear();
editRemarkControllers.clear();
_selectedProductIds.clear();
_selectedProducts = null;
_selectedAddEditProductId = null;
......
......@@ -9,6 +9,7 @@ import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../../screens/crm/LeadDetailsByMode.dart';
import '../../screens/order/addOrder.dart';
import '../../services/api_calling.dart';
import 'crmLeadDetailsProvider.dart';
......@@ -156,26 +157,33 @@ class followUpUpdateProvider extends ChangeNotifier {
}
Future<void> crmAddFollowUpAPIFunction(
BuildContext context,
fromScreen,
nextAppointmentStatus,
orderStatus,
leadID,
followupType,
competitor,
leadStatus,
appointmentType,
sms, [
mode,
]) async {
BuildContext context,
fromScreen,
nextAppointmentStatus,
orderStatus,
leadID,
followupType,
competitor,
leadStatus,
appointmentType,
feedback,
inTime,
loc,
sms, [
mode,
]) async {
try {
// validate form before API call
if (!valid()) {
return;
}
_submitLoading = true;
notifyListeners();
final prov = Provider.of<HomescreenNotifier>(context, listen: false);
final prov2 = Provider.of<crmLeadDetailsProvider>(context, listen: false);
final data = await ApiCalling.crmLeadDetailsAddFollowUpAPI(
prov.session,
prov.empId,
......@@ -194,24 +202,44 @@ class followUpUpdateProvider extends ChangeNotifier {
sms,
noteController.text,
);
debugPrint("status ================ $orderStatus");
if (data != null && data.error == "0") {
_submitLoading = false;
resetForm();
if (fromScreen == "Pending Tasks") {
if (orderStatus == "Order Gain") {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddorderScreen(
key: UniqueKey(),
mode: mode,
pageTitleName: "Add Order",
leadId: leadID,
feedback: feedback,
followupType: followupType,
inTime: inTime,
loc: loc,
),
),
);
} else if (fromScreen == "Pending Tasks") {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder:
(context) => LeadDetailsByMode(
mode: "executive",
pageTitleName: "Lead Details",
leadId: leadID ?? "-",
),
settings: RouteSettings(name: "LeadDetailsByMode"),
builder: (_) => LeadDetailsByMode(
mode: "executive",
pageTitleName: "Lead Details",
leadId: leadID ?? "-",
),
settings: const RouteSettings(name: "LeadDetailsByMode"),
),
(Route<dynamic> route) {
return route.settings.name == 'CrmdashboardScreen';
},
(Route<dynamic> route) =>
route.settings.name == 'CrmdashboardScreen',
);
} else {
Navigator.pop(context);
......@@ -229,10 +257,11 @@ class followUpUpdateProvider extends ChangeNotifier {
} catch (e, s) {
_submitLoading = false;
notifyListeners();
print("Error: $e, Stack: $s");
debugPrint("Error: $e, Stack: $s");
}
}
bool valid() {
followupError = null;
followupFeedbackError = null;
......
......@@ -390,45 +390,85 @@ class Requesitionlidtdetailsprovider extends ChangeNotifier {
}
Future<void> paymentrequisitionApproveSubmitAPIFunction(
context,
mode,
paymentRequestId,
approvedAmount,
approveRemarks,
proposedPaymentAccountId,
) async {
BuildContext context,
String mode,
String payment_request_id,
String approved_amount,
String approve_remarks,
String proposed_payment_account_id,
) async {
print("🎯 === PROVIDER METHOD STARTED ===");
try {
if (!validateApproval(
approvedAmount,
approveRemarks,
proposedPaymentAccountId,
)) {
print("🔍 Starting validation...");
if (!validateApproval(approved_amount, approve_remarks, proposed_payment_account_id)) {
print("❌ VALIDATION FAILED - Stopping execution");
return;
}
print("✅ Validation passed");
var provider = Provider.of<HomescreenNotifier>(context, listen: false);
print("👤 Getting home provider...");
var homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
print("👤 Emp ID: ${homeProvider.empId}, Session: ${homeProvider.session}");
print("🌐 Calling API...");
final data = await ApiCalling.ApprovePaymentRequestSubmitAPI(
provider.empId,
provider.session,
homeProvider.empId,
homeProvider.session,
mode,
paymentRequestId,
approvedAmount,
approveRemarks,
proposedPaymentAccountId,
payment_request_id,
approved_amount,
approve_remarks,
proposed_payment_account_id,
);
print("🌐 API call completed");
if (data != null) {
print("📡 API Response: error=${data.error}, message=${data.message}");
if (data.error == "0") {
paymentRequesitionDetails(context, paymentRequestId);
resetAll();
toast(context, data.message);
Navigator.pop(context, true);
print("✅ API SUCCESS - Processing...");
// Check if context is still valid before UI operations
if (context.mounted) {
print("🎯 Context mounted - performing UI operations");
paymentRequesitionDetails(context, payment_request_id);
resetAll();
toast(context, data.message);
Navigator.pop(context, true);
print("✅ UI operations completed");
} else {
print("⚠️ Context not mounted - skipping UI operations");
}
notifyListeners();
print("✅ Notify listeners called");
} else {
print("❌ API returned error: ${data.message}");
if (context.mounted) {
toast(context, data.message ?? "Submission failed");
}
}
} else {
print("❌ NULL response from API");
if (context.mounted) {
toast(context, "No response from server");
}
}
} catch (e) {}
} catch (e, s) {
print("💥 EXCEPTION in provider method: $e");
print("📋 Stack trace: $s");
// Show error to user
if (context.mounted) {
toast(context, "Error: ${e.toString()}");
}
}
print("🎯 === PROVIDER METHOD COMPLETED ===");
}
editPrevalues() {
editPaymentRequestedAmountController.text =
_requestDetails.requestedAmount ?? "-";
......@@ -530,35 +570,72 @@ class Requesitionlidtdetailsprovider extends ChangeNotifier {
}
bool validateApproval(
approvedAmount,
approveRemarks,
proposedPaymentAccountId,
) {
String approvedAmount, // Add type for clarity
String approveRemarks, // Add type for clarity
String proposedPaymentAccountId, // Add type for clarity
) {
print("🔍 === VALIDATION STARTED ===");
print(" Approved Amount: '$approvedAmount'");
print(" Requested Amount: '${requestedAmount.text}'");
print(" Remarks: '$approveRemarks'");
print(" Payment Account ID: '$proposedPaymentAccountId'");
print(" Selected ID: '$_selectedID'");
remarksError = null;
ApprovedAmountError = null;
selectpaymentAccountError = null;
bool isValid = true;
if (approvedAmount.toString().trim().isEmpty) {
// Fix 1: Use the String parameter directly (no .text)
if (approvedAmount.trim().isEmpty) {
ApprovedAmountError = "Enter Amount";
isValid = false;
print("❌ Approved amount is empty");
}
if (approveRemarks.toString().trim().isEmpty) {
// Fix 2: Use the String parameter directly (no .text)
if (approveRemarks.trim().isEmpty) {
remarksError = "Please Enter Remarks";
isValid = false;
print("❌ Remarks are empty");
}
final numberFormat = NumberFormat.decimalPattern();
if (numberFormat.parse(approvedAmount.text) >
numberFormat.parse(requestedAmount.text)) {
ApprovedAmountError =
"Approved Amount should not be greater than requested amount";
isValid = false;
// Fix 3: Parse the String parameters, not .text
if (approvedAmount.trim().isNotEmpty && requestedAmount.text.trim().isNotEmpty) {
try {
final numberFormat = NumberFormat.decimalPattern();
double approved = numberFormat.parse(approvedAmount.trim()).toDouble();
double requested = numberFormat.parse(requestedAmount.text.trim()).toDouble();
print("💰 Amount Comparison: Approved: $approved, Requested: $requested");
if (approved > requested) {
ApprovedAmountError = "Approved Amount should not be greater than requested amount";
isValid = false;
print("❌ Approved amount exceeds requested amount");
}
} catch (e) {
print("💥 Error parsing amounts: $e");
ApprovedAmountError = "Invalid amount format";
isValid = false;
}
}
if (_selectedPaymentAccounts == null || _selectedID.isEmpty) {
// Fix 4: Use the parameter OR check both for consistency
if (proposedPaymentAccountId.isEmpty && _selectedID.isEmpty) {
selectpaymentAccountError = "Please select an account";
isValid = false;
print("❌ No payment account selected");
} else if (proposedPaymentAccountId.isNotEmpty && _selectedID.isEmpty) {
// If parameter has value but _selectedID doesn't, sync them
_selectedID = proposedPaymentAccountId;
print("🔄 Synced selected ID from parameter: $_selectedID");
}
_submitClicked = false;
notifyListeners();
print("🔍 === VALIDATION RESULT: $isValid ===");
return isValid;
}
......
import 'package:flutter/material.dart';
import '../../Models/hrmModels/advanceListResponse.dart';
import '../../services/api_calling.dart';
class AdvanceListProvider extends ChangeNotifier {
bool _isLoading = false;
bool get isLoading => _isLoading;
String? _errorMessage;
String? get errorMessage => _errorMessage;
List<AdvanceList> _advanceList = [];
List<AdvanceList> get advanceList => _advanceList;
int _currentPage = 1;
int get currentPage => _currentPage;
bool _hasMore = true;
bool get hasMore => _hasMore;
/// Fetch Advance List
Future<void> fetchAdvanceList(
BuildContext context, String session, String empId,
{int page = 1, bool loadMore = false}) async {
if (_isLoading) return;
_isLoading = true;
if (!loadMore) {
_errorMessage = null;
_advanceList = [];
_currentPage = 1;
_hasMore = true;
}
notifyListeners();
try {
final response = await ApiCalling.advanceListAPI(session, empId, page);
debugPrint('lenght: ${response?.advanceList?.length}, empId: ${empId}, session: ${session}, pageNumber: $page');
if (response != null) {
if (response.error == "0") {
final newList = response.advanceList ?? [];
if (loadMore) {
_advanceList.addAll(newList);
} else {
_advanceList = newList;
}
_currentPage = page;
_hasMore = newList.isNotEmpty;
} else {
_errorMessage = response.message ?? "Something went wrong";
}
} else {
_errorMessage = "No response from server";
}
} catch (e) {
_errorMessage = "Failed to fetch advance list: $e";
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Refresh Advance List
Future<void> refreshAdvanceList(
BuildContext context, String session, String empId) async {
await fetchAdvanceList(context, session, empId, page: 1, loadMore: false);
}
/// Load More (Pagination)
Future<void> loadMoreAdvanceList(
BuildContext context, String session, String empId) async {
if (!_hasMore || _isLoading) return;
await fetchAdvanceList(context, session, empId,
page: _currentPage + 1, loadMore: true);
}
/// Clear state
void clear() {
_advanceList = [];
_errorMessage = null;
_currentPage = 1;
_hasMore = true;
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../Models/hrmModels/casualLeaveHistoryResponse.dart';
import '../../services/api_calling.dart';
import '../HomeScreenNotifier.dart';
class CasualLeaveHistoryProvider extends ChangeNotifier {
/// --- Variables ---
List<CasualLeaveHistory> casualLeaveHistoryList = [];
bool isLoading = false;
String? errorMessage;
/// --- Fetch Casual Leave History ---
Future<void> fetchCasualLeaveHistory(BuildContext context) async {
isLoading = true;
errorMessage = null;
notifyListeners();
try {
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
final response = await ApiCalling.casualLeaveHistoryAPI(
homeProvider.session,
homeProvider.empId,
);
if (response != null) {
if (response.error != null && response.error != "0") {
errorMessage = response.message ?? "Something went wrong";
casualLeaveHistoryList = [];
} else {
casualLeaveHistoryList = response.casualLeaveHistory ?? [];
}
} else {
errorMessage = "No response from server";
}
} catch (e) {
errorMessage = "Failed to load leave history: $e";
} finally {
isLoading = false;
notifyListeners();
}
}
/// --- Clear data (optional) ---
void clearHistory() {
casualLeaveHistoryList.clear();
errorMessage = null;
notifyListeners();
}
}
......@@ -89,7 +89,7 @@ class Addorderprovider extends ChangeNotifier {
List<String> _tpcApplicable = [];
List<TpcList> _tpcAgent = [];
AccountDetails _accountDetails = AccountDetails();
String? selectAccountError;
String? orderDateError;
......@@ -309,7 +309,6 @@ class Addorderprovider extends ChangeNotifier {
String? get selectedTpcAgentValue => _selectedTpcAgentValue;
AccountDetails get accountDetails => _accountDetails;
set accountList(List<AccountList> value) {
_accountList = value;
......@@ -529,10 +528,7 @@ class Addorderprovider extends ChangeNotifier {
notifyListeners();
}
set accountDetails(AccountDetails value) {
_accountDetails = value;
notifyListeners();
}
set imagePath(File? value) {
_imageName = value;
......@@ -812,13 +808,18 @@ class Addorderprovider extends ChangeNotifier {
// };
// }
Future<void> ordersAddOrderAPIViewFunction(context, mode) async {
Future<void> ordersAddOrderAPIViewFunction(context, mode, leadId, feedback, followupType, inTime, loc) async {
try {
final provider = Provider.of<HomescreenNotifier>(context, listen: false);
final data = await ApiCalling.addOrderViewAPI(
provider.empId,
provider.session,
mode,
provider.empId,
provider.session,
mode,
leadId,
feedback,
followupType,
inTime,
loc
);
if (data != null) {
if (data.error == "0") {
......@@ -839,6 +840,116 @@ class Addorderprovider extends ChangeNotifier {
} catch (e) {}
}
// AccountDetails _accountDetails = AccountDetails();
//
// AccountDetails get accountDetails => _accountDetails;
AccountDetails? _accountDetails; // nullable
AccountDetails? get accountDetails => _accountDetails;
set accountDetails(AccountDetails? value) {
_accountDetails = value;
notifyListeners();
}
Future<void> fetchAddOrderViewData(
BuildContext context,
String empId,
String session,
String mode,
leadId,
feedback,
followupType,
inTime,
loc
) async {
debugPrint("🔥 fetchAddOrderViewData CALLED with leadId=$leadId, mode=$mode");
try {
_isLoading = true;
notifyListeners();
final response = await ApiCalling.addOrderViewAPI(
empId, session, mode, leadId, feedback, followupType, inTime, loc
);
if (response != null) {
if (response.error == "0") {
_tpcApplicable = ["Yes", "No"];
_erectionScope = response.erectionScope ?? [];
_unloadingScope = response.unloadingScope ?? [];
_freightScope = response.freightScope ?? [];
_employees = response.employees ?? [];
_billingStates = response.states ?? [];
_dispatchStates = response.states ?? [];
_saleProducts = response.saleProducts ?? [];
// getSubLocationAPI(context, response.accountDetails?.subLocality);
// getDistrictAPI(context, response.accountDetails?.district);
// _billingStates = response.accountDetails?.state as List<States>;
checkDropdownselected();
notifyListeners();
} else {}
} else {}
debugPrint(" data to all fields##################################");
if (response != null) {
debugPrint("Employees: ${response.accountDetails?.pincode}");
debugPrint("States: ${response.accountDetails?.state}");
debugPrint("Sale Products: ${response.accountDetails?.address}");
debugPrint("Account Details Name: ${response.accountDetails?.name}");
debugPrint("District: ${response.accountDetails?.district}");
debugPrint("Sub location: ${response.accountDetails?.name}");
} else {
debugPrint("Response is NULL");
}
if (response != null && response.error == "0") {
// Save accountDetails safely
_accountDetails = AccountDetails(
id: response.accountDetails?.id,
name: response.accountDetails?.name,
address: response.accountDetails?.address,
state: response.accountDetails?.state,
pincode: response.accountDetails?.pincode,
date: response.accountDetails?.date,
subLocality: response.accountDetails?.subLocality,
district: response.accountDetails?.district,
);
// Save lists safely
_unloadingScope = response.unloadingScope ?? [];
_freightScope = response.freightScope ?? [];
_erectionScope = response.erectionScope ?? [];
// default selections
if (_employees.isNotEmpty) selectedEmployees = _employees.first;
if (leadId != "")
if (_billingStates.isNotEmpty) selecetdBillingStates = _billingStates.first;
if (_billingDistricts.isNotEmpty) selectedBillingDistricts = _billingDistricts.first;
if (_billingStates.isNotEmpty) selecetdBillingStates = _billingStates.first;
if (_dispatchStates.isNotEmpty) selecetdDispatchStates = _dispatchStates.first;
if (_saleProducts.isNotEmpty) selectedSaleProducts = _saleProducts.first;
if (_unloadingScope.isNotEmpty) selectedUnloadingScope = _unloadingScope.first;
if (_freightScope.isNotEmpty) selectedFreightScope = _freightScope.first;
if (_erectionScope.isNotEmpty) selectedErectionScope = _erectionScope.first;
notifyListeners(); // notify listeners/UI
} else {
debugPrint("API Error: ${response?.message}");
}
} catch (e) {
debugPrint("Provider Error in fetchAddOrderViewData: $e");
} finally {
_isLoading = false;
notifyListeners();
}
}
void checkDropdownselected() {
if (_selectedAccountList != null &&
!_accountList.contains(_selectedAccountList)) {
......@@ -972,12 +1083,12 @@ class Addorderprovider extends ChangeNotifier {
if (data.error == "0") {
_billingDistricts = data.districts!;
_selectedBillingDistricts = data.districts!.firstWhere(
(e) => e.id == accountDetails.district!,
(e) => e.id == accountDetails?.district!,
);
_selectedBillingDistrictValue =
data.districts!
.firstWhere((e) => e.id == accountDetails.district!)
.firstWhere((e) => e.id == accountDetails?.district!)
.district;
notifyListeners();
......@@ -1020,12 +1131,12 @@ class Addorderprovider extends ChangeNotifier {
if (data.error == "0") {
_billingSubLocations = data.subLocations!;
_selectedBillingSubLocations = data.subLocations!.firstWhere(
(e) => e.id == accountDetails.subLocality!,
(e) => e.id == accountDetails?.subLocality!,
);
_selectedBillingSubLocValue =
data.subLocations!
.firstWhere((e) => e.id == accountDetails.subLocality!)
.firstWhere((e) => e.id == accountDetails?.subLocality!)
.subLocality;
notifyListeners();
......
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:generp/Models/VersionsResponse.dart';
import 'package:generp/Notifiers/HomeScreenNotifier.dart';
import 'package:generp/Notifiers/loginNotifier.dart';
import 'package:generp/Utils/SharedpreferencesService.dart';
import 'package:generp/screens/HomeScreen.dart';
......@@ -55,7 +54,7 @@ class SplashVersionNotifier extends ChangeNotifier {
if (data != null) {
if (kDebugMode) {
print("Current Build: $currentBuild");
print("Server Response: $data");
print("Server Response: ${data.latestVersionCode}");
}
if (Platform.isAndroid &&
currentBuild < (data.latestVersionCode ?? 0)) {
......
import 'dart:convert';
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
......@@ -10,56 +11,139 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:generp/Models/hrmModels/leaveApplicationLIstResponse.dart';
import 'package:generp/Utils/app_colors.dart';
import 'package:generp/screens/AttendanceScreen.dart';
import 'package:generp/screens/HomeScreen.dart';
import 'package:generp/screens/crm/LeadDetailsByMode.dart';
import 'package:generp/screens/crm/ProspectListByMode.dart';
import 'package:generp/screens/crm/crmDashboard.dart';
import 'package:generp/screens/hrm/Attendancelist.dart';
import 'package:generp/screens/hrm/OrganizationStructureScreen.dart';
import 'package:generp/screens/hrm/TourExpensesListScreen.dart';
import 'Notifiers/hrmProvider/advanceProvider.dart';
import 'screens/notifierExports.dart';
import 'package:generp/Utils/SharedpreferencesService.dart';
import 'package:generp/screens/splash.dart';
import 'package:provider/provider.dart';
import 'Utils/commonWidgets.dart';
// Navigator Key for global navigation
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Notification Channel
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'generp_channel', // id
'generp_channel',
'generp_channel_name',
importance: Importance.max,
playSound: false,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
FlutterLocalNotificationsPlugin();
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
String type = message.data['type'] ?? '';
debugPrint("📩 Background notification received: ${message.data}");
}
if (type == 'offline_reminder') {
FlutterRingtonePlayer().play(
// fromAsset: "assets/offline_reminder.mp3",
ios: IosSounds.glass, // Specify the iOS sound
);
} else if (type == 'normal') {
FlutterRingtonePlayer().play(
// fromAsset: "assets/notification_sound.mp3",
ios: IosSounds.glass, // Specify the iOS sound
);
} else if (type == 'web_erp_notification') {
FlutterRingtonePlayer().play(
// fromAsset: "assets/notification_sound.mp3",
ios: IosSounds.glass, // Specify the iOS sound
);
} else {
FlutterRingtonePlayer().play(
// fromAsset: "assets/notification_sound.mp3",
// will be the sound on Android
ios: IosSounds.glass, // will be the sound on iOS
);
}
if (kDebugMode) {
print('A Background message just showed up: ${message.messageId}');
void handleNotificationTap(Map<String, dynamic> data) {
debugPrint("👉 Handling notification tap: $data");
try {
String type = data['type'] ?? '';
String? rollId = data['RoleID'] ?? data['rollId'];
String? notificationId = data['notification_id'] ?? data['notificationId'];
String? url = data['url'];
String? mode = data['Parameter'];
debugPrint("👉 Handling notification tap: $mode");
if (type.toLowerCase() == "app") {
// 🔥 Navigation based on RoleID
switch (rollId) {
case "1":
navigatorKey.currentState?.pushNamed('/crm_lead_list');
break;
case "4":
navigatorKey.currentState?.pushNamed('/crm_prospect_list');
break;
case "5":
navigatorKey.currentState?.pushNamed(
'/crm_prospect_details',
arguments: {"id": notificationId},
);
break;
case "6":
navigatorKey.currentState?.pushNamed(
'/crm_lead_details',
arguments: {"id": notificationId},
);
break;
case "7":
navigatorKey.currentState?.pushNamed('/dashboard');
break;
case "8":
navigatorKey.currentState?.pushNamed('/crm_new_customer_new_lead_register');
break;
case "10":
navigatorKey.currentState?.pushNamed('/crm_prospect_list_team');
break;
case "11":
navigatorKey.currentState?.pushNamed('/crm_prospect_list_admin');
break;
case "15":
navigatorKey.currentState?.pushNamed('/finance_list_employee');
break;
case "16":
navigatorKey.currentState?.pushNamed('/finance_list_admin');
break;
case "17":
navigatorKey.currentState?.pushNamed('/dispatch_list_executive');
break;
case "312":
navigatorKey.currentState?.pushNamed('/tour_bill_list');
break;
case "601":
navigatorKey.currentState?.pushNamed('/manual_attendance_request_list');
break;
default:
navigatorKey.currentState?.pushNamed('/home'); // fallback
}
} else if (type.toLowerCase() == "web" && url != null && url.isNotEmpty) {
// Open in WebView or browser
navigatorKey.currentState?.pushNamed(
'/webview',
arguments: {"url": url},
);
} else {
// fallback
navigatorKey.currentState?.pushNamed('/home');
}
} catch (e) {
debugPrint("❌ Error handling notification tap: $e");
}
}
// FlutterLocalNotificationsPlugin cannot auto-detect foreground taps
void showInAppNotification(BuildContext context, Map<String, dynamic> data) {
final snackBar = SnackBar(
content: Text(data['type'] ?? "New Notification"),
action: SnackBarAction(
label: 'Open',
onPressed: () {
debugPrint("👉 Foreground notification tapped: $data");
handleNotificationTap(data);
},
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Firebase init
if (Platform.isAndroid) {
await Firebase.initializeApp(
options: FirebaseOptions(
......@@ -72,75 +156,176 @@ void main() async {
} else if (Platform.isIOS) {
await Firebase.initializeApp();
}
if (kDebugMode) {
if (Firebase.apps.isNotEmpty) {
print("Firebase is initialized");
} else {
print("Firebase is not initialized");
// 🔔 Local Notification Init
const AndroidInitializationSettings initSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initSettingsIOS = DarwinInitializationSettings();
const InitializationSettings initializationSettings = InitializationSettings(
android: initSettingsAndroid,
iOS: initSettingsIOS,
);
flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) async {
if (notificationResponse.payload != null) {
handleNotificationTap(jsonDecode(notificationResponse.payload!));
debugPrint("📩 Notification clicked: $notificationResponse");
}
}
}
);
// Firebase Messaging
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging messaging = FirebaseMessaging.instance;
await messaging.requestPermission(alert: true, badge: true, sound: false);
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: false,
);
// Foreground notification
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification;
AndroidNotification? android = message.notification?.android;
print("msg");
String type = message.data['type'] ?? '';
if (type == 'offline_reminder') {
FlutterRingtonePlayer().play(
fromAsset: "assets/offline_reminder.mp3",
ios: IosSounds.glass, // Specify the iOS sound
);
} else if (type == 'normal') {
FlutterRingtonePlayer().play(
fromAsset: "assets/notification_sound.mp3",
ios: IosSounds.glass, // Specify the iOS sound
);
} else if (type == 'web_erp_notification') {
FlutterRingtonePlayer().play(
fromAsset: "assets/notification_sound.mp3",
ios: IosSounds.glass, // Specify the iOS sound
);
} else {
FlutterRingtonePlayer().play(
fromAsset:
"assets/notification_sound.mp3", // will be the sound on Android
ios: IosSounds.glass, // will be the sound on iOS
final notification = message.notification;
final android = message.notification?.android;
debugPrint("😊 Foreground msg received: ${message.data}");
if (notification != null && android != null) {
// flutterLocalNotificationsPlugin.show(
// notification.hashCode,
// notification.title,
// notification.body,
// NotificationDetails(
// android: AndroidNotificationDetails(
// channel.id,
// channel.name,
// importance: Importance.max,
// priority: Priority.high,
// icon: '@mipmap/ic_launcher',
// ),
// ),
// payload: jsonEncode({
// "type": message.data['type'],
// "rollId": message.data['RoleID'],
// "notificationId": message.data['notification_id'],
// }),
// );
}
// ⚡ Foreground tap alternative:
// Show an in-app banner or SnackBar with a tap action
final context = navigatorKey.currentContext;
if (context != null) {
// Add haptic feedback
HapticFeedback.mediumImpact();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
SizedBox(
child: Image.asset(
"assets/images/ic_splash.jpg",
height: 30,
width: 30,
),
),
SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
notification?.title ?? "New Notification",
style: TextStyle(
fontSize: 14,
color: AppColors.app_blue,
fontFamily: "JakartaMedium",
),
),
if (notification?.body != null) SizedBox(height: 2),
if (notification?.body != null)
Text(
notification!.body!,
style: TextStyle(
fontSize: 12,
color: AppColors.semi_black,
fontFamily: "JakartaMedium",
),
),
],
),
),
],
),
action: SnackBarAction(
label: "Open",
textColor: AppColors.app_blue,
onPressed: () {
// Add haptic feedback for button press
HapticFeedback.lightImpact();
debugPrint("👉 Foreground notification tapped: ${message.data}");
// Use the same payload structure as local notifications
handleNotificationTap({
"type": message.data['type'],
"rollId": message.data['RoleID'],
"notificationId": message.data['notification_id'],
});
},
),
backgroundColor: AppColors.white,
behavior: SnackBarBehavior.floating,
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
margin: const EdgeInsets.all(16),
duration: const Duration(seconds: 4),
),
);
}
});
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>()
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
sound: false,
);
await FirebaseMessaging.instance.getToken().then((value) {
String? token = value;
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
String type = message.data['type'] ?? '';
String redirectUrl = message.data['redirect_url'] ?? '';
handleNotificationTap(message.data);
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => Dashboard()),
// );
if (kDebugMode) {
// print("fbstoken:{$token}");
print('A new onMessageOpenedApp event was published!');
}
});
SharedpreferencesService().saveString("fbstoken", token!);
// Save FCM Token
await FirebaseMessaging.instance.getToken().then((value) {
if (value != null) {
SharedpreferencesService().saveString("fbstoken", value);
debugPrint("🔑 FCM Token: $value");
}
});
runApp(const MyApp());
......@@ -153,19 +338,19 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// SystemChrome.setApplicationSwitcherDescription(ApplicationSwitcherDescription());
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
String type = message.data['type'] ?? '';
String redirectUrl = message.data['redirect_url'] ?? '';
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => Dashboard()),
// );
if (kDebugMode) {
print('A new onMessageOpenedApp event was published!');
}
});
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
// FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// String type = message.data['type'] ?? '';
// String redirectUrl = message.data['redirect_url'] ?? '';
//
// // Navigator.push(
// // context,
// // MaterialPageRoute(builder: (context) => Dashboard()),
// // );
// if (kDebugMode) {
// print('A new onMessageOpenedApp event was published!');
// }
// });
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => SplashVersionNotifier()),
......@@ -226,9 +411,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => Dispatchorderprovider()),
ChangeNotifierProvider(create: (_) => followUpUpdateProvider()),
ChangeNotifierProvider(create: (_) => Appointmentcalendarprovider()),
ChangeNotifierProvider(
create: (_) => Addnewleadsandprospectsprovider(),
),
ChangeNotifierProvider(create: (_) => Addnewleadsandprospectsprovider()),
ChangeNotifierProvider(create: (_) => HrmAccessiblePagesProvider()),
ChangeNotifierProvider(create: (_) => Attendancelistprovider()),
ChangeNotifierProvider(create: (_) => AttendanceDetailsProvider()),
......@@ -237,12 +420,36 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => RewardListProvider()),
ChangeNotifierProvider(create: (_) => LeaveApplicationListProvider()),
ChangeNotifierProvider(create: (_) => Orgprovider()),
ChangeNotifierProvider(create: (_) => AdvanceListProvider()),
ChangeNotifierProvider(create: (_) => CasualLeaveHistoryProvider()),
],
child: Builder(
builder: (BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
routes: {
'/home': (context) => const MyHomePage(),
// '/chat': (context) => AttendanceScreen(),
// '/product_details': (context) => AttendanceScreen(),
// '/order_details': (context) => AttendanceScreen(),
'/crm_lead_list': (context) => LeadDetailsByMode(mode: "", pageTitleName: "Lead Details", leadId: ""),
// '/crm_prospect_list': (context) => ProspectListByMode(),
// '/crm_prospect_details': (context) => AttendanceScreen(),
// '/crm_lead_details': (context) => AttendanceScreen(),
'/dashboard': (context) => CrmdashboardScreen(),
// '/crm_new_customer_new_lead_register': (context) => AttendanceScreen(),
// '/crm_prospect_list_team': (context) => AttendanceScreen(),
// '/crm_prospect_list_admin': (context) => AttendanceScreen(),
// '/finance_list_employee': (context) => AttendanceScreen(),
// '/finance_list_admin': (context) => AttendanceScreen(),
// '/dispatch_list_executive': (context) => AttendanceScreen(),
'/tour_bill_list': (context) => TourExpensesListScreen(),
'/manual_attendance_request_list': (context) => AttendanceListScreen(mode: "",),
},
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse},
dragDevices: {PointerDeviceKind.touch,PointerDeviceKind.mouse},
),
navigatorObservers: [MyNavigatorObserver()],
......@@ -261,6 +468,7 @@ class MyApp extends StatelessWidget {
highlightColor: Colors.transparent,
hoverColor: Colors.transparent,
scaffoldBackgroundColor: Colors.white,
dialogBackgroundColor: Colors.white,
cardColor: Colors.white,
shadowColor: Colors.white54,
searchBarTheme: const SearchBarThemeData(),
......@@ -304,8 +512,8 @@ class MyApp extends StatelessWidget {
dragHandleSize: Size(60.0, 6.0),
),
colorScheme: const ColorScheme.light(
surface: Colors.white,
).copyWith(surface: Colors.white),
background: Colors.white,
).copyWith(background: Colors.white),
scrollbarTheme: ScrollbarThemeData(
minThumbLength: 20,
interactive: true,
......@@ -316,8 +524,10 @@ class MyApp extends StatelessWidget {
),
),
checkboxTheme: CheckboxThemeData(
side: BorderSide(width: 0.5),
checkColor: WidgetStatePropertyAll(AppColors.white),
),
useMaterial3: true,
// inputDecorationTheme: InputDecorationTheme(
......
This diff is collapsed.
......@@ -797,7 +797,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
if (productsNotEmpty) ...[
SizedBox(
width: double.infinity,
height: 130,
height: 150,
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
......@@ -872,7 +872,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
);
},
child: Container(
height: 130,
height: 140,
width:
MediaQuery.of(context).size.width *
0.9,
......@@ -939,15 +939,16 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
),
),
Expanded(
flex: 2,
flex: 3,
child: Text(
maxLines: 1,
textAlign:
TextAlign.right,
"₹${provider.leadProducts[lp].price ?? "-"}",
style: TextStyle(
fontFamily:
"JakartaMedium",
fontSize: 14,
fontSize: 13,
color:
AppColors
.semi_black,
......@@ -966,7 +967,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
AppColors.grey_semi,
),
),
SizedBox(height: 5),
SizedBox(height: 3),
DottedLine(
dashGapLength: 4,
dashGapColor:
......@@ -988,6 +989,24 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
.semi_black,
),
),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
provider.leadProducts[lp].remarks ?? "",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.semi_black,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
)
],
),
),
......@@ -1211,7 +1230,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
),
),
Expanded(
flex: 2,
flex: 3,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
......@@ -1894,7 +1913,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
.astatus ==
"No") ...[
Expanded(
flex: 2,
flex: 3,
child: Container(
height: 45,
padding:
......@@ -1937,9 +1956,10 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
child: Row(
children: [
Expanded(
flex: 3,
flex: 4,
child: Text(
"Appointment",
maxLines: 1,
style: TextStyle(
fontSize: 14,
fontFamily:
......@@ -2195,7 +2215,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
if (quotationNotEmpty) ...[
SizedBox(
width: double.infinity,
height: 200,
height: 244,
child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
......@@ -2207,7 +2227,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
itemCount: provider.quotationsDetails.length,
itemBuilder: (context, lp) {
return Container(
height: 200,
height: 242,
width:
MediaQuery.of(context).size.width *
0.9,
......@@ -3458,6 +3478,34 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
),
],
),
const SizedBox(height: 10),
TextWidget(context, "Remarks"),
Container(
margin: EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextFormField(
controller: editProvider.addEditRemarkController,
maxLines: 3,
enabled: true,
style: TextStyle(
color: Colors.black,
fontSize: 14,
),
decoration: InputDecoration(
hintText: "Enter remark",
hintStyle: TextStyle(
color: Colors.grey.shade500,
fontSize: 14,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 12),
),
),
),
// IconButton(
// icon: const Icon(Icons.delete),
// onPressed: editProvider.editProductPriceControllers.length > 1
......@@ -3476,8 +3524,8 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
provider.leadDetails.id!,
type,
leadProductId,
editProvider
.selectedAddEditProductId,
editProvider.selectedAddEditProductId,
editProvider.addEditRemarkController
);
},
child: Container(
......
......@@ -267,24 +267,22 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
),
),
SizedBox(width: 10),
Expanded(
flex: 1,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
launch(
'tel://${crmLists[index].mob1}',
);
},
child: SizedBox(
height: 35,
width: 35,
child: SvgPicture.asset(
"assets/svg/crm/lead_list_call_ic.svg",
),
),
),
),
Expanded(
flex: 1,
child: InkResponse(
onTap: () {
HapticFeedback.selectionClick();
_showContactOptions(context, crmLists[index].mob1);
},
child: SizedBox(
height: 35,
width: 35,
child: SvgPicture.asset(
"assets/svg/crm/lead_list_call_ic.svg",
),
),
),
),
],
),
],
......@@ -930,6 +928,94 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
},
);
}
void _showContactOptions(BuildContext context, String? phoneNumber) {
if (phoneNumber == null || phoneNumber.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("No phone number available")),
);
return;
}
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
backgroundColor: Colors.white,
builder: (BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.shade400,
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 16),
Text(
"Contact Options",
style: TextStyle(
fontSize: 18,
color: AppColors.app_blue,
fontFamily: "JakartaMedium",
),
),
const SizedBox(height: 20),
// --- Call Option ---
ListTile(
leading: const Icon(Icons.phone, color: Colors.green),
title: const Text("Call", style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
)
),
onTap: () async {
Navigator.pop(context);
final uri = Uri.parse("tel:$phoneNumber");
await launchUrl(uri, mode: LaunchMode.externalApplication);
},
),
// --- WhatsApp Option ---
ListTile(
leading: SvgPicture.asset(
"assets/svg/whatsapp_ic.svg",
width: 23,
height: 23,
),
title: const Text("WhatsApp", style: TextStyle(
fontSize: 15,
fontFamily: "JakartaMedium",
)
),
onTap: () async {
Navigator.pop(context);
final message = Uri.encodeComponent("Hello, I’d like to connect with you.");
final whatsappUri = Uri.parse("https://wa.me/$phoneNumber?text=$message");
if (await canLaunchUrl(whatsappUri)) {
await launchUrl(whatsappUri, mode: LaunchMode.externalApplication);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("WhatsApp not installed or invalid number")),
);
}
},
),
],
),
);
},
);
}
bool _isFilterSelected(Leadlistprovider provider, int index) {
switch (index) {
......
......@@ -3375,43 +3375,43 @@ class ProspectDetailsByModeState extends State<ProspectDetailsByMode> {
children: [
Expanded(
child: DropdownButton2<String>(
hint: Text(
"Select Status",
style: TextStyle(fontSize: 14),
isExpanded: true,
hint: const Row(
children: [
Expanded(
child: Text(
'Select Lead Status',
style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
),
],
),
items:
addleadProvider.statusList
.map(
(slist) =>
DropdownMenuItem<String>(
value: slist,
child: Text(
slist,
style: TextStyle(
fontSize: 14,
),
),
),
)
.toList(),
<String>['All', 'Cold', 'Hot', 'Warm']
.map(
(value) => DropdownMenuItem<String>(
value: value,
child: Text(
value ?? '',
style: const TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
),
),
)
.toList(),
value: addleadProvider.selectedStatus,
onChanged: (String? value) {
if (value != null) {
if (addleadProvider
.statusList
.isNotEmpty) {
addleadProvider.selectedStatus =
value;
}
}
onChanged: (String? newValue) {
setState(() {
addleadProvider.selectedStatus = newValue!;
});
},
isExpanded: true,
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData:
ddtheme.menuItemStyleData,
dropdownStyleData:
ddtheme.dropdownStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
......@@ -3421,6 +3421,34 @@ class ProspectDetailsByModeState extends State<ProspectDetailsByMode> {
errorWidget(context, addleadProvider.statusError),
],
TextWidget(context, "Remarks"),
Container(
margin: EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextFormField(
controller: addleadProvider.addLeadProductRemarksController,
maxLines: 3,
enabled: true,
style: TextStyle(
color: Colors.black,
fontSize: 14,
),
decoration: InputDecoration(
hintText: "Enter remark",
hintStyle: TextStyle(
color: Colors.grey.shade500,
fontSize: 14,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 12),
),
),
),
InkResponse(
onTap:
addleadProvider.submitLoading
......
......@@ -29,7 +29,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
@override
void initState() {
// TODO: implement initState
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((source) {
......@@ -47,17 +46,15 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
provider.addProductPriceController.clear();
provider.addQuantityController.clear();
provider.addTotalAmountController.clear();
provider.remarkController.clear(); // Clear remarks too
} else {
provider.prefillProductForEdit(widget.editIndex!);
}
// provider.addEditInitializeForm(context);
});
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
_connectivity.disposeStream();
}
......@@ -77,15 +74,15 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
}
return (connection == "Online")
? Platform.isAndroid
? WillPopScope(
onWillPop: () => onBackPressed(context),
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context),
),
)
: _scaffold(context)
? WillPopScope(
onWillPop: () => onBackPressed(context),
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context),
),
)
: _scaffold(context)
: NoNetwork(context);
}
......@@ -94,7 +91,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
builder: (context, provider, child) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: appbarNew(context, "Generate Quotation", 0xFFFFFFFF),
backgroundColor: AppColors.scaffold_bg_color,
body: SingleChildScrollView(
......@@ -109,7 +105,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
horizontal: 10,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
......@@ -129,67 +124,55 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
items:
provider.productsList
.map(
(ord) => DropdownMenuItem<Products>(
value: ord,
child: Text(
"${ord.name}",
style: const TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
),
),
)
.toList(),
// provider.selectedOrderIds[index] != null?
// provider
// .orderList
// .firstWhere(
// (product) =>
// product
// .orderId ==
// provider
// .selectedOrderIds[index],
// )
value:
provider.selectedProducts != null
? provider.productsList.firstWhere(
(element) =>
element.id ==
provider.selectedProductsId,
)
: null,
items: provider.productsList
.map(
(ord) => DropdownMenuItem<Products>(
value: ord,
child: Text(
"${ord.name}",
style: const TextStyle(
fontSize: 14,
),
overflow: TextOverflow.ellipsis,
),
),
)
.toList(),
value: provider.selectedProducts != null
? provider.productsList.firstWhere(
(element) =>
element.id ==
provider.selectedProductsId,
)
: null,
onChanged: (Products? value) {
if (value != null) {
provider.selectedProducts = value;
provider.selectedProductsId = value.id!;
provider.selectedProductsValue = value.name;
provider.selectedProductsRemark = value.remarks;
provider.crmSelectedProductDetailsApiFunction(
context,
value.id.toString(),
);
}
provider.crmSelectedProductDetailsApiFunction(
context,
value!.id.toString(),
);
},
dropdownSearchData: DropdownSearchData(
searchInnerWidgetHeight: 50,
searchController:
provider.productSearchController,
provider.productSearchController,
searchInnerWidget: Padding(
padding: const EdgeInsets.all(8),
child: TextFormField(
controller:
provider.productSearchController,
provider.productSearchController,
decoration: InputDecoration(
isDense: true,
contentPadding:
const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
hintText: 'Search Product...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
......@@ -201,10 +184,10 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
),
searchMatchFn: (item, searchValue) {
return item.value?.name
?.toLowerCase()
.contains(
searchValue.toLowerCase(),
) ??
?.toLowerCase()
.contains(
searchValue.toLowerCase(),
) ??
false;
},
),
......@@ -222,7 +205,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
],
),
),
const SizedBox(height: 10),
textControllerWidget(
context,
......@@ -255,7 +237,7 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
provider.addTotalAmountController,
"Amount",
"Total Amount",
(_) {},
(_) {},
TextInputType.number,
true,
FilteringTextInputFormatter.digitsOnly,
......@@ -263,54 +245,133 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
),
],
),
// IconButton(
// icon: const Icon(Icons.delete),
// onPressed: provider.editProductPriceControllers.length > 1
// ? () => provider.editRemoveRow(j)
// : null,
// ),
TextWidget(context, "Remarks"),
Container(
margin: EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: AppColors.text_field_color,
borderRadius: BorderRadius.circular(14),
),
child: TextFormField(
controller: provider.remarkController,
maxLines: 3,
enabled: true,
style: TextStyle(
color: Colors.black,
fontSize: 14,
),
decoration: InputDecoration(
hintText: "Enter remark",
hintStyle: TextStyle(
color: Colors.grey.shade500,
fontSize: 14,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 12, vertical: 12),
),
),
)
],
),
),
],
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkWell(
onTap: () {
HapticFeedback.selectionClick();
if (provider.selectedProducts != null) {
final productData = {
"product_id": provider.selectedProductsId!,
"price": provider.addProductPriceController.text,
"qty": provider.addQuantityController.text,
"net_price": provider.addTotalAmountController.text,
};
if (widget.editIndex != null) {
provider.updateProduct(widget.editIndex!, productData);
} else {
provider.addProduct(productData);
}
print(provider.getJsonEncodedProducts());
Navigator.pop(context, provider.getJsonEncodedProducts());
// Validate required fields
if (provider.selectedProducts == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Please select a product"),
backgroundColor: Colors.red,
duration: Duration(seconds: 2),
),
);
return;
}
if (provider.addProductPriceController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Please enter product price"),
backgroundColor: Colors.red,
duration: Duration(seconds: 2),
),
);
return;
}
if (provider.addQuantityController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Please enter quantity"),
backgroundColor: Colors.red,
duration: Duration(seconds: 2),
),
);
return;
}
// Prepare product data - FIX: Use .text for remarks
final productData = {
"product_id": provider.selectedProductsId!,
"price": provider.addProductPriceController.text,
"qty": provider.addQuantityController.text,
"net_price": provider.addTotalAmountController.text,
"remarks": provider.remarkController.text, // FIXED: Use .text
};
if (widget.editIndex != null) {
provider.updateProduct(widget.editIndex!, productData);
print("Product updated at index ${widget.editIndex}");
} else {
provider.addProduct(productData);
print("New product added");
}
print("Product data: ${provider.getJsonEncodedProducts()}");
// Show success message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.editIndex != null
? "Product updated successfully!"
: "Product added successfully!"),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
),
);
// Close screen after a short delay to show the success message
Future.delayed(Duration(milliseconds: 1500), () {
if (mounted) {
Navigator.pop(context, true); // Return true to indicate success
}
});
},
child: Container(
alignment: Alignment.center,
height: 45,
decoration: BoxDecoration(
color: AppColors.app_blue, //1487C9
color: AppColors.app_blue,
borderRadius: BorderRadius.circular(14.0),
),
margin: EdgeInsets.symmetric(horizontal: 10),
child: Center(
child: Text(
"Submit",
widget.editIndex != null ? "Update Product" : "Add Product",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
......@@ -319,4 +380,18 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
},
);
}
}
Widget TextWidget(context, text) {
return Padding(
padding: const EdgeInsets.only(bottom: 5.0, top: 8.0),
child: Text(
text,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.semi_black,
),
),
);
}
}
\ No newline at end of file
......@@ -299,27 +299,28 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
),
errorWidget(context, provider.companynameError),
textControllerWidget(
context,
provider.contactPersonNameController,
"Contact Person Name",
"Enter Name",
provider.onChangeContactPersonName,
TextInputType.name,
false,
null,
focusNodes[1],
focusNodes[2],
TextInputAction.next,
),
errorWidget(context, provider.nameError),
textControllerWidget(
context,
provider.contactPersonNameController,
"Contact Person Name",
"Enter Name",
(value) => provider.onChangeContactPersonName(context, value),
TextInputType.name,
false,
null,
focusNodes[1],
focusNodes[2],
TextInputAction.next,
),
errorWidget(context, provider.nameError),
textControllerWidget(
context,
provider.mobileController,
"Mobile Number",
"Enter Mobile Number",
provider.onChangemobile,
(value) => provider.onChangemobile(context, value),
TextInputType.phone,
false,
FilteringTextInputFormatter.digitsOnly,
......@@ -931,40 +932,36 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
scrollDirection: Axis.horizontal,
itemCount: provider.productRows.length,
itemBuilder: (context, index) {
final product =
provider.productRows[index];
final productName =
provider.productsList
.firstWhere(
(p) =>
p.id ==
product['product_id'],
orElse:
() => Products(
id: '',
name: 'Unknown',
),
)
.name;
final product = provider.productRows[index];
final productName = provider.productsList
.firstWhere(
(p) => p.id == product['product_id'],
orElse: () => Products(
id: '',
name: 'Unknown',
),
)
.name;
final prodPrice = product['price'] ?? '-';
final prodQty = product['qty'] ?? '-';
final totalPrice =
product['net_price'] ?? '-';
final totalPrice = product['net_price'] ?? '-';
// FIX: Get the text from TextEditingController, not the controller itself
final remark = product['remarks'] is TextEditingController
? (product['remarks'] as TextEditingController).text
: product['remarks']?.toString() ?? '';
return InkResponse(
onTap: () async {
var res = await Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
Addleadproductscreen(
type: "Edit",
editIndex: index,
),
builder: (context) => Addleadproductscreen(
type: "Edit",
editIndex: index,
),
settings: RouteSettings(
name:
'Generatequotationaddeditproduct',
name: 'Generatequotationaddeditproduct',
),
),
);
......@@ -973,21 +970,10 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
}
},
child: Container(
width:
MediaQuery.of(
context,
).size.width *
0.8,
width: MediaQuery.of(context).size.width * 0.8,
margin: EdgeInsets.only(
left: index == 0 ? 10 : 5,
right:
index ==
provider
.productRows
.length -
1
? 10
: 5,
right: index == provider.productRows.length - 1 ? 10 : 5,
bottom: 5,
),
padding: EdgeInsets.symmetric(
......@@ -996,15 +982,11 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
),
decoration: BoxDecoration(
color: Color(0xFFE6F6FF),
borderRadius: BorderRadius.circular(
14,
),
borderRadius: BorderRadius.circular(14),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 1,
......@@ -1016,10 +998,8 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
Expanded(
flex: 6,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
......@@ -1028,32 +1008,23 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
child: Text(
productName ?? "-",
maxLines: 2,
overflow:
TextOverflow
.ellipsis,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily:
"JakartaMedium",
fontFamily: "JakartaMedium",
fontSize: 14,
color:
AppColors
.semi_black,
color: AppColors.semi_black,
),
),
),
Expanded(
flex: 3,
child: Text(
textAlign:
TextAlign.right,
textAlign: TextAlign.right,
"₹$prodPrice",
style: TextStyle(
fontFamily:
"JakartaMedium",
fontFamily: "JakartaMedium",
fontSize: 14,
color:
AppColors
.semi_black,
color: AppColors.semi_black,
),
),
),
......@@ -1062,42 +1033,51 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
Text(
"x $prodQty",
style: TextStyle(
fontFamily:
"JakartaMedium",
fontFamily: "JakartaMedium",
fontSize: 14,
color:
AppColors.grey_semi,
color: AppColors.grey_semi,
),
),
SizedBox(height: 5),
DottedLine(
dashGapLength: 4,
dashGapColor:
Colors.white,
dashColor:
AppColors.grey_semi,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
SizedBox(height: 5),
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"₹$totalPrice",
style: TextStyle(
fontFamily:
"JakartaMedium",
fontFamily: "JakartaMedium",
fontSize: 14,
color:
AppColors
.semi_black,
color: AppColors.semi_black,
),
),
],
),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
remark, // Now this is a String, not a TextEditingController
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.semi_black,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
)
],
),
),
......
......@@ -11,6 +11,8 @@ import 'package:generp/Utils/commonWidgets.dart';
import 'package:generp/Utils/dropdownTheme.dart';
import 'package:provider/provider.dart';
import '../order/ordersListByModes.dart';
class Followupupdatescreen extends StatefulWidget {
final leadID;
final mode;
......@@ -391,7 +393,7 @@ class _FollowupupdatescreenState extends State<Followupupdatescreen> {
),
items:
<String>[
// 'Order Gain',
'Order Gain',
'Order Lost',
'No Requirement',
]
......@@ -709,6 +711,9 @@ class _FollowupupdatescreenState extends State<Followupupdatescreen> {
provider.selectedCompetitor,
provider.selectedLeadStatus,
provider.selectNextAppointmentType,
provider.followUpFeedbackController,
provider.selectedTime,
provider.currentLocationLatLng,
provider.smsSent,
widget.mode,
);
......
......@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:http/http.dart' as http;
import 'dart:typed_data';
import 'package:photo_view/photo_view.dart';
import '../../Utils/app_colors.dart';
......@@ -24,7 +25,7 @@ class Fileviewer extends StatefulWidget {
class _FileviewerState extends State<Fileviewer> {
final Completer<InAppWebViewController> _controller =
Completer<InAppWebViewController>();
Completer<InAppWebViewController>();
var empId = "";
var sessionId = "";
bool isLoading = true;
......@@ -36,6 +37,11 @@ class _FileviewerState extends State<Fileviewer> {
bool pullToRefreshEnabled = true;
final GlobalKey webViewKey = GlobalKey();
// Zoom control variables
PhotoViewController _photoViewController = PhotoViewController();
PhotoViewScaleStateController _scaleStateController = PhotoViewScaleStateController();
String getFileExtension(String fileName) {
print(widget.fileUrl);
return fileName.split('.').last.toLowerCase();
......@@ -51,36 +57,52 @@ class _FileviewerState extends State<Fileviewer> {
}
var Finalurl;
@override
void initState() {
// loadData();
pullToRefreshController =
kIsWeb
? null
: PullToRefreshController(
settings: pullToRefreshSettings,
onRefresh: () async {
if (defaultTargetPlatform == TargetPlatform.android) {
webViewController?.reload();
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
webViewController?.loadUrl(
urlRequest: URLRequest(
url: await webViewController?.getUrl(),
),
);
}
},
);
// print("URL:${widget.url}");
kIsWeb
? null
: PullToRefreshController(
settings: pullToRefreshSettings,
onRefresh: () async {
if (defaultTargetPlatform == TargetPlatform.android) {
webViewController?.reload();
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
webViewController?.loadUrl(
urlRequest: URLRequest(
url: await webViewController?.getUrl(),
),
);
}
},
);
// Initialize photo view controllers
_photoViewController = PhotoViewController();
_scaleStateController = PhotoViewScaleStateController();
super.initState();
}
@override
void dispose() {
_photoViewController.dispose();
_scaleStateController.dispose();
pullToRefreshController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: appbarNew(context, "File Viewer", 0xFFFFFFFF),
body: SafeArea(child: Center(child: fileWidget(context))),
body: SafeArea(
child: Center(
child: fileWidget(context)
),
),
);
}
......@@ -91,28 +113,91 @@ class _FileviewerState extends State<Fileviewer> {
case 'jpeg':
case 'png':
case 'gif':
return CachedNetworkImage(
imageUrl: widget.fileUrl,
placeholder:
(context, url) =>
const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => const Icon(Icons.error),
fit: BoxFit.contain,
);
case 'bmp':
case 'webp':
return _buildImageViewer();
case 'pdf':
return SfPdfViewer.network(widget.fileUrl, key: GlobalKey());
return _buildPdfViewer();
case 'doc':
case 'docx':
case 'xls':
case 'xlsx':
return InAppWebView(
case 'ppt':
case 'pptx':
return _buildDocumentViewer();
default:
return _buildUnsupportedViewer();
}
}
Widget _buildImageViewer() {
return PhotoView(
imageProvider: CachedNetworkImageProvider(widget.fileUrl),
loadingBuilder: (context, event) => Center(
child: Container(
width: 40,
height: 40,
child: CircularProgressIndicator(
value: event == null ? 0 : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1),
),
),
),
errorBuilder: (context, error, stackTrace) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, color: Colors.red, size: 50),
SizedBox(height: 10),
Text(
'Failed to load image',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
),
backgroundDecoration: BoxDecoration(color: Colors.white),
minScale: PhotoViewComputedScale.contained * 0.5,
maxScale: PhotoViewComputedScale.covered * 4.0,
initialScale: PhotoViewComputedScale.contained,
basePosition: Alignment.center,
scaleStateController: _scaleStateController,
controller: _photoViewController,
enableRotation: true,
gestureDetectorBehavior: HitTestBehavior.deferToChild,
filterQuality: FilterQuality.high,
);
}
Widget _buildPdfViewer() {
return SfPdfViewer.network(
widget.fileUrl,
key: GlobalKey(),
canShowScrollHead: true,
canShowPaginationDialog: true,
pageLayoutMode: PdfPageLayoutMode.single,
interactionMode: PdfInteractionMode.pan,
enableDoubleTapZooming: true,
enableTextSelection: true,
onZoomLevelChanged: (PdfZoomDetails details) {
// Use the correct property name
//print('Zoom level changed: ${details.zoomLevel}');
},
);
}
Widget _buildDocumentViewer() {
return Stack(
children: [
InAppWebView(
key: webViewKey,
initialUrlRequest: URLRequest(url: WebUri(widget.fileUrl)),
androidOnGeolocationPermissionsShowPrompt: (
InAppWebViewController controller,
String origin,
) async {
InAppWebViewController controller,
String origin,
) async {
return GeolocationPermissionShowPromptResponse(
origin: origin,
allow: true,
......@@ -120,27 +205,41 @@ class _FileviewerState extends State<Fileviewer> {
);
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
javaScriptEnabled: true,
clearCache: true,
supportZoom: true,
),
android: AndroidInAppWebViewOptions(
useWideViewPort: true,
loadWithOverviewMode: true,
allowContentAccess: true,
geolocationEnabled: true,
allowFileAccess: true,
databaseEnabled: true, // Enables the WebView database
domStorageEnabled: true, // Enables DOM storage
builtInZoomControls: true, // Enables the built-in zoom controls
displayZoomControls: false, // Disables displaying zoom controls
safeBrowsingEnabled: true, // Enables Safe Browsing
databaseEnabled: true,
domStorageEnabled: true,
builtInZoomControls: true,
displayZoomControls: false,
safeBrowsingEnabled: true,
clearSessionCache: true,
supportMultipleWindows: false,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
allowsAirPlayForMediaPlayback: true,
allowsPictureInPictureMediaPlayback: true,
allowsBackForwardNavigationGestures: true,
allowsLinkPreview: true,
isFraudulentWebsiteWarningEnabled: true,
),
ios: IOSInAppWebViewOptions(allowsInlineMediaPlayback: true),
),
androidOnPermissionRequest: (
InAppWebViewController controller,
String origin,
List<String> resources,
) async {
InAppWebViewController controller,
String origin,
List<String> resources,
) async {
return PermissionRequestResponse(
resources: resources,
action: PermissionRequestResponseAction.GRANT,
......@@ -152,18 +251,29 @@ class _FileviewerState extends State<Fileviewer> {
},
pullToRefreshController: pullToRefreshController,
onLoadStart: (controller, url) {
return setState(() {
setState(() {
isLoading = true;
});
},
onLoadStop: (controller, url) {
pullToRefreshController?.endRefreshing();
return setState(() {
setState(() {
isLoading = false;
});
// Enable zooming in WebView
controller.evaluateJavascript(source: """
var meta = document.createElement('meta');
meta.name = 'viewport';
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=4.0, user-scalable=yes';
document.getElementsByTagName('head')[0].appendChild(meta);
""");
},
onReceivedError: (controller, request, error) {
pullToRefreshController?.endRefreshing();
setState(() {
isLoading = false;
});
},
onProgressChanged: (controller, progress) {
if (progress == 100) {
......@@ -172,21 +282,101 @@ class _FileviewerState extends State<Fileviewer> {
},
onConsoleMessage: (controller, consoleMessage) {
if (kDebugMode) {
debugPrint("consoleMessage$consoleMessage");
debugPrint("consoleMessage: ${consoleMessage.message}");
}
debugPrint("JavaScript console message: ${consoleMessage.message}");
},
);
default:
return Container();
}
),
// Loading indicator for documents
if (isLoading)
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.3),
child: Center(
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(AppColors.app_blue),
),
SizedBox(height: 10),
Text(
'Loading Document...',
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
],
),
),
),
),
),
],
);
}
Widget _buildUnsupportedViewer() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.insert_drive_file,
size: 64,
color: Colors.grey[400],
),
SizedBox(height: 16),
Text(
'Unsupported File Format',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
),
),
SizedBox(height: 8),
Text(
'Format: ${getFileExtension(widget.fileName).toUpperCase()}',
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () {
_launchUrl(widget.fileUrl);
},
icon: Icon(Icons.open_in_new),
label: Text('Open in External App'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.app_blue,
foregroundColor: Colors.white,
),
),
],
),
);
}
Future<Uint8List?> _loadPdf(String url) async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
print(response.bodyBytes);
return response.bodyBytes;
}
} catch (e) {
......@@ -194,4 +384,4 @@ class _FileviewerState extends State<Fileviewer> {
}
return null;
}
}
}
\ No newline at end of file
This diff is collapsed.
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