Commit dc88a3f9 authored by Sai Srinivas's avatar Sai Srinivas
Browse files

gen erp 08-10

parent d2c9404a
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:generp/Models/VersionsResponse.dart'; import 'package:generp/Models/VersionsResponse.dart';
import 'package:generp/Notifiers/HomeScreenNotifier.dart';
import 'package:generp/Notifiers/loginNotifier.dart'; import 'package:generp/Notifiers/loginNotifier.dart';
import 'package:generp/Utils/SharedpreferencesService.dart'; import 'package:generp/Utils/SharedpreferencesService.dart';
import 'package:generp/screens/HomeScreen.dart'; import 'package:generp/screens/HomeScreen.dart';
...@@ -55,7 +54,7 @@ class SplashVersionNotifier extends ChangeNotifier { ...@@ -55,7 +54,7 @@ class SplashVersionNotifier extends ChangeNotifier {
if (data != null) { if (data != null) {
if (kDebugMode) { if (kDebugMode) {
print("Current Build: $currentBuild"); print("Current Build: $currentBuild");
print("Server Response: $data"); print("Server Response: ${data.latestVersionCode}");
} }
if (Platform.isAndroid && if (Platform.isAndroid &&
currentBuild < (data.latestVersionCode ?? 0)) { currentBuild < (data.latestVersionCode ?? 0)) {
......
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
...@@ -10,56 +11,138 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; ...@@ -10,56 +11,138 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart'; import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:generp/Models/hrmModels/leaveApplicationLIstResponse.dart'; import 'package:generp/Models/hrmModels/leaveApplicationLIstResponse.dart';
import 'package:generp/Utils/app_colors.dart'; import 'package:generp/Utils/app_colors.dart';
import 'package:generp/screens/AttendanceScreen.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 'screens/notifierExports.dart';
import 'package:generp/Utils/SharedpreferencesService.dart'; import 'package:generp/Utils/SharedpreferencesService.dart';
import 'package:generp/screens/splash.dart'; import 'package:generp/screens/splash.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'Utils/commonWidgets.dart'; import 'Utils/commonWidgets.dart';
// Navigator Key for global navigation
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Notification Channel
const AndroidNotificationChannel channel = AndroidNotificationChannel( const AndroidNotificationChannel channel = AndroidNotificationChannel(
'generp_channel', // id 'generp_channel',
'generp_channel_name', 'generp_channel_name',
importance: Importance.max, importance: Importance.max,
playSound: false, playSound: true,
); );
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async { Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(); await Firebase.initializeApp();
String type = message.data['type'] ?? ''; debugPrint("📩 Background notification received: ${message.data}");
}
if (type == 'offline_reminder') { void handleNotificationTap(Map<String, dynamic> data) {
FlutterRingtonePlayer().play( debugPrint("👉 Handling notification tap: $data");
// fromAsset: "assets/offline_reminder.mp3",
ios: IosSounds.glass, // Specify the iOS sound try {
); String type = data['type'] ?? '';
} else if (type == 'normal') { String? rollId = data['RoleID'] ?? data['rollId'];
FlutterRingtonePlayer().play( String? notificationId = data['notification_id'] ?? data['notificationId'];
// fromAsset: "assets/notification_sound.mp3", String? url = data['url'];
ios: IosSounds.glass, // Specify the iOS sound String? mode = data['Parameter'];
); debugPrint("👉 Handling notification tap: $mode");
} else if (type == 'web_erp_notification') {
FlutterRingtonePlayer().play( if (type.toLowerCase() == "app") {
// fromAsset: "assets/notification_sound.mp3", // 🔥 Navigation based on RoleID
ios: IosSounds.glass, // Specify the iOS sound switch (rollId) {
); case "1":
} else { navigatorKey.currentState?.pushNamed('/crm_lead_list');
FlutterRingtonePlayer().play( break;
// fromAsset: "assets/notification_sound.mp3", case "4":
// will be the sound on Android navigatorKey.currentState?.pushNamed('/crm_prospect_list');
ios: IosSounds.glass, // will be the sound on iOS break;
); case "5":
} navigatorKey.currentState?.pushNamed(
if (kDebugMode) { '/crm_prospect_details',
print('A Background message just showed up: ${message.messageId}'); 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 { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// Firebase init
if (Platform.isAndroid) { if (Platform.isAndroid) {
await Firebase.initializeApp( await Firebase.initializeApp(
options: FirebaseOptions( options: FirebaseOptions(
...@@ -72,60 +155,147 @@ void main() async { ...@@ -72,60 +155,147 @@ void main() async {
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
await Firebase.initializeApp(); await Firebase.initializeApp();
} }
if (kDebugMode) {
if (Firebase.apps.isNotEmpty) { // 🔔 Local Notification Init
print("Firebase is initialized"); const AndroidInitializationSettings initSettingsAndroid =
} else { AndroidInitializationSettings('@mipmap/ic_launcher');
print("Firebase is not initialized");
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; FirebaseMessaging messaging = FirebaseMessaging.instance;
await messaging.requestPermission(alert: true, badge: true, sound: true);
NotificationSettings settings = await messaging.requestPermission( // Foreground notification
alert: true,
announcement: true,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: false,
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) { FirebaseMessaging.onMessage.listen((RemoteMessage message) {
RemoteNotification? notification = message.notification; final notification = message.notification;
AndroidNotification? android = message.notification?.android; final android = message.notification?.android;
print("msg");
String type = message.data['type'] ?? ''; debugPrint("😊 Foreground msg received: ${message.data}");
if (type == 'offline_reminder') {
FlutterRingtonePlayer().play( if (notification != null && android != null) {
fromAsset: "assets/offline_reminder.mp3", // flutterLocalNotificationsPlugin.show(
ios: IosSounds.glass, // Specify the iOS sound // notification.hashCode,
); // notification.title,
} else if (type == 'normal') { // notification.body,
FlutterRingtonePlayer().play( // NotificationDetails(
fromAsset: "assets/notification_sound.mp3", // android: AndroidNotificationDetails(
ios: IosSounds.glass, // Specify the iOS sound // channel.id,
); // channel.name,
} else if (type == 'web_erp_notification') { // importance: Importance.max,
FlutterRingtonePlayer().play( // priority: Priority.high,
fromAsset: "assets/notification_sound.mp3", // icon: '@mipmap/ic_launcher',
ios: IosSounds.glass, // Specify the iOS sound // ),
); // ),
} else { // payload: jsonEncode({
FlutterRingtonePlayer().play( // "type": message.data['type'],
fromAsset: // "rollId": message.data['RoleID'],
"assets/notification_sound.mp3", // will be the sound on Android // "notificationId": message.data['notification_id'],
ios: IosSounds.glass, // will be the sound on iOS // }),
// );
}
// ⚡ 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 await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation< .resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin AndroidFlutterLocalNotificationsPlugin>()
>()
?.createNotificationChannel(channel); ?.createNotificationChannel(channel);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
...@@ -134,13 +304,27 @@ void main() async { ...@@ -134,13 +304,27 @@ void main() async {
sound: true, sound: true,
); );
await FirebaseMessaging.instance.getToken().then((value) { FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
String? token = value; String type = message.data['type'] ?? '';
String redirectUrl = message.data['redirect_url'] ?? '';
handleNotificationTap(message.data);
// Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => Dashboard()),
// );
if (kDebugMode) { 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()); runApp(const MyApp());
...@@ -153,19 +337,19 @@ class MyApp extends StatelessWidget { ...@@ -153,19 +337,19 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// SystemChrome.setApplicationSwitcherDescription(ApplicationSwitcherDescription()); // SystemChrome.setApplicationSwitcherDescription(ApplicationSwitcherDescription());
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { // FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
String type = message.data['type'] ?? ''; // String type = message.data['type'] ?? '';
String redirectUrl = message.data['redirect_url'] ?? ''; // String redirectUrl = message.data['redirect_url'] ?? '';
//
// Navigator.push( // // Navigator.push(
// context, // // context,
// MaterialPageRoute(builder: (context) => Dashboard()), // // MaterialPageRoute(builder: (context) => Dashboard()),
// ); // // );
if (kDebugMode) { // if (kDebugMode) {
print('A new onMessageOpenedApp event was published!'); // print('A new onMessageOpenedApp event was published!');
} // }
}); // });
return MultiProvider( return MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (_) => SplashVersionNotifier()), ChangeNotifierProvider(create: (_) => SplashVersionNotifier()),
...@@ -226,9 +410,7 @@ class MyApp extends StatelessWidget { ...@@ -226,9 +410,7 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => Dispatchorderprovider()), ChangeNotifierProvider(create: (_) => Dispatchorderprovider()),
ChangeNotifierProvider(create: (_) => followUpUpdateProvider()), ChangeNotifierProvider(create: (_) => followUpUpdateProvider()),
ChangeNotifierProvider(create: (_) => Appointmentcalendarprovider()), ChangeNotifierProvider(create: (_) => Appointmentcalendarprovider()),
ChangeNotifierProvider( ChangeNotifierProvider(create: (_) => Addnewleadsandprospectsprovider()),
create: (_) => Addnewleadsandprospectsprovider(),
),
ChangeNotifierProvider(create: (_) => HrmAccessiblePagesProvider()), ChangeNotifierProvider(create: (_) => HrmAccessiblePagesProvider()),
ChangeNotifierProvider(create: (_) => Attendancelistprovider()), ChangeNotifierProvider(create: (_) => Attendancelistprovider()),
ChangeNotifierProvider(create: (_) => AttendanceDetailsProvider()), ChangeNotifierProvider(create: (_) => AttendanceDetailsProvider()),
...@@ -237,12 +419,36 @@ class MyApp extends StatelessWidget { ...@@ -237,12 +419,36 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => RewardListProvider()), ChangeNotifierProvider(create: (_) => RewardListProvider()),
ChangeNotifierProvider(create: (_) => LeaveApplicationListProvider()), ChangeNotifierProvider(create: (_) => LeaveApplicationListProvider()),
ChangeNotifierProvider(create: (_) => Orgprovider()), ChangeNotifierProvider(create: (_) => Orgprovider()),
ChangeNotifierProvider(create: (_) => AdvanceListProvider()),
ChangeNotifierProvider(create: (_) => CasualLeaveHistoryProvider()),
], ],
child: Builder( child: Builder(
builder: (BuildContext context) { builder: (BuildContext context) {
return MaterialApp( return MaterialApp(
navigatorKey: navigatorKey,
routes: {
'/home': (context) => const AttendanceScreen(),
'/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( scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse}, dragDevices: {PointerDeviceKind.touch,PointerDeviceKind.mouse},
), ),
navigatorObservers: [MyNavigatorObserver()], navigatorObservers: [MyNavigatorObserver()],
...@@ -261,6 +467,7 @@ class MyApp extends StatelessWidget { ...@@ -261,6 +467,7 @@ class MyApp extends StatelessWidget {
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
scaffoldBackgroundColor: Colors.white, scaffoldBackgroundColor: Colors.white,
dialogBackgroundColor: Colors.white,
cardColor: Colors.white, cardColor: Colors.white,
shadowColor: Colors.white54, shadowColor: Colors.white54,
searchBarTheme: const SearchBarThemeData(), searchBarTheme: const SearchBarThemeData(),
...@@ -304,8 +511,8 @@ class MyApp extends StatelessWidget { ...@@ -304,8 +511,8 @@ class MyApp extends StatelessWidget {
dragHandleSize: Size(60.0, 6.0), dragHandleSize: Size(60.0, 6.0),
), ),
colorScheme: const ColorScheme.light( colorScheme: const ColorScheme.light(
surface: Colors.white, background: Colors.white,
).copyWith(surface: Colors.white), ).copyWith(background: Colors.white),
scrollbarTheme: ScrollbarThemeData( scrollbarTheme: ScrollbarThemeData(
minThumbLength: 20, minThumbLength: 20,
interactive: true, interactive: true,
...@@ -316,8 +523,10 @@ class MyApp extends StatelessWidget { ...@@ -316,8 +523,10 @@ class MyApp extends StatelessWidget {
), ),
), ),
checkboxTheme: CheckboxThemeData( checkboxTheme: CheckboxThemeData(
side: BorderSide(width: 0.5), side: BorderSide(width: 0.5),
checkColor: WidgetStatePropertyAll(AppColors.white), checkColor: WidgetStatePropertyAll(AppColors.white),
), ),
useMaterial3: true, useMaterial3: true,
// inputDecorationTheme: InputDecorationTheme( // inputDecorationTheme: InputDecorationTheme(
......
...@@ -1779,12 +1779,12 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1779,12 +1779,12 @@ class _MyHomePageState extends State<MyHomePage> {
} }
Future<void> _showProfileBottomSheet(BuildContext context) { Future<void> _showProfileBottomSheet(BuildContext context) {
final profileNotifier = Provider.of<ProfileNotifer>(context, listen: false); // final profileNotifier = Provider.of<ProfileNotifer>(context, listen: false);
profileNotifier.fetchJobDescription( // profileNotifier.fetchJobDescription(
Provider.of<HomescreenNotifier>(context, listen: false), // Provider.of<HomescreenNotifier>(context, listen: false),
context, // context,
); // );
return showModalBottomSheet( return showModalBottomSheet(
useSafeArea: true, useSafeArea: true,
isDismissible: true, isDismissible: true,
...@@ -1975,18 +1975,7 @@ class _MyHomePageState extends State<MyHomePage> { ...@@ -1975,18 +1975,7 @@ class _MyHomePageState extends State<MyHomePage> {
AppColors.semi_black, AppColors.semi_black,
), ),
), ),
if (profileNotifier if (index == 2) // only for Designation
.response
?.jobDescription
?.jobDescription !=
null &&
profileNotifier
.response!
.jobDescription!
.jobDescription !=
"")
if (index ==
2) // only for Designation
InkWell( InkWell(
onTap: onTap:
index == 2 index == 2
......
...@@ -797,7 +797,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> { ...@@ -797,7 +797,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
if (productsNotEmpty) ...[ if (productsNotEmpty) ...[
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 130, height: 150,
child: ListView.builder( child: ListView.builder(
physics: AlwaysScrollableScrollPhysics(), physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,
...@@ -872,7 +872,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> { ...@@ -872,7 +872,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
); );
}, },
child: Container( child: Container(
height: 130, height: 140,
width: width:
MediaQuery.of(context).size.width * MediaQuery.of(context).size.width *
0.9, 0.9,
...@@ -966,7 +966,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> { ...@@ -966,7 +966,7 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
AppColors.grey_semi, AppColors.grey_semi,
), ),
), ),
SizedBox(height: 5), SizedBox(height: 3),
DottedLine( DottedLine(
dashGapLength: 4, dashGapLength: 4,
dashGapColor: dashGapColor:
...@@ -988,6 +988,24 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> { ...@@ -988,6 +988,24 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
.semi_black, .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,
),
),
],
)
], ],
), ),
), ),
...@@ -3458,6 +3476,34 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> { ...@@ -3458,6 +3476,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( // IconButton(
// icon: const Icon(Icons.delete), // icon: const Icon(Icons.delete),
// onPressed: editProvider.editProductPriceControllers.length > 1 // onPressed: editProvider.editProductPriceControllers.length > 1
...@@ -3476,8 +3522,8 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> { ...@@ -3476,8 +3522,8 @@ class _LeadDetailsByModeState extends State<LeadDetailsByMode> {
provider.leadDetails.id!, provider.leadDetails.id!,
type, type,
leadProductId, leadProductId,
editProvider editProvider.selectedAddEditProductId,
.selectedAddEditProductId, editProvider.addEditRemarkController
); );
}, },
child: Container( child: Container(
......
...@@ -267,24 +267,22 @@ class _LeadlistbymodeState extends State<Leadlistbymode> { ...@@ -267,24 +267,22 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
), ),
), ),
SizedBox(width: 10), SizedBox(width: 10),
Expanded( Expanded(
flex: 1, flex: 1,
child: InkResponse( child: InkResponse(
onTap: () { onTap: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
launch( _showContactOptions(context, crmLists[index].mob1);
'tel://${crmLists[index].mob1}', },
); child: SizedBox(
}, height: 35,
child: SizedBox( width: 35,
height: 35, child: SvgPicture.asset(
width: 35, "assets/svg/crm/lead_list_call_ic.svg",
child: SvgPicture.asset( ),
"assets/svg/crm/lead_list_call_ic.svg", ),
), ),
), ),
),
),
], ],
), ),
], ],
...@@ -930,6 +928,91 @@ class _LeadlistbymodeState extends State<Leadlistbymode> { ...@@ -930,6 +928,91 @@ 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),
const Text(
"Contact Options",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: "JakartaMedium",
color: Colors.black
),
),
const SizedBox(height: 20),
// --- Call Option ---
ListTile(
leading: const Icon(Icons.phone, color: Colors.green),
title: const Text("Call", style: TextStyle(
fontSize: 16,
fontFamily: "JakartaMedium",
)
),
onTap: () async {
Navigator.pop(context);
final uri = Uri.parse("tel:$phoneNumber");
await launchUrl(uri, mode: LaunchMode.externalApplication);
},
),
// --- WhatsApp Option ---
ListTile(
leading: const Icon(Icons.chat, color: Colors.teal),
title: const Text("WhatsApp", style: TextStyle(
fontSize: 16,
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) { bool _isFilterSelected(Leadlistprovider provider, int index) {
switch (index) { switch (index) {
......
...@@ -3375,43 +3375,43 @@ class ProspectDetailsByModeState extends State<ProspectDetailsByMode> { ...@@ -3375,43 +3375,43 @@ class ProspectDetailsByModeState extends State<ProspectDetailsByMode> {
children: [ children: [
Expanded( Expanded(
child: DropdownButton2<String>( child: DropdownButton2<String>(
hint: Text( isExpanded: true,
"Select Status", hint: const Row(
style: TextStyle(fontSize: 14), children: [
Expanded(
child: Text(
'Select Lead Status',
style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
),
],
), ),
items: items:
addleadProvider.statusList <String>['All', 'Cold', 'Hot', 'Warm']
.map( .map(
(slist) => (value) => DropdownMenuItem<String>(
DropdownMenuItem<String>( value: value,
value: slist, child: Text(
child: Text( value ?? '',
slist, style: const TextStyle(
style: TextStyle( fontSize: 14,
fontSize: 14, ),
), overflow: TextOverflow.ellipsis,
), ),
), ),
) )
.toList(), .toList(),
value: addleadProvider.selectedStatus, value: addleadProvider.selectedStatus,
onChanged: (String? value) { onChanged: (String? newValue) {
if (value != null) { setState(() {
if (addleadProvider addleadProvider.selectedStatus = newValue!;
.statusList });
.isNotEmpty) {
addleadProvider.selectedStatus =
value;
}
}
}, },
isExpanded: true,
buttonStyleData: ddtheme.buttonStyleData, buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData, iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: menuItemStyleData: ddtheme.menuItemStyleData,
ddtheme.menuItemStyleData, dropdownStyleData: ddtheme.dropdownStyleData,
dropdownStyleData:
ddtheme.dropdownStyleData,
), ),
), ),
], ],
...@@ -3421,6 +3421,34 @@ class ProspectDetailsByModeState extends State<ProspectDetailsByMode> { ...@@ -3421,6 +3421,34 @@ class ProspectDetailsByModeState extends State<ProspectDetailsByMode> {
errorWidget(context, addleadProvider.statusError), 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( InkResponse(
onTap: onTap:
addleadProvider.submitLoading addleadProvider.submitLoading
......
...@@ -29,7 +29,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -29,7 +29,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
@override @override
void initState() { void initState() {
// TODO: implement initState
super.initState(); super.initState();
_connectivity.initialise(); _connectivity.initialise();
_connectivity.myStream.listen((source) { _connectivity.myStream.listen((source) {
...@@ -47,17 +46,15 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -47,17 +46,15 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
provider.addProductPriceController.clear(); provider.addProductPriceController.clear();
provider.addQuantityController.clear(); provider.addQuantityController.clear();
provider.addTotalAmountController.clear(); provider.addTotalAmountController.clear();
provider.remarkController.clear(); // Clear remarks too
} else { } else {
provider.prefillProductForEdit(widget.editIndex!); provider.prefillProductForEdit(widget.editIndex!);
} }
// provider.addEditInitializeForm(context);
}); });
} }
@override @override
void dispose() { void dispose() {
// TODO: implement dispose
super.dispose(); super.dispose();
_connectivity.disposeStream(); _connectivity.disposeStream();
} }
...@@ -77,15 +74,15 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -77,15 +74,15 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
} }
return (connection == "Online") return (connection == "Online")
? Platform.isAndroid ? Platform.isAndroid
? WillPopScope( ? WillPopScope(
onWillPop: () => onBackPressed(context), onWillPop: () => onBackPressed(context),
child: SafeArea( child: SafeArea(
top: false, top: false,
bottom: true, bottom: true,
child: _scaffold(context), child: _scaffold(context),
), ),
) )
: _scaffold(context) : _scaffold(context)
: NoNetwork(context); : NoNetwork(context);
} }
...@@ -94,7 +91,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -94,7 +91,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
builder: (context, provider, child) { builder: (context, provider, child) {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
appBar: appbarNew(context, "Generate Quotation", 0xFFFFFFFF), appBar: appbarNew(context, "Generate Quotation", 0xFFFFFFFF),
backgroundColor: AppColors.scaffold_bg_color, backgroundColor: AppColors.scaffold_bg_color,
body: SingleChildScrollView( body: SingleChildScrollView(
...@@ -109,7 +105,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -109,7 +105,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
horizontal: 10, horizontal: 10,
vertical: 10, vertical: 10,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
...@@ -129,67 +124,55 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -129,67 +124,55 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
style: TextStyle(fontSize: 14), style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
items: items: provider.productsList
provider.productsList .map(
.map( (ord) => DropdownMenuItem<Products>(
(ord) => DropdownMenuItem<Products>( value: ord,
value: ord, child: Text(
child: Text( "${ord.name}",
"${ord.name}", style: const TextStyle(
style: const TextStyle( fontSize: 14,
fontSize: 14, ),
), overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, ),
), ),
), )
) .toList(),
.toList(), value: provider.selectedProducts != null
// provider.selectedOrderIds[index] != null? ? provider.productsList.firstWhere(
// provider (element) =>
// .orderList element.id ==
// .firstWhere( provider.selectedProductsId,
// (product) => )
// product : null,
// .orderId ==
// provider
// .selectedOrderIds[index],
// )
value:
provider.selectedProducts != null
? provider.productsList.firstWhere(
(element) =>
element.id ==
provider.selectedProductsId,
)
: null,
onChanged: (Products? value) { onChanged: (Products? value) {
if (value != null) { if (value != null) {
provider.selectedProducts = value; provider.selectedProducts = value;
provider.selectedProductsId = value.id!; provider.selectedProductsId = value.id!;
provider.selectedProductsValue = value.name; provider.selectedProductsValue = value.name;
provider.selectedProductsRemark = value.remarks;
provider.crmSelectedProductDetailsApiFunction(
context,
value.id.toString(),
);
} }
provider.crmSelectedProductDetailsApiFunction(
context,
value!.id.toString(),
);
}, },
dropdownSearchData: DropdownSearchData( dropdownSearchData: DropdownSearchData(
searchInnerWidgetHeight: 50, searchInnerWidgetHeight: 50,
searchController: searchController:
provider.productSearchController, provider.productSearchController,
searchInnerWidget: Padding( searchInnerWidget: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextFormField( child: TextFormField(
controller: controller:
provider.productSearchController, provider.productSearchController,
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
contentPadding: contentPadding:
const EdgeInsets.symmetric( const EdgeInsets.symmetric(
horizontal: 10, horizontal: 10,
vertical: 8, vertical: 8,
), ),
hintText: 'Search Product...', hintText: 'Search Product...',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
...@@ -201,10 +184,10 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -201,10 +184,10 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
), ),
searchMatchFn: (item, searchValue) { searchMatchFn: (item, searchValue) {
return item.value?.name return item.value?.name
?.toLowerCase() ?.toLowerCase()
.contains( .contains(
searchValue.toLowerCase(), searchValue.toLowerCase(),
) ?? ) ??
false; false;
}, },
), ),
...@@ -222,7 +205,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -222,7 +205,6 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
], ],
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
textControllerWidget( textControllerWidget(
context, context,
...@@ -255,7 +237,7 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -255,7 +237,7 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
provider.addTotalAmountController, provider.addTotalAmountController,
"Amount", "Amount",
"Total Amount", "Total Amount",
(_) {}, (_) {},
TextInputType.number, TextInputType.number,
true, true,
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
...@@ -263,54 +245,133 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> { ...@@ -263,54 +245,133 @@ class _AddleadproductscreenState extends State<Addleadproductscreen> {
), ),
], ],
), ),
TextWidget(context, "Remarks"),
// IconButton( Container(
// icon: const Icon(Icons.delete), margin: EdgeInsets.only(bottom: 6),
// onPressed: provider.editProductPriceControllers.length > 1 decoration: BoxDecoration(
// ? () => provider.editRemoveRow(j) color: AppColors.text_field_color,
// : null, 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:
FloatingActionButtonLocation.centerFloat, FloatingActionButtonLocation.centerFloat,
floatingActionButton: InkWell( floatingActionButton: InkWell(
onTap: () { onTap: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
if (provider.selectedProducts != null) {
final productData = { // Validate required fields
"product_id": provider.selectedProductsId!, if (provider.selectedProducts == null) {
"price": provider.addProductPriceController.text, ScaffoldMessenger.of(context).showSnackBar(
"qty": provider.addQuantityController.text, SnackBar(
"net_price": provider.addTotalAmountController.text, content: Text("Please select a product"),
}; backgroundColor: Colors.red,
if (widget.editIndex != null) { duration: Duration(seconds: 2),
provider.updateProduct(widget.editIndex!, productData); ),
} else { );
provider.addProduct(productData); return;
} }
print(provider.getJsonEncodedProducts());
Navigator.pop(context, provider.getJsonEncodedProducts()); 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( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
height: 45, height: 45,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.app_blue, //1487C9 color: AppColors.app_blue,
borderRadius: BorderRadius.circular(14.0), borderRadius: BorderRadius.circular(14.0),
), ),
margin: EdgeInsets.symmetric(horizontal: 10), margin: EdgeInsets.symmetric(horizontal: 10),
child: Center( child: Center(
child: Text( child: Text(
"Submit", widget.editIndex != null ? "Update Product" : "Add Product",
textAlign: TextAlign.center, 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> { ...@@ -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> { ...@@ -299,27 +299,28 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
), ),
errorWidget(context, provider.companynameError), errorWidget(context, provider.companynameError),
textControllerWidget( textControllerWidget(
context, context,
provider.contactPersonNameController, provider.contactPersonNameController,
"Contact Person Name", "Contact Person Name",
"Enter Name", "Enter Name",
provider.onChangeContactPersonName, (value) => provider.onChangeContactPersonName(context, value),
TextInputType.name, TextInputType.name,
false, false,
null, null,
focusNodes[1], focusNodes[1],
focusNodes[2], focusNodes[2],
TextInputAction.next, TextInputAction.next,
), ),
errorWidget(context, provider.nameError),
errorWidget(context, provider.nameError),
textControllerWidget( textControllerWidget(
context, context,
provider.mobileController, provider.mobileController,
"Mobile Number", "Mobile Number",
"Enter Mobile Number", "Enter Mobile Number",
provider.onChangemobile, (value) => provider.onChangemobile(context, value),
TextInputType.phone, TextInputType.phone,
false, false,
FilteringTextInputFormatter.digitsOnly, FilteringTextInputFormatter.digitsOnly,
...@@ -931,40 +932,36 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> { ...@@ -931,40 +932,36 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: provider.productRows.length, itemCount: provider.productRows.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final product = final product = provider.productRows[index];
provider.productRows[index]; final productName = provider.productsList
final productName = .firstWhere(
provider.productsList (p) => p.id == product['product_id'],
.firstWhere( orElse: () => Products(
(p) => id: '',
p.id == name: 'Unknown',
product['product_id'], ),
orElse: )
() => Products( .name;
id: '',
name: 'Unknown',
),
)
.name;
final prodPrice = product['price'] ?? '-'; final prodPrice = product['price'] ?? '-';
final prodQty = product['qty'] ?? '-'; final prodQty = product['qty'] ?? '-';
final totalPrice = final totalPrice = product['net_price'] ?? '-';
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( return InkResponse(
onTap: () async { onTap: () async {
var res = await Navigator.push( var res = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: builder: (context) => Addleadproductscreen(
(context) => type: "Edit",
Addleadproductscreen( editIndex: index,
type: "Edit", ),
editIndex: index,
),
settings: RouteSettings( settings: RouteSettings(
name: name: 'Generatequotationaddeditproduct',
'Generatequotationaddeditproduct',
), ),
), ),
); );
...@@ -973,21 +970,10 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> { ...@@ -973,21 +970,10 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
} }
}, },
child: Container( child: Container(
width: width: MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(
context,
).size.width *
0.8,
margin: EdgeInsets.only( margin: EdgeInsets.only(
left: index == 0 ? 10 : 5, left: index == 0 ? 10 : 5,
right: right: index == provider.productRows.length - 1 ? 10 : 5,
index ==
provider
.productRows
.length -
1
? 10
: 5,
bottom: 5, bottom: 5,
), ),
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
...@@ -996,15 +982,11 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> { ...@@ -996,15 +982,11 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Color(0xFFE6F6FF), color: Color(0xFFE6F6FF),
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(14),
14,
),
), ),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.start,
MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 1,
...@@ -1016,10 +998,8 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> { ...@@ -1016,10 +998,8 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
Expanded( Expanded(
flex: 6, flex: 6,
child: Column( child: Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
...@@ -1028,32 +1008,23 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> { ...@@ -1028,32 +1008,23 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
child: Text( child: Text(
productName ?? "-", productName ?? "-",
maxLines: 2, maxLines: 2,
overflow: overflow: TextOverflow.ellipsis,
TextOverflow
.ellipsis,
style: TextStyle( style: TextStyle(
fontFamily: fontFamily: "JakartaMedium",
"JakartaMedium",
fontSize: 14, fontSize: 14,
color: color: AppColors.semi_black,
AppColors
.semi_black,
), ),
), ),
), ),
Expanded( Expanded(
flex: 3, flex: 3,
child: Text( child: Text(
textAlign: textAlign: TextAlign.right,
TextAlign.right,
"₹$prodPrice", "₹$prodPrice",
style: TextStyle( style: TextStyle(
fontFamily: fontFamily: "JakartaMedium",
"JakartaMedium",
fontSize: 14, fontSize: 14,
color: color: AppColors.semi_black,
AppColors
.semi_black,
), ),
), ),
), ),
...@@ -1062,42 +1033,51 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> { ...@@ -1062,42 +1033,51 @@ class _AddleadsprospectsscreenState extends State<Addleadsprospectsscreen> {
Text( Text(
"x $prodQty", "x $prodQty",
style: TextStyle( style: TextStyle(
fontFamily: fontFamily: "JakartaMedium",
"JakartaMedium",
fontSize: 14, fontSize: 14,
color: color: AppColors.grey_semi,
AppColors.grey_semi,
), ),
), ),
SizedBox(height: 5), SizedBox(height: 5),
DottedLine( DottedLine(
dashGapLength: 4, dashGapLength: 4,
dashGapColor: dashGapColor: Colors.white,
Colors.white, dashColor: AppColors.grey_semi,
dashColor:
AppColors.grey_semi,
dashLength: 2, dashLength: 2,
lineThickness: 0.5, lineThickness: 0.5,
), ),
SizedBox(height: 5), SizedBox(height: 5),
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment
.spaceBetween,
children: [ children: [
Text( Text(
"₹$totalPrice", "₹$totalPrice",
style: TextStyle( style: TextStyle(
fontFamily: fontFamily: "JakartaMedium",
"JakartaMedium",
fontSize: 14, fontSize: 14,
color: color: AppColors.semi_black,
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'; ...@@ -11,6 +11,8 @@ import 'package:generp/Utils/commonWidgets.dart';
import 'package:generp/Utils/dropdownTheme.dart'; import 'package:generp/Utils/dropdownTheme.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../order/ordersListByModes.dart';
class Followupupdatescreen extends StatefulWidget { class Followupupdatescreen extends StatefulWidget {
final leadID; final leadID;
final mode; final mode;
...@@ -391,7 +393,7 @@ class _FollowupupdatescreenState extends State<Followupupdatescreen> { ...@@ -391,7 +393,7 @@ class _FollowupupdatescreenState extends State<Followupupdatescreen> {
), ),
items: items:
<String>[ <String>[
// 'Order Gain', 'Order Gain',
'Order Lost', 'Order Lost',
'No Requirement', 'No Requirement',
] ]
...@@ -709,6 +711,9 @@ class _FollowupupdatescreenState extends State<Followupupdatescreen> { ...@@ -709,6 +711,9 @@ class _FollowupupdatescreenState extends State<Followupupdatescreen> {
provider.selectedCompetitor, provider.selectedCompetitor,
provider.selectedLeadStatus, provider.selectedLeadStatus,
provider.selectNextAppointmentType, provider.selectNextAppointmentType,
provider.followUpFeedbackController,
provider.selectedTime,
provider.currentLocationLatLng,
provider.smsSent, provider.smsSent,
widget.mode, widget.mode,
); );
......
...@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart'; ...@@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:photo_view/photo_view.dart';
import '../../Utils/app_colors.dart'; import '../../Utils/app_colors.dart';
...@@ -24,7 +25,7 @@ class Fileviewer extends StatefulWidget { ...@@ -24,7 +25,7 @@ class Fileviewer extends StatefulWidget {
class _FileviewerState extends State<Fileviewer> { class _FileviewerState extends State<Fileviewer> {
final Completer<InAppWebViewController> _controller = final Completer<InAppWebViewController> _controller =
Completer<InAppWebViewController>(); Completer<InAppWebViewController>();
var empId = ""; var empId = "";
var sessionId = ""; var sessionId = "";
bool isLoading = true; bool isLoading = true;
...@@ -36,6 +37,11 @@ class _FileviewerState extends State<Fileviewer> { ...@@ -36,6 +37,11 @@ class _FileviewerState extends State<Fileviewer> {
bool pullToRefreshEnabled = true; bool pullToRefreshEnabled = true;
final GlobalKey webViewKey = GlobalKey(); final GlobalKey webViewKey = GlobalKey();
// Zoom control variables
PhotoViewController _photoViewController = PhotoViewController();
PhotoViewScaleStateController _scaleStateController = PhotoViewScaleStateController();
String getFileExtension(String fileName) { String getFileExtension(String fileName) {
print(widget.fileUrl); print(widget.fileUrl);
return fileName.split('.').last.toLowerCase(); return fileName.split('.').last.toLowerCase();
...@@ -51,36 +57,52 @@ class _FileviewerState extends State<Fileviewer> { ...@@ -51,36 +57,52 @@ class _FileviewerState extends State<Fileviewer> {
} }
var Finalurl; var Finalurl;
@override @override
void initState() { void initState() {
// loadData();
pullToRefreshController = pullToRefreshController =
kIsWeb kIsWeb
? null ? null
: PullToRefreshController( : PullToRefreshController(
settings: pullToRefreshSettings, settings: pullToRefreshSettings,
onRefresh: () async { onRefresh: () async {
if (defaultTargetPlatform == TargetPlatform.android) { if (defaultTargetPlatform == TargetPlatform.android) {
webViewController?.reload(); webViewController?.reload();
} else if (defaultTargetPlatform == TargetPlatform.iOS) { } else if (defaultTargetPlatform == TargetPlatform.iOS) {
webViewController?.loadUrl( webViewController?.loadUrl(
urlRequest: URLRequest( urlRequest: URLRequest(
url: await webViewController?.getUrl(), url: await webViewController?.getUrl(),
), ),
); );
} }
}, },
); );
// print("URL:${widget.url}");
// Initialize photo view controllers
_photoViewController = PhotoViewController();
_scaleStateController = PhotoViewScaleStateController();
super.initState(); super.initState();
} }
@override
void dispose() {
_photoViewController.dispose();
_scaleStateController.dispose();
pullToRefreshController?.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
appBar: appbarNew(context, "File Viewer", 0xFFFFFFFF), 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> { ...@@ -91,28 +113,91 @@ class _FileviewerState extends State<Fileviewer> {
case 'jpeg': case 'jpeg':
case 'png': case 'png':
case 'gif': case 'gif':
return CachedNetworkImage( case 'bmp':
imageUrl: widget.fileUrl, case 'webp':
placeholder: return _buildImageViewer();
(context, url) =>
const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => const Icon(Icons.error),
fit: BoxFit.contain,
);
case 'pdf': case 'pdf':
return SfPdfViewer.network(widget.fileUrl, key: GlobalKey()); return _buildPdfViewer();
case 'doc': case 'doc':
case 'docx': case 'docx':
case 'xls': case 'xls':
case 'xlsx': 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)), initialUrlRequest: URLRequest(url: WebUri(widget.fileUrl)),
androidOnGeolocationPermissionsShowPrompt: ( androidOnGeolocationPermissionsShowPrompt: (
InAppWebViewController controller, InAppWebViewController controller,
String origin, String origin,
) async { ) async {
return GeolocationPermissionShowPromptResponse( return GeolocationPermissionShowPromptResponse(
origin: origin, origin: origin,
allow: true, allow: true,
...@@ -120,27 +205,41 @@ class _FileviewerState extends State<Fileviewer> { ...@@ -120,27 +205,41 @@ class _FileviewerState extends State<Fileviewer> {
); );
}, },
initialOptions: InAppWebViewGroupOptions( initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
javaScriptEnabled: true,
clearCache: true,
supportZoom: true,
),
android: AndroidInAppWebViewOptions( android: AndroidInAppWebViewOptions(
useWideViewPort: true, useWideViewPort: true,
loadWithOverviewMode: true, loadWithOverviewMode: true,
allowContentAccess: true, allowContentAccess: true,
geolocationEnabled: true, geolocationEnabled: true,
allowFileAccess: true, allowFileAccess: true,
databaseEnabled: true, // Enables the WebView database databaseEnabled: true,
domStorageEnabled: true, // Enables DOM storage domStorageEnabled: true,
builtInZoomControls: true, // Enables the built-in zoom controls builtInZoomControls: true,
displayZoomControls: false, // Disables displaying zoom controls displayZoomControls: false,
safeBrowsingEnabled: true, // Enables Safe Browsing safeBrowsingEnabled: true,
clearSessionCache: true, clearSessionCache: true,
supportMultipleWindows: false,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
allowsAirPlayForMediaPlayback: true,
allowsPictureInPictureMediaPlayback: true,
allowsBackForwardNavigationGestures: true,
allowsLinkPreview: true,
isFraudulentWebsiteWarningEnabled: true,
), ),
ios: IOSInAppWebViewOptions(allowsInlineMediaPlayback: true),
), ),
androidOnPermissionRequest: ( androidOnPermissionRequest: (
InAppWebViewController controller, InAppWebViewController controller,
String origin, String origin,
List<String> resources, List<String> resources,
) async { ) async {
return PermissionRequestResponse( return PermissionRequestResponse(
resources: resources, resources: resources,
action: PermissionRequestResponseAction.GRANT, action: PermissionRequestResponseAction.GRANT,
...@@ -152,18 +251,29 @@ class _FileviewerState extends State<Fileviewer> { ...@@ -152,18 +251,29 @@ class _FileviewerState extends State<Fileviewer> {
}, },
pullToRefreshController: pullToRefreshController, pullToRefreshController: pullToRefreshController,
onLoadStart: (controller, url) { onLoadStart: (controller, url) {
return setState(() { setState(() {
isLoading = true; isLoading = true;
}); });
}, },
onLoadStop: (controller, url) { onLoadStop: (controller, url) {
pullToRefreshController?.endRefreshing(); pullToRefreshController?.endRefreshing();
return setState(() { setState(() {
isLoading = false; 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) { onReceivedError: (controller, request, error) {
pullToRefreshController?.endRefreshing(); pullToRefreshController?.endRefreshing();
setState(() {
isLoading = false;
});
}, },
onProgressChanged: (controller, progress) { onProgressChanged: (controller, progress) {
if (progress == 100) { if (progress == 100) {
...@@ -172,21 +282,101 @@ class _FileviewerState extends State<Fileviewer> { ...@@ -172,21 +282,101 @@ class _FileviewerState extends State<Fileviewer> {
}, },
onConsoleMessage: (controller, consoleMessage) { onConsoleMessage: (controller, consoleMessage) {
if (kDebugMode) { 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 { Future<Uint8List?> _loadPdf(String url) async {
try { try {
final response = await http.get(Uri.parse(url)); final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) { if (response.statusCode == 200) {
print(response.bodyBytes);
return response.bodyBytes; return response.bodyBytes;
} }
} catch (e) { } catch (e) {
...@@ -194,4 +384,4 @@ class _FileviewerState extends State<Fileviewer> { ...@@ -194,4 +384,4 @@ class _FileviewerState extends State<Fileviewer> {
} }
return null; return null;
} }
} }
\ No newline at end of file
...@@ -1360,14 +1360,14 @@ class _PaymentrequestionlistdetailsState ...@@ -1360,14 +1360,14 @@ class _PaymentrequestionlistdetailsState
context, context,
provider.requestedAmount, provider.requestedAmount,
"Enter Requested Amount", "Enter Requested Amount",
(p0) {}, (p0) {},
), ),
textControllerWidget( textControllerWidget(
context, context,
provider.approvedAmount, provider.approvedAmount,
"Approved Amount", "Approved Amount",
"Enter Approved Amount", "Enter Approved Amount",
(p0) { (p0) {
provider.onChangeApprov(p0); provider.onChangeApprov(p0);
}, },
TextInputType.numberWithOptions(), TextInputType.numberWithOptions(),
...@@ -1383,7 +1383,7 @@ class _PaymentrequestionlistdetailsState ...@@ -1383,7 +1383,7 @@ class _PaymentrequestionlistdetailsState
remarks, remarks,
"Remarks", "Remarks",
"Enter Remarks", "Enter Remarks",
(p0) { (p0) {
provider.remarksError = null; provider.remarksError = null;
provider.notifyListeners(); provider.notifyListeners();
}, },
...@@ -1408,30 +1408,30 @@ class _PaymentrequestionlistdetailsState ...@@ -1408,30 +1408,30 @@ class _PaymentrequestionlistdetailsState
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
items: items:
provider.paymentsAccounts provider.paymentsAccounts
.map( .map(
(paymenents) => DropdownMenuItem< (paymenents) => DropdownMenuItem<
PaymentAccounts PaymentAccounts
>( >(
value: paymenents, value: paymenents,
child: Text( child: Text(
paymenents.name ?? '', paymenents.name ?? '',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
), ),
overflow: overflow:
TextOverflow.ellipsis, TextOverflow.ellipsis,
), ),
), ),
) )
.toList(), .toList(),
value: value:
provider.paymentsAccounts.contains( provider.paymentsAccounts.contains(
provider.selectedPaymentAccounts, provider.selectedPaymentAccounts,
) )
? provider.selectedPaymentAccounts ? provider.selectedPaymentAccounts
: null, : null,
// value: provider.selectedPaymentAccounts, // value: provider.selectedPaymentAccounts,
onChanged: (PaymentAccounts? value) { onChanged: (PaymentAccounts? value) {
if (value != null) { if (value != null) {
...@@ -1446,7 +1446,8 @@ class _PaymentrequestionlistdetailsState ...@@ -1446,7 +1446,8 @@ class _PaymentrequestionlistdetailsState
provider.selectedID = value.id!; provider.selectedID = value.id!;
provider.selectedValue = value.name!; provider.selectedValue = value.name!;
print( print(
"hfjkshfg${provider.selectedID}", "hfjkshfg" +
provider.selectedID.toString(),
); );
} }
} }
...@@ -1454,35 +1455,35 @@ class _PaymentrequestionlistdetailsState ...@@ -1454,35 +1455,35 @@ class _PaymentrequestionlistdetailsState
dropdownSearchData: DropdownSearchData( dropdownSearchData: DropdownSearchData(
searchInnerWidgetHeight: 50, searchInnerWidgetHeight: 50,
searchController: searchController:
provider provider
.paymentAccountSearchController, .paymentAccountSearchController,
searchInnerWidget: Padding( searchInnerWidget: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextFormField( child: TextFormField(
controller: controller:
provider provider
.paymentAccountSearchController, .paymentAccountSearchController,
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
contentPadding: contentPadding:
const EdgeInsets.symmetric( const EdgeInsets.symmetric(
horizontal: 10, horizontal: 10,
vertical: 8, vertical: 8,
), ),
hintText: 'Search account...', hintText: 'Search account...',
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: borderRadius:
BorderRadius.circular(8), BorderRadius.circular(8),
), ),
), ),
), ),
), ),
searchMatchFn: (item, searchValue) { searchMatchFn: (item, searchValue) {
return item.value?.name return item.value?.name
?.toLowerCase() ?.toLowerCase()
.contains( .contains(
searchValue.toLowerCase(), searchValue.toLowerCase(),
) ?? ) ??
false; false;
}, },
...@@ -1497,9 +1498,9 @@ class _PaymentrequestionlistdetailsState ...@@ -1497,9 +1498,9 @@ class _PaymentrequestionlistdetailsState
buttonStyleData: ddtheme.buttonStyleData, buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData, iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: menuItemStyleData:
ddtheme.menuItemStyleData, ddtheme.menuItemStyleData,
dropdownStyleData: dropdownStyleData:
ddtheme.dropdownStyleData, ddtheme.dropdownStyleData,
), ),
), ),
], ],
...@@ -1511,15 +1512,21 @@ class _PaymentrequestionlistdetailsState ...@@ -1511,15 +1512,21 @@ class _PaymentrequestionlistdetailsState
), ),
InkWell( InkWell(
onTap: () { onTap: () {
print("🖱️ === SUBMIT BUTTON TAPPED ===");
print("📋 Mode: ${widget.mode}");
print("📋 Payment ID: $paymentID");
print("📋 Approved Amount: ${provider.approvedAmount.text}");
print("📋 Remarks: ${remarks.text}");
print("📋 Selected Account ID: ${provider.selectedID}");
provider provider
.paymentrequisitionApproveSubmitAPIFunction( .paymentrequisitionApproveSubmitAPIFunction(
context, context,
widget.mode, widget.mode,
paymentID, paymentID,
provider.approvedAmount.text, provider.approvedAmount.text,
remarks.text, remarks.text,
provider.selectedID, provider.selectedID,
); );
}, },
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
......
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/HomeScreenNotifier.dart';
import '../../Models/hrmModels/advanceListResponse.dart';
import 'package:intl/intl.dart';
import 'package:flutter_svg/svg.dart';
import '../../Notifiers/hrmProvider/advanceProvider.dart';
import '../../Utils/app_colors.dart';
class AdvanceListScreen extends StatefulWidget {
const AdvanceListScreen({super.key});
@override
State<AdvanceListScreen> createState() => _AdvanceListScreenState();
}
class _AdvanceListScreenState extends State<AdvanceListScreen> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
final provider = Provider.of<AdvanceListProvider>(context, listen: false);
final homeProvider = Provider.of<HomescreenNotifier>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((_) {
provider.fetchAdvanceList(context, homeProvider.session, homeProvider.empId);
});
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
provider.loadMoreAdvanceList(context, homeProvider.session, homeProvider.empId);
}
});
}
@override
Widget build(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;
final double screenHeight = MediaQuery.of(context).size.height;
final bool isSmallScreen = screenWidth < 360;
final bool isLargeScreen = screenWidth > 600;
return Scaffold(
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 : 25,
),
),
SizedBox(width: isSmallScreen ? 8 : 10),
Text(
"Advance List",
style: TextStyle(
fontSize: isSmallScreen ? 16 : 18,
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
backgroundColor: AppColors.scaffold_bg_color,
body: Consumer<AdvanceListProvider>(
builder: (context, provider, _) {
if (provider.isLoading && provider.advanceList.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (provider.errorMessage != null) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 16 : 20),
child: Center(
child: Text(
provider.errorMessage!,
style: TextStyle(
color: Colors.red,
fontSize: isSmallScreen ? 14 : 16,
fontFamily: "Plus Jakarta Sans",
),
textAlign: TextAlign.center,
),
),
);
}
if (provider.advanceList.isEmpty) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: isSmallScreen ? 16 : 20),
child: Center(
child: Text(
"No records found.",
style: TextStyle(
fontSize: isSmallScreen ? 14 : 16,
fontFamily: "Plus Jakarta Sans",
color: AppColors.grey_semi,
),
),
),
);
}
return ListView.builder(
controller: _scrollController,
padding: EdgeInsets.all(isSmallScreen ? 8 : isLargeScreen ? 16 : 12),
itemCount: provider.advanceList.length +
(provider.isLoading ? 1 : 0), // for pagination loader
itemBuilder: (context, index) {
if (index == provider.advanceList.length) {
return Padding(
padding: EdgeInsets.all(isSmallScreen ? 12.0 : 16.0),
child: const Center(child: CircularProgressIndicator()),
);
}
AdvanceList item = provider.advanceList[index];
return _buildAdvanceCard(item, screenWidth);
},
);
},
),
);
}
Widget _buildAdvanceCard(AdvanceList item, double screenWidth) {
final bool isSmallScreen = screenWidth < 360;
final bool isLargeScreen = screenWidth > 600;
final date = item.createdDatetime != null
? _formatDate(item.createdDatetime!)
: "-";
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(isSmallScreen ? 12 : 16)),
margin: EdgeInsets.symmetric(
vertical: isSmallScreen ? 5 : 7,
horizontal: isLargeScreen ? 4 : 0,
),
elevation: 0,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: isSmallScreen ? 12 : isLargeScreen ? 18 : 14,
vertical: isSmallScreen ? 10 : isLargeScreen ? 14 : 12,
),
child: Row(
children: [
// Circular Avatar - Responsive size
CircleAvatar(
backgroundColor: _getAvatarColor(item.type),
radius: isSmallScreen ? 18 : isLargeScreen ? 26 : 22,
child: Text(
(item.narration?.isNotEmpty == true)
? item.type![0].toUpperCase()
: "?",
style: TextStyle(
color: _getAvatarTxtColor(item.type),
fontWeight: FontWeight.bold,
fontSize: isSmallScreen ? 12 : isLargeScreen ? 16 : 14,
),
),
),
SizedBox(width: isSmallScreen ? 12 : isLargeScreen ? 16 : 14),
// Title + Subtitle
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.narration ?? "No Title",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: isSmallScreen ? 13 : isLargeScreen ? 16 : 14,
color: AppColors.semi_black,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: isSmallScreen ? 2 : 4),
Text(
date,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: isSmallScreen ? 12 : isLargeScreen ? 14 : 13,
color: AppColors.grey_semi,
),
),
],
),
),
SizedBox(width: isSmallScreen ? 8 : isLargeScreen ? 12 : 10),
// Right side amounts
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (item.issuedAmount != "₹ 0.00")
Text(
"${item.issuedAmount ?? "0"}",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: isSmallScreen ? 13 : isLargeScreen ? 16 : 14,
color: const Color(0xff1487c9),
),
textAlign: TextAlign.end,
),
if (item.deductedAmount != "₹ 0.00")
SizedBox(height: isSmallScreen ? 1 : 2),
if (item.deductedAmount != "₹ 0.00")
Text(
"-${item.deductedAmount ?? "0"}",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: isSmallScreen ? 13 : isLargeScreen ? 16 : 14,
color: AppColors.red,
),
textAlign: TextAlign.end,
),
],
),
],
),
),
);
}
String _formatDate(String dateStr) {
try {
final date = DateTime.parse(dateStr);
return DateFormat("dd MMM yy").format(date);
} catch (_) {
return dateStr;
}
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
/// Avatar color generator
Color _getAvatarColor(value) {
var color = AppColors.approved_bg_color;
switch (value) {
case 'issued':
return AppColors.requested_bg_color;
case 'received':
return AppColors.approved_bg_color;
}
return color;
}
Color _getAvatarTxtColor(value) {
var color = AppColors.approved_text_color;
switch (value) {
case 'issued':
return AppColors.requested_text_color;
case 'received':
return AppColors.approved_text_color;
}
return color;
}
String getText(value) {
switch (value) {
case 'issued':
return "I";
case 'received':
return "R";
default:
return "-";
}
}
}
\ No newline at end of file
This diff is collapsed.
...@@ -3,6 +3,8 @@ import 'package:flutter_svg/svg.dart'; ...@@ -3,6 +3,8 @@ import 'package:flutter_svg/svg.dart';
import 'package:generp/screens/hrm/Attendancelist.dart'; import 'package:generp/screens/hrm/Attendancelist.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 'CasualLeaveHistoryScreen.dart';
import 'LeaveApplicationScreen.dart'; import 'LeaveApplicationScreen.dart';
import 'TourExpensesListScreen.dart'; import 'TourExpensesListScreen.dart';
import 'RewardListScreen.dart'; import 'RewardListScreen.dart';
...@@ -24,6 +26,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -24,6 +26,8 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
"Tour Bill List", "Tour Bill List",
"Rewards List", "Rewards List",
"Attendance Request List", "Attendance Request List",
"Advance List",
"Casual Leave List"
]; ];
@override @override
...@@ -215,7 +219,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -215,7 +219,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
crossAxisCount: (constraints.maxWidth / crossAxisCount: (constraints.maxWidth /
180) 180)
.floor() .floor()
.clamp(2, 4), .clamp(2, 6),
crossAxisSpacing: 1, crossAxisSpacing: 1,
mainAxisSpacing: 2, mainAxisSpacing: 2,
childAspectRatio: 1.8, childAspectRatio: 1.8,
...@@ -224,7 +228,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -224,7 +228,7 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
final page = pages[index]; final page = pages[index];
return _buildTile( return _buildTile(
label: page.pageName ?? "", label: page.pageName ?? "",//in page number there is 6 items comming from serever it showing only four
subtitle: _getSubtitle( subtitle: _getSubtitle(
page.pageName ?? "", page.pageName ?? "",
), ),
...@@ -244,6 +248,11 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -244,6 +248,11 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
); );
}, },
), ),
////////////
SizedBox(height: 40,)
], ],
), ),
], ],
...@@ -355,6 +364,10 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -355,6 +364,10 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
return ""; return "";
case "Team Attendance Approval": case "Team Attendance Approval":
return ""; return "";
case "Advance List":
return "Advance Payment";
case "Casual Leave List":
return "Track Casual Leave";
default: default:
return ""; return "";
} }
...@@ -375,6 +388,10 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -375,6 +388,10 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
return "assets/svg/hrm/logout_ic.svg"; return "assets/svg/hrm/logout_ic.svg";
case "Team Attendance Approval": case "Team Attendance Approval":
return "assets/svg/hrm/check_ic.svg"; return "assets/svg/hrm/check_ic.svg";
case "Advance List":
return "assets/svg/hrm/advance_list_ic.svg";
case "Casual Leave List":
return "assets/svg/hrm/casual_leave_history_ic.svg";
default: default:
return "assets/svg/hrm/groupIc.svg"; return "assets/svg/hrm/groupIc.svg";
} }
...@@ -432,6 +449,24 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> { ...@@ -432,6 +449,24 @@ class _HrmdashboardScreenState extends State<HrmdashboardScreen> {
), ),
); );
break; break;
case "Advance List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AdvanceListScreen(),
),
);
break;
case "Casual Leave List":
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CasualLeaveHistoryScreen(),
),
);
break;
} }
} }
} }
...@@ -65,6 +65,6 @@ export 'package:generp/Notifiers/hrmProvider/tourExpensesDetailsProvider.dart'; ...@@ -65,6 +65,6 @@ export 'package:generp/Notifiers/hrmProvider/tourExpensesDetailsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/rewardListProvider.dart'; 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/orgprovider.dart'; export 'package:generp/Notifiers/hrmprovider/orgprovider.dart';
This diff is collapsed.
This diff is collapsed.
...@@ -181,9 +181,9 @@ const crmDashboardQuotationsUrl = "${baseUrl_test}crm_dashboard_quotations_list" ...@@ -181,9 +181,9 @@ const crmDashboardQuotationsUrl = "${baseUrl_test}crm_dashboard_quotations_list"
const ogcharturl = "${baseUrl_test}organisation_structures"; const ogcharturl = "${baseUrl_test}organisation_structures";
const JobDesciptionUrl ="${baseUrl_test}job_description"; const JobDesciptionUrl ="${baseUrl_test}job_description";
///HRM ///HRM
//Attendance //Attendance
const HrmAccessiblePagesUrl ="${baseUrl_test}hrm_accessible_pages"; const HrmAccessiblePagesUrl ="${baseUrl_test}hrm_accessible_pages";
const AttendanceRequestListUrl ="${baseUrl_test}attendance_request_list"; const AttendanceRequestListUrl ="${baseUrl_test}attendance_request_list";
const AttendanceRequestDetailsUrl ="${baseUrl_test}attendance_request_details"; const AttendanceRequestDetailsUrl ="${baseUrl_test}attendance_request_details";
...@@ -203,9 +203,9 @@ const LeaveApplicationDetailsUrl ="${baseUrl_test}leave_request_details"; ...@@ -203,9 +203,9 @@ const LeaveApplicationDetailsUrl ="${baseUrl_test}leave_request_details";
const LeaveRequestAdditionUrl ="${baseUrl_test}add_leave_request"; 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 AdvanceListUrl ="${baseUrl_test}advance_list";
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