From f9b0eb40f3c7f6af8e8d7252c9250d4e719899c8 Mon Sep 17 00:00:00 2001 From: Vaibhav Date: Sat, 12 Oct 2024 12:47:52 +0530 Subject: [PATCH] firebase setup and stock update api --- android/app/build.gradle | 15 +- android/app/google-services.json | 29 ++ android/app/src/main/AndroidManifest.xml | 69 +++-- .../MainActivity.kt | 2 +- android/build.gradle | 5 + android/settings.gradle | 4 + .../get_order_placed_controller.dart | 18 +- .../inventory_management_controller.dart | 1 + lib/controller/notification_controller.dart | 32 +++ lib/firebase_options.dart | 69 +++++ lib/main.dart | 86 ++++++ lib/models/InventoryManagementResponse.dart | 100 +++++++ lib/models/NotificationListResponse.dart | 67 +++++ lib/notification_services.dart | 165 +++++++++++ .../controller/auth_controller.dart | 8 + lib/screens/home_screen.dart | 82 +++--- .../inventory_management_screen.dart | 80 +++--- .../inventory_product_detail_screen.dart | 7 +- .../inventory/update_stock_screen.dart | 9 +- .../order_management_screen.dart | 258 ++++++++++++------ lib/screens/report/notification_screen.dart | 118 ++++++++ .../report/order_history_report_screen.dart | 191 ------------- lib/services/api_service.dart | 5 +- lib/utils/api_urls.dart | 5 +- lib/widgets/inventory_product_card.dart | 126 +++++---- 25 files changed, 1094 insertions(+), 457 deletions(-) create mode 100644 android/app/google-services.json rename android/app/src/main/kotlin/com/example/{cheminova => cheminova_rd}/MainActivity.kt (73%) create mode 100644 lib/controller/inventory_management_controller.dart create mode 100644 lib/controller/notification_controller.dart create mode 100644 lib/firebase_options.dart create mode 100644 lib/models/InventoryManagementResponse.dart create mode 100644 lib/models/NotificationListResponse.dart create mode 100644 lib/notification_services.dart create mode 100644 lib/screens/report/notification_screen.dart delete mode 100644 lib/screens/report/order_history_report_screen.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index e2ee37a..fcfd7eb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,9 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' + // END: FlutterFire Configuration id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" @@ -24,7 +28,7 @@ if (flutterVersionName == null) { } android { - namespace = "com.example.cheminova" + namespace = "com.example.cheminova_rd" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -35,7 +39,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.cheminova" + applicationId = "com.example.cheminova_rd" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdk = flutter.minSdkVersion @@ -56,3 +60,10 @@ android { flutter { source = "../.." } + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' + implementation 'androidx.activity:activity:1.8.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..eb1938e --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "181536932687", + "project_id": "cheminova-rd", + "storage_bucket": "cheminova-rd.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:181536932687:android:8f3b0977f4b4dcc56f7b3a", + "android_client_info": { + "package_name": "com.example.cheminova_rd" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyAYZUuqmSoE9MrTvOaavYYjdRoQFD-XnFc" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7c7f3d0..ae729ce 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,49 +1,62 @@ - + + + + + + + + + + + + + + - - - + android:icon="@mipmap/ic_launcher" + android:label="cheminova"> - + to determine the Window background behind the Flutter UI. + --> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> + - - + + + - + - - - - - - - - + \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/cheminova/MainActivity.kt b/android/app/src/main/kotlin/com/example/cheminova_rd/MainActivity.kt similarity index 73% rename from android/app/src/main/kotlin/com/example/cheminova/MainActivity.kt rename to android/app/src/main/kotlin/com/example/cheminova_rd/MainActivity.kt index 548a7cf..6a5d304 100644 --- a/android/app/src/main/kotlin/com/example/cheminova/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/cheminova_rd/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.cheminova +package com.example.cheminova_rd import io.flutter.embedding.android.FlutterActivity diff --git a/android/build.gradle b/android/build.gradle index d2ffbff..a054dd4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,8 @@ +buildscript { + dependencies { + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + } +} allprojects { repositories { google() diff --git a/android/settings.gradle b/android/settings.gradle index 536165d..5959482 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,6 +19,10 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "7.3.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + id "com.google.firebase.crashlytics" version "2.8.1" apply false + // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "1.7.10" apply false } diff --git a/lib/controller/get_order_placed_controller.dart b/lib/controller/get_order_placed_controller.dart index 911a9e7..44b2fe3 100644 --- a/lib/controller/get_order_placed_controller.dart +++ b/lib/controller/get_order_placed_controller.dart @@ -17,7 +17,8 @@ class GetPlacedOrderController extends GetxController { Get.put(OrderPlacedController()); final GetSingleOrderPlacedService _getSingleOrderPlacedService = GetSingleOrderPlacedService(); - var placedOrders = [].obs; + List placedOrders = [].obs; + List filterOrder = [].obs; var products = [].obs; var isLoading = false.obs; @@ -31,6 +32,8 @@ class GetPlacedOrderController extends GetxController { RxInt currentPage = 1.obs; RxBool hasMoreOrders = true.obs; + RxString productStatus = 'All'.obs; + Future getOrders() async { if (isLoading.value || !hasMoreOrders.value) return; @@ -44,7 +47,6 @@ class GetPlacedOrderController extends GetxController { if (response.statusCode == 200) { placedOrders.addAll(data.placedOrders ?? []); - debugPrint('allOrders.length: ${placedOrders.toJson()}'); currentPage++; hasMoreOrders.value = placedOrders.length < data.totalOrders!.toInt(); } else { @@ -82,6 +84,18 @@ class GetPlacedOrderController extends GetxController { } } + void updateProductStatus(dynamic newValue) { + productStatus.value = newValue; + filterOrder.clear(); + if (productStatus.value == 'All') { + filterOrder.addAll(placedOrders); + } else { + filterOrder.addAll(placedOrders + .where((order) => order.status.toString().toLowerCase() == productStatus.value.toLowerCase()) + .toList()); + } + } + // // Optional: Reset the pagination if needed // void resetPagination() { // _getOrderPlacedService.resetPagination(); diff --git a/lib/controller/inventory_management_controller.dart b/lib/controller/inventory_management_controller.dart new file mode 100644 index 0000000..bc8e008 --- /dev/null +++ b/lib/controller/inventory_management_controller.dart @@ -0,0 +1 @@ +import 'dart:convert'; import 'package:cheminova/services/api_service.dart'; import 'package:cheminova/utils/api_urls.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import '../models/InventoryManagementResponse.dart'; class InventoryManagementController extends GetxController { final apiService = ApiService(); Future getProducts() async { final response = await apiService.get(ApiUrls.inventoryManangementOrdersStock); if (response.statusCode == 200) { return InventoryManagementResponse.fromJson(response.data); }else{ return InventoryManagementResponse(); } } } \ No newline at end of file diff --git a/lib/controller/notification_controller.dart b/lib/controller/notification_controller.dart new file mode 100644 index 0000000..2c910db --- /dev/null +++ b/lib/controller/notification_controller.dart @@ -0,0 +1,32 @@ +import 'package:cheminova/services/api_service.dart'; +import 'package:get/get.dart'; +import '../models/NotificationListResponse.dart'; + +import '../utils/api_urls.dart'; + +class NotificationController extends GetxController { + final ApiService _apiClient = ApiService(); + RxList notificationsList = [].obs; + RxBool isLoading = false.obs; + + @override + void onInit() { + super.onInit(); + getNotification(); + } + + Future getNotification() async { + isLoading.value = true; + try { + final response = await _apiClient.get(ApiUrls.getNotificationUrl); + isLoading.value = false; + if (response.statusCode == 200) { + final data = NotificationListResponse.fromJson(response.data); + notificationsList.value = data.notifications ?? []; + } + } catch (e) { + isLoading.value = false; + Get.snackbar('Error', 'Failed to fetch notifications'); + } + } +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..75b157f --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,69 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyAYZUuqmSoE9MrTvOaavYYjdRoQFD-XnFc', + appId: '1:181536932687:android:8f3b0977f4b4dcc56f7b3a', + messagingSenderId: '181536932687', + projectId: 'cheminova-rd', + storageBucket: 'cheminova-rd.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyCerSkU_YMUkOhz5uM7t8pCOFi6mvkLpOw', + appId: '1:181536932687:ios:a4866cdefdcb5cf06f7b3a', + messagingSenderId: '181536932687', + projectId: 'cheminova-rd', + storageBucket: 'cheminova-rd.appspot.com', + iosBundleId: 'com.example.cheminova', + ); + +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 57040fd..5bbb7e5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,94 @@ +import 'dart:developer'; +import 'dart:io'; + import 'package:cheminova/screens/splash_screen.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get/get.dart'; +import 'firebase_options.dart'; + + + +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await Firebase.initializeApp(); + log("Handling a background message: ${message.data["data"]}"); +} + +AndroidNotificationChannel channel = const AndroidNotificationChannel( + 'High Importance Channel', 'High Importance Notifications', + importance: Importance.high); + +late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; void main()async{ + + WidgetsFlutterBinding.ensureInitialized(); + + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + FlutterError.onError = (errorDetails) { + FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails); + }; + PlatformDispatcher.instance.onError = (error, stack) { + FirebaseCrashlytics.instance.recordError(error, stack, fatal: true); + return true; + }; + + if (!kIsWeb) { + flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + await FirebaseMessaging.instance.getInitialMessage().then((message) {}); + + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + RemoteNotification? notification = message.notification; + AndroidNotification? android = message.notification?.android; + if (notification != null && android != null) { + flutterLocalNotificationsPlugin.show( + notification.hashCode, + notification.title, + notification.body, + NotificationDetails( + android: AndroidNotificationDetails(channel.id, channel.name, + icon: '@mipmap/ic_launcher', importance: Importance.max), + iOS: const DarwinNotificationDetails())); + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + const DarwinInitializationSettings darwinInitializationSettings = + DarwinInitializationSettings(); + var initSettings = const InitializationSettings( + android: initializationSettingsAndroid, + iOS: darwinInitializationSettings); + flutterLocalNotificationsPlugin.initialize(initSettings); + } + }); + + if (Platform.isAndroid) { + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(); + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + } + if (Platform.isIOS) { + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>() + ?.requestPermissions(alert: true, badge: true, sound: true); + } + + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + alert: true, badge: true, sound: true); + } runApp(const MyApp()); } diff --git a/lib/models/InventoryManagementResponse.dart b/lib/models/InventoryManagementResponse.dart new file mode 100644 index 0000000..4a9519d --- /dev/null +++ b/lib/models/InventoryManagementResponse.dart @@ -0,0 +1,100 @@ +class InventoryManagementResponse { + bool? success; + int? totalProducts; + List? products; + + InventoryManagementResponse( + {this.success, this.totalProducts, this.products}); + + InventoryManagementResponse.fromJson(Map json) { + success = json['success']; + totalProducts = json['totalProducts']; + if (json['products'] != null) { + products = []; + json['products'].forEach((v) { + products!.add(new Products.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['success'] = this.success; + data['totalProducts'] = this.totalProducts; + if (this.products != null) { + data['products'] = this.products!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Products { + String? sId; + String? sKU; + String? name; + int? price; + int? gST; + int? hSNCode; + String? description; + String? productStatus; + String? addedBy; + List? image; + String? createdAt; + String? updatedAt; + String? category; + String? brand; + int? stock; + + Products( + {this.sId, + this.sKU, + this.name, + this.price, + this.gST, + this.hSNCode, + this.description, + this.productStatus, + this.addedBy, + this.image, + this.createdAt, + this.updatedAt, + this.category, + this.brand, + this.stock}); + + Products.fromJson(Map json) { + sId = json['_id']; + sKU = json['SKU']; + name = json['name']; + price = json['price']; + gST = json['GST']; + hSNCode = json['HSN_Code']; + description = json['description']; + productStatus = json['product_Status']; + addedBy = json['addedBy']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + category = json['category']; + brand = json['brand']; + stock = json['stock']; + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['SKU'] = this.sKU; + data['name'] = this.name; + data['price'] = this.price; + data['GST'] = this.gST; + data['HSN_Code'] = this.hSNCode; + data['description'] = this.description; + data['product_Status'] = this.productStatus; + data['addedBy'] = this.addedBy; + data['createdAt'] = this.createdAt; + data['updatedAt'] = this.updatedAt; + data['category'] = this.category; + data['brand'] = this.brand; + data['stock'] = this.stock; + return data; + } +} diff --git a/lib/models/NotificationListResponse.dart b/lib/models/NotificationListResponse.dart new file mode 100644 index 0000000..90199a8 --- /dev/null +++ b/lib/models/NotificationListResponse.dart @@ -0,0 +1,67 @@ +class NotificationListResponse { + String? returnMessage; + List? notifications; + + NotificationListResponse({this.returnMessage, this.notifications}); + + NotificationListResponse.fromJson(Map json) { + returnMessage = json['return_message']; + if (json['notifications'] != null) { + notifications = []; + json['notifications'].forEach((v) { + notifications!.add(Notifications.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['return_message'] = returnMessage; + if (notifications != null) { + data['notifications'] = + notifications!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Notifications { + String? sId; + String? title; + String? msg; + String? addedFor; + String? createdAt; + String? updatedAt; + int? iV; + + Notifications( + {this.sId, + this.title, + this.msg, + this.addedFor, + this.createdAt, + this.updatedAt, + this.iV}); + + Notifications.fromJson(Map json) { + sId = json['_id']; + title = json['title']; + msg = json['msg']; + addedFor = json['added_for']; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + iV = json['__v']; + } + + Map toJson() { + final Map data = {}; + data['_id'] = sId; + data['title'] = title; + data['msg'] = msg; + data['added_for'] = addedFor; + data['createdAt'] = createdAt; + data['updatedAt'] = updatedAt; + data['__v'] = iV; + return data; + } +} diff --git a/lib/notification_services.dart b/lib/notification_services.dart new file mode 100644 index 0000000..6777ade --- /dev/null +++ b/lib/notification_services.dart @@ -0,0 +1,165 @@ +import 'dart:io'; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class NotificationServices { + //initialising firebase message plugin + FirebaseMessaging messaging = FirebaseMessaging.instance; + + //initialising firebase message plugin + final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + //function to initialise flutter local notification plugin to show notifications for android when app is active + void initLocalNotifications( + BuildContext context, RemoteMessage message) async { + var androidInitializationSettings = + const AndroidInitializationSettings('@mipmap/ic_launcher'); + var iosInitializationSettings = const DarwinInitializationSettings(); + + var initializationSetting = InitializationSettings( + android: androidInitializationSettings, iOS: iosInitializationSettings); + + await _flutterLocalNotificationsPlugin.initialize(initializationSetting, + onDidReceiveNotificationResponse: (payload) { + // handle interaction when app is active for android + handleMessage(context, message); + }); + } + + void firebaseInit(BuildContext context) { + FirebaseMessaging.onMessage.listen((message) { + RemoteNotification? notification = message.notification; + AndroidNotification? android = message.notification!.android; + + if (kDebugMode) { + print("notifications title:${notification!.title}"); + print("notifications body:${notification.body}"); + print('count:${android!.count}'); + print('data:${message.data.toString()}'); + } + + if (Platform.isIOS) { + forgroundMessage(); + } + + if (Platform.isAndroid) { + initLocalNotifications(context, message); + showNotification(message); + } + }); + } + + void requestNotificationPermission() async { + NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: true, + badge: true, + carPlay: true, + criticalAlert: true, + provisional: true, + sound: true, + ); + + if (settings.authorizationStatus == AuthorizationStatus.authorized) { + if (kDebugMode) { + print('user granted permission'); + } + } else if (settings.authorizationStatus == + AuthorizationStatus.provisional) { + if (kDebugMode) { + print('user granted provisional permission'); + } + } else { + //appsetting.AppSettings.openNotificationSettings(); + if (kDebugMode) { + print('user denied permission'); + } + } + } + + // function to show visible notification when app is active + Future showNotification(RemoteMessage message) async { + AndroidNotificationChannel channel = AndroidNotificationChannel( + message.notification!.android!.channelId.toString(), + message.notification!.android!.channelId.toString(), + importance: Importance.max, + showBadge: true, + playSound: true, + sound: const RawResourceAndroidNotificationSound('jetsons_doorbell')); + + AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails( + channel.id.toString(), channel.name.toString(), + channelDescription: 'your channel description', + importance: Importance.high, + priority: Priority.high, + playSound: true, + ticker: 'ticker', + sound: channel.sound + // sound: RawResourceAndroidNotificationSound('jetsons_doorbell') + // icon: largeIconPath + ); + + const DarwinNotificationDetails darwinNotificationDetails = + DarwinNotificationDetails( + presentAlert: true, presentBadge: true, presentSound: true); + + NotificationDetails notificationDetails = NotificationDetails( + android: androidNotificationDetails, iOS: darwinNotificationDetails); + + Future.delayed(Duration.zero, () { + _flutterLocalNotificationsPlugin.show( + 0, + message.notification!.title.toString(), + message.notification!.body.toString(), + notificationDetails, + ); + }); + } + + //function to get device token on which we will send the notifications + Future getDeviceToken() async { + String? token = await messaging.getToken(); + return token!; + } + + void isTokenRefresh() async { + messaging.onTokenRefresh.listen((event) { + event.toString(); + if (kDebugMode) { + print('refresh'); + } + }); + } + + //handle tap on notification when app is in background or terminated + Future setupInteractMessage(BuildContext context) async { + // when app is terminated + RemoteMessage? initialMessage = + await FirebaseMessaging.instance.getInitialMessage(); + + if (initialMessage != null) { + handleMessage(context, initialMessage); + } + + //when app ins background + FirebaseMessaging.onMessageOpenedApp.listen((event) { + handleMessage(context, event); + }); + } + + void handleMessage(BuildContext context, RemoteMessage message) {} + + Future forgroundMessage() async { + await FirebaseMessaging.instance + .setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + } +} diff --git a/lib/screens/authentication/controller/auth_controller.dart b/lib/screens/authentication/controller/auth_controller.dart index ab08871..77a9100 100644 --- a/lib/screens/authentication/controller/auth_controller.dart +++ b/lib/screens/authentication/controller/auth_controller.dart @@ -1,12 +1,17 @@ import 'package:cheminova/screens/authentication/controller/auth_service.dart'; import 'package:cheminova/screens/home_screen.dart'; +import 'package:cheminova/services/api_service.dart'; import 'package:cheminova/utils/show_snackbar.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../../notification_services.dart'; +import '../../../utils/api_urls.dart'; + class AuthController extends GetxController { final authService = AuthService(); + final _apiClient = ApiService(); TextEditingController emailController = TextEditingController(); TextEditingController passwordController = TextEditingController(); @@ -26,6 +31,9 @@ class AuthController extends GetxController { isLoading.value = false; update(); if (response != null) { + final fcmToken = await NotificationServices().getDeviceToken(); + print('fcmToken: $fcmToken'); + await _apiClient.post(ApiUrls.fcmUrl, data: {'fcmToken': fcmToken}); showSnackbar("Your Successfully logged In!"); Get.offAll(() => const HomeScreen()); } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 61f309b..072526e 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -3,10 +3,8 @@ import 'package:cheminova/screens/inventory/inventory_management_screen.dart'; import 'package:cheminova/screens/order/order_tracking_screen.dart'; import 'package:cheminova/screens/order_management/order_management_screen.dart'; import 'package:cheminova/screens/product/product_catalog_screen.dart'; -import 'package:cheminova/screens/report/order_history_report_screen.dart'; -import 'package:cheminova/screens/report/reporting_analytics_screen.dart'; +import 'package:cheminova/screens/report/notification_screen.dart'; import 'package:cheminova/screens/retail/retail_distributer_on_boarding_screen.dart'; -import 'package:cheminova/screens/shipping/shipping_management_screen.dart'; import 'package:cheminova/widgets/home_card.dart'; import 'package:cheminova/widgets/my_drawer.dart'; import 'package:flutter/material.dart'; @@ -29,19 +27,23 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - extendBodyBehindAppBar: true, // The app bar extends behind the body content. + extendBodyBehindAppBar: true, + // The app bar extends behind the body content. // AppBar configuration with transparent background and a custom menu icon appBar: AppBar( centerTitle: true, - backgroundColor: Colors.transparent, // AppBar background is transparent to blend with the background image. - elevation: 0, // No shadow under the AppBar. + backgroundColor: Colors.transparent, + // AppBar background is transparent to blend with the background image. + elevation: 0, + // No shadow under the AppBar. // Leading widget is a custom menu icon that opens the drawer when tapped. leading: Builder( builder: (context) { return GestureDetector( - onTap: () => Scaffold.of(context).openDrawer(), // Opens the drawer. + onTap: () => Scaffold.of(context).openDrawer(), + // Opens the drawer. child: Padding( padding: const EdgeInsets.all(16.0), child: SvgPicture.asset( @@ -79,30 +81,34 @@ class _HomeScreenState extends State { // SingleChildScrollView allows scrolling if the content is larger than the screen. child: SingleChildScrollView( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Evenly spaces the rows of cards. + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // Evenly spaces the rows of cards. children: [ // Row with two HomeCard widgets for Product Catalogue and Order Tracking. Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Cards are spaced evenly across the row. + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // Cards are spaced evenly across the row. children: [ // HomeCard widget with the title and navigation to the Product Catalog screen. HomeCard( - title: 'Product Catalogue', - onTap: () => - Get.to(() => const ProductCatalogScreen()), // Navigates to ProductCatalogScreen using GetX. + title: 'Products', + onTap: () => Get.to(() => + const ProductCatalogScreen()), // Navigates to ProductCatalogScreen using GetX. ), // HomeCard widget with the title and navigation to the Order Tracking screen. HomeCard( title: 'Order Tracking', onTap: () => Get.to( - () => const OrderTrackingScreen(), // Navigates to OrderTrackingScreen using GetX. + () => + const OrderTrackingScreen(), // Navigates to OrderTrackingScreen using GetX. ), ), ], ), - const SizedBox(height: 10), // Adds vertical spacing between rows. + const SizedBox(height: 30), + // Adds vertical spacing between rows. // Row with two HomeCard widgets for Order Management and Shipping Management. Row( @@ -111,55 +117,41 @@ class _HomeScreenState extends State { HomeCard( title: 'Order Management', onTap: () => Get.to( - () => OrderManagementScreen(), // Navigates to OrderManagementScreen. + () => + OrderManagementScreen(), // Navigates to OrderManagementScreen. ), ), - HomeCard( - title: 'Shipping Management', - onTap: () => Get.to( - () => const ShippingManagementScreen(), // Navigates to ShippingManagementScreen. - ), - ), - ], - ), - - const SizedBox(height: 10), - - // Row with two HomeCard widgets for Inventory Management and Reporting & Analytics. - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ HomeCard( title: 'Inventory Management', onTap: () => Get.to( - () => const InventoryManagementScreen(), // Navigates to InventoryManagementScreen. - ), - ), - HomeCard( - title: 'Reporting & Analytics', - onTap: () => Get.to( - () => const ReportingAnalyticsScreen(), // Navigates to ReportingAnalyticsScreen. + () => + const InventoryManagementScreen(), // Navigates to InventoryManagementScreen. ), ), + // HomeCard( + // title: 'Shipping Management', + // onTap: () => Get.to( + // () => + // const ShippingManagementScreen(), // Navigates to ShippingManagementScreen. + // ), + // ), ], ), - const SizedBox(height: 10), + const SizedBox(height: 30), // Row with two HomeCard widgets for Order Data Export and Retail Distributors Onboarding. Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ HomeCard( - title: 'Order Data Export', - onTap: () => Get.to( - () => const OrderHistoryReportScreen(), // Navigates to OrderHistoryReportScreen. - ), - ), + title: 'Notifications', + onTap: () => Get.to(() => NotificationScreen())), HomeCard( - title: 'Retail Distributors Onboarding', + title: 'Announcement', onTap: () => Get.to( - () => const RetailDistributerOnBoardingScreen(), // Navigates to RetailDistributerOnBoardingScreen. + () => + const RetailDistributerOnBoardingScreen(), // Navigates to RetailDistributerOnBoardingScreen. ), ), ], diff --git a/lib/screens/inventory/inventory_management_screen.dart b/lib/screens/inventory/inventory_management_screen.dart index 9d4e385..60c5372 100644 --- a/lib/screens/inventory/inventory_management_screen.dart +++ b/lib/screens/inventory/inventory_management_screen.dart @@ -1,10 +1,11 @@ -import 'package:cheminova/models/product_model.dart'; import 'package:cheminova/widgets/inventory_product_card.dart'; -import 'package:cheminova/widgets/my_drawer.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:cheminova/widgets/my_drawer.dart'; +import '../../controller/inventory_management_controller.dart'; +import '../../models/InventoryManagementResponse.dart'; class InventoryManagementScreen extends StatefulWidget { const InventoryManagementScreen({super.key}); @@ -15,22 +16,23 @@ class InventoryManagementScreen extends StatefulWidget { } class _InventoryManagementScreenState extends State { - final List _productList = [ - ProductModel( - id: '1', - name: 'Product 1', - price: 100, - quantity: 100, - description: 'Description 1', - category: ProductCategory.food, - image: 'assets/images/product.png', - ) - ]; + late Future _productsFuture; + + final InventoryManagementController _controller = + Get.put(InventoryManagementController()); + final List _filterList = [ "Category", "Price Range", "Availability", ]; + + @override + void initState() { + super.initState(); + _productsFuture = _controller.getProducts(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -45,9 +47,7 @@ class _InventoryManagementScreenState extends State { onTap: () => Scaffold.of(context).openDrawer(), child: Padding( padding: const EdgeInsets.all(16.0), - child: SvgPicture.asset( - 'assets/svg/menu.svg', - ), + child: SvgPicture.asset('assets/svg/menu.svg'), ), ); }, @@ -57,15 +57,11 @@ class _InventoryManagementScreenState extends State { onTap: () => Get.back(), child: Padding( padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - 'assets/svg/back_arrow.svg', - ), + child: SvgPicture.asset('assets/svg/back_arrow.svg'), ), ), ], - title: const Text( - "Inventory Management", - ), + title: const Text("Inventory Management"), ), drawer: const MyDrawer(), body: Stack( @@ -78,9 +74,7 @@ class _InventoryManagementScreenState extends State { SafeArea( child: Column( children: [ - SizedBox( - height: Get.height * 0.02, - ), + SizedBox(height: Get.height * 0.02), Card( margin: const EdgeInsets.symmetric(horizontal: 18), shape: RoundedRectangleBorder( @@ -116,16 +110,32 @@ class _InventoryManagementScreenState extends State { ), SizedBox( height: Get.height * 0.6, - child: ListView.builder( - padding: EdgeInsets.zero, - shrinkWrap: true, - itemCount: 10, - itemBuilder: (context, index) => Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: InventoryProductCard( - product: _productList[0], - ), - ), + child: FutureBuilder( + future: _productsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center( + child: Text('Error: ${snapshot.error}')); + } else if (snapshot.hasData) { + final products = snapshot.data!.products ?? []; + return ListView.builder( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: products.length, + itemBuilder: (context, index) { + final data = products[index]; + return InventoryProductCard(product: data); + }, + ); + } else { + return const Center( + child: Text('No products found')); + } + }, ), ) ], diff --git a/lib/screens/inventory/inventory_product_detail_screen.dart b/lib/screens/inventory/inventory_product_detail_screen.dart index 2d59cff..bee9af9 100644 --- a/lib/screens/inventory/inventory_product_detail_screen.dart +++ b/lib/screens/inventory/inventory_product_detail_screen.dart @@ -1,3 +1,4 @@ +import 'package:cheminova/models/InventoryManagementResponse.dart'; import 'package:cheminova/models/product_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; @@ -5,7 +6,7 @@ import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; class InventoryProductDetailScreen extends StatefulWidget { - final ProductModel product; + final Products product; const InventoryProductDetailScreen({ super.key, @@ -95,7 +96,7 @@ class _InventoryProductDetailScreenState child: ClipRRect( borderRadius: BorderRadius.circular(10), child: Image.asset( - widget.product.image, + 'assets/images/product.png', fit: BoxFit.cover, ), ), @@ -159,7 +160,7 @@ class _InventoryProductDetailScreenState padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), child: Text( - "Current Stock: ${widget.product.quantity}", + "Current Stock: ${widget.product.stock}", style: GoogleFonts.roboto( fontSize: Get.width * 0.04, fontWeight: FontWeight.w400, diff --git a/lib/screens/inventory/update_stock_screen.dart b/lib/screens/inventory/update_stock_screen.dart index 2e0f0ba..6fdb9c1 100644 --- a/lib/screens/inventory/update_stock_screen.dart +++ b/lib/screens/inventory/update_stock_screen.dart @@ -1,3 +1,4 @@ +import 'package:cheminova/models/InventoryManagementResponse.dart'; import 'package:cheminova/models/product_model.dart'; import 'package:cheminova/widgets/input_field.dart'; import 'package:flutter/material.dart'; @@ -6,7 +7,7 @@ import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; class UpdateStockScreen extends StatefulWidget { - final ProductModel product; + final Products product; const UpdateStockScreen({ super.key, @@ -48,7 +49,7 @@ class _UpdateStockScreenState extends State { ), ], title: const Text( - "Product Detail", + "Product update", ), ), body: Stack( @@ -95,7 +96,7 @@ class _UpdateStockScreenState extends State { child: ClipRRect( borderRadius: BorderRadius.circular(10), child: Image.asset( - widget.product.image, + 'assets/images/product.png', fit: BoxFit.cover, ), ), @@ -123,7 +124,7 @@ class _UpdateStockScreenState extends State { child: Padding( padding: const EdgeInsets.all(12), child: Text( - "Current Stock: ${widget.product.quantity}", + "Current Stock: ${widget.product.stock}", style: GoogleFonts.roboto( fontSize: Get.width * 0.04, fontWeight: FontWeight.w700, diff --git a/lib/screens/order_management/order_management_screen.dart b/lib/screens/order_management/order_management_screen.dart index 56dbfc7..3cf62b8 100644 --- a/lib/screens/order_management/order_management_screen.dart +++ b/lib/screens/order_management/order_management_screen.dart @@ -7,14 +7,15 @@ import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; + import '../../controller/cart_controller.dart'; import '../../controller/get_order_placed_controller.dart'; import '../../models/product_model1.dart'; - class OrderManagementScreen extends StatefulWidget { final Product? productModel; PlacedOrderList? placeOrder; + OrderManagementScreen({super.key, this.productModel, this.placeOrder}); @override @@ -25,10 +26,11 @@ class _OrderManagementScreenState extends State { final _searchController = TextEditingController(); final List _filterList = ["Order Status", "Date Range"]; - final GetPlacedOrderController _getPlacedOrderController = Get.put(GetPlacedOrderController()); + final GetPlacedOrderController _getPlacedOrderController = + Get.put(GetPlacedOrderController()); final CartController _cartController = Get.put(CartController()); final GlobalKey _refreshIndicatorKey = - GlobalKey(); + GlobalKey(); @override void initState() { @@ -38,7 +40,7 @@ class _OrderManagementScreenState extends State { Future _onRefresh() async { await getOrder1(); - await Future.delayed(Duration(seconds: 1)); + await Future.delayed(const Duration(seconds: 1)); } Future getOrder1() async { @@ -96,13 +98,14 @@ class _OrderManagementScreenState extends State { Image.asset('assets/images/image_1.png', fit: BoxFit.cover), SafeArea( child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom), child: RefreshIndicator( - key: _refreshIndicatorKey, - onRefresh: _onRefresh, - color: Colors.black, - backgroundColor: Colors.white, + key: _refreshIndicatorKey, + onRefresh: _onRefresh, + color: Colors.black, + backgroundColor: Colors.white, child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -125,20 +128,36 @@ class _OrderManagementScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - height: Get.height * 0.05, - child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: _filterList.length, - itemBuilder: (context, index) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Chip( - label: Text( - _filterList[index], - style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.w500), - ), - ), + Obx( + () => Container( + height: Get.height * 0.05, + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(5)), + child: DropdownButton( + value: _getPlacedOrderController + .productStatus.value, + onChanged: (dynamic newValue) { + if (newValue != null) { + _getPlacedOrderController + .updateProductStatus(newValue); + } + }, + items: [ + 'All', + 'Delivered', + 'Processing', + 'Cancelled' + ].map>( + (String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), ), ), ), @@ -147,10 +166,19 @@ class _OrderManagementScreenState extends State { child: Obx(() { // Use a set to keep track of unique order IDs final Set uniqueOrderIds = {}; - final List uniqueOrders = []; + final List uniqueOrders = + []; - for (var order in _getPlacedOrderController.placedOrders) { - if (uniqueOrderIds.add(order.sId??'')) { + final orders = _getPlacedOrderController + .filterOrder.isNotEmpty + ? _getPlacedOrderController + .filterOrder + : _getPlacedOrderController + .placedOrders; + + for (var order in orders) { + if (uniqueOrderIds + .add(order.sId ?? '')) { uniqueOrders.add(order); } } @@ -164,48 +192,85 @@ class _OrderManagementScreenState extends State { // Combine product names into a single string final productNames = order.orderItem! - .map((item) => capitalizeFirstLetter(item.name??'')) + .map((item) => + capitalizeFirstLetter( + item.name ?? '')) .join(', '); return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.symmetric( + vertical: 8), child: Card( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 8, 0), + padding: const EdgeInsets + .fromLTRB(16, 8, 8, 0), child: Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment + .start, children: [ - Text("Order ID: ", style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.bold)), - Text("${order.uniqueId}") + Text("Order ID: ", + style: GoogleFonts.roboto( + fontSize: 14, + fontWeight: + FontWeight + .bold)), + Text( + "${order.uniqueId}") ], ), ), Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 8, 0), + padding: const EdgeInsets + .fromLTRB(16, 8, 8, 0), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, // Aligns the Column to the top of the Text + crossAxisAlignment: + CrossAxisAlignment + .start, + // Aligns the Column to the top of the Text children: [ Text( "Product Names: ", - style: GoogleFonts.roboto( + style: GoogleFonts + .roboto( fontSize: 14, - fontWeight: FontWeight.bold, + fontWeight: + FontWeight.bold, ), ), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, // Aligns text to the right within the Column + crossAxisAlignment: + CrossAxisAlignment + .start, + // Aligns text to the right within the Column children: [ - const SizedBox(height: 4), // Adds a small space between the label and the product names - for (int i = 0; i < productNames.split(",").length; i++) + const SizedBox( + height: 4), + // Adds a small space between the label and the product names + for (int i = 0; + i < + productNames + .split( + ",") + .length; + i++) Text( - '${i + 1}. ${productNames.split(",")[i].trim()}', // Adds index and trims whitespace - textAlign: TextAlign.left, // Aligns text to the right - style: GoogleFonts.roboto( - fontSize: 14, + '${i + 1}. ${productNames.split(",")[i].trim()}', + // Adds index and trims whitespace + textAlign: + TextAlign + .left, + // Aligns text to the right + style: + GoogleFonts + .roboto( + fontSize: + 14, ), ), ], @@ -214,42 +279,76 @@ class _OrderManagementScreenState extends State { ], ), ), - - Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 8, 0), + padding: const EdgeInsets + .fromLTRB(16, 8, 8, 0), child: Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment + .start, children: [ - Text("Order Date: ", style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.bold)), - Text(formatDate("${order.createdAt}")) + Text("Order Date: ", + style: GoogleFonts.roboto( + fontSize: 14, + fontWeight: + FontWeight + .bold)), + Text(formatDate( + "${order.createdAt}")) ], ), ), Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 8, 8), + padding: const EdgeInsets + .fromLTRB(16, 8, 8, 8), child: Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment + .start, children: [ - Text("Status: ", style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.bold)), - Text(capitalizeFirstLetter("${order.status}")) + Text("Status: ", + style: GoogleFonts.roboto( + fontSize: 14, + fontWeight: + FontWeight + .bold)), + Text(capitalizeFirstLetter( + "${order.status}")) ], ), ), SizedBox( width: Get.width * 0.4, child: Padding( - padding: const EdgeInsets.all(8.0), + padding: + const EdgeInsets.all( + 8.0), child: ElevatedButton( - onPressed: () => Get.to(() => OrderManagementDetailScreen( - placedOrderList: uniqueOrders[index])), - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: const Color(0xFF004791), + onPressed: () => Get.to(() => + OrderManagementDetailScreen( + placedOrderList: + uniqueOrders[ + index])), + style: ElevatedButton + .styleFrom( + foregroundColor: + Colors.white, + backgroundColor: + const Color( + 0xFF004791), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)), + borderRadius: + BorderRadius + .circular( + 10)), ), - child: Text("View Details", style: GoogleFonts.roboto(fontSize: 14, fontWeight: FontWeight.w400)), + child: Text( + "View Details", + style: GoogleFonts.roboto( + fontSize: 14, + fontWeight: + FontWeight + .w400)), ), ), ) @@ -274,31 +373,24 @@ class _OrderManagementScreenState extends State { ], ), ), - Obx(() { - if (_getPlacedOrderController.isLoading.value) { - return Container( - color: Colors.black.withOpacity(0.5), - child: const Center( - child: CircularProgressIndicator(strokeWidth: 1), - ), - ); - } - return const SizedBox.shrink(); - },) + Obx( + () { + if (_getPlacedOrderController.isLoading.value) { + return Container( + color: Colors.black.withOpacity(0.5), + child: const Center( + child: CircularProgressIndicator(strokeWidth: 1), + ), + ); + } + return const SizedBox.shrink(); + }, + ) ], ); } } - - - - - - - - - // import 'package:cheminova/controller/get_place_order_service.dart'; // import 'package:cheminova/controller/place_order_controller.dart'; // import 'package:cheminova/models/place_order_list_model.dart'; diff --git a/lib/screens/report/notification_screen.dart b/lib/screens/report/notification_screen.dart new file mode 100644 index 0000000..68e7e5c --- /dev/null +++ b/lib/screens/report/notification_screen.dart @@ -0,0 +1,118 @@ +import 'package:cheminova/widgets/my_drawer.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import '../../controller/notification_controller.dart'; +import '../../models/NotificationListResponse.dart'; +import '../../widgets/comman_background.dart'; +import '../../widgets/common_appbar.dart'; +import '../../widgets/common_elevated_button.dart'; + +class NotificationScreen extends StatelessWidget { + final NotificationController controller = Get.put(NotificationController()); + + NotificationScreen({super.key}); + + @override + Widget build(BuildContext context) { + return CommonBackground( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: CommonAppBar( + title: const Text('Notification', + style: TextStyle( + fontSize: 20, + color: Colors.black, + fontWeight: FontWeight.w400, + fontFamily: 'Anek')), + backgroundColor: Colors.transparent, + elevation: 0, + actions: [ + IconButton( + onPressed: () => Navigator.pop(context), + icon: SvgPicture.asset('assets/svg/back_arrow.svg'), + padding: const EdgeInsets.only(right: 20)) + ], + ), + drawer: const MyDrawer(), + body: Obx(() => controller.isLoading.value + ? const Center(child: CircularProgressIndicator()) + : MyListView(notificationList: controller.notificationsList)), + ), + ); + } +} + +Widget buildProductButton(String productName) { + return Padding( + padding: const EdgeInsets.only(bottom: 15), + child: CommonElevatedButton( + borderRadius: 30, + width: double.infinity, + height: kToolbarHeight - 10, + text: productName, + backgroundColor: const Color(0xff004791), + onPressed: () { + debugPrint('$productName pressed'); + }, + ), + ); +} + +class MyListView extends StatelessWidget { + final List notificationList; + + const MyListView({super.key, required this.notificationList}); + + @override + Widget build(BuildContext context) { + Map> groupedNotifications = {}; + + for (var notification in notificationList) { + String date = DateFormat("dd MMM yyyy").format(DateTime.parse(notification.createdAt ?? '')); + if (!groupedNotifications.containsKey(date)) { + groupedNotifications[date] = []; + } + groupedNotifications[date]!.add(notification); + } + + return ListView.builder( + padding: const EdgeInsets.only(top: 15), + itemCount: groupedNotifications.length, + itemBuilder: (context, index) { + String date = groupedNotifications.keys.elementAt(index); + List notificationsForDate = groupedNotifications[date]!; + + return Padding( + padding: const EdgeInsets.only(bottom: 10, left: 10, right: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + date, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ...notificationsForDate.map((item) => Padding( + padding: const EdgeInsets.only(bottom: 10), + child: ExpansionTile( + collapsedBackgroundColor: Colors.white, + backgroundColor: Colors.white, + trailing: const SizedBox.shrink(), + title: Text( + item.title ?? '', + style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + ), + subtitle: Text(item.msg ?? ''), + ), + )), + ], + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/screens/report/order_history_report_screen.dart b/lib/screens/report/order_history_report_screen.dart deleted file mode 100644 index c146e9c..0000000 --- a/lib/screens/report/order_history_report_screen.dart +++ /dev/null @@ -1,191 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:get/get.dart'; -import 'package:google_fonts/google_fonts.dart'; - -class OrderHistoryReportScreen extends StatefulWidget { - const OrderHistoryReportScreen({super.key}); - - @override - State createState() => - _OrderHistoryReportScreenState(); -} - -class _OrderHistoryReportScreenState extends State { - final List _filterList = [ - "Order Status", - "Date Range", - ]; - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - appBar: AppBar( - centerTitle: true, - backgroundColor: Colors.transparent, - elevation: 0, - leading: GestureDetector( - onTap: () {}, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SvgPicture.asset( - 'assets/svg/menu.svg', - ), - ), - ), - actions: [ - GestureDetector( - onTap: () => Get.back(), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - 'assets/svg/back_arrow.svg', - ), - ), - ), - ], - title: const Text( - "Order History Report", - ), - ), - body: Stack( - fit: StackFit.expand, - children: [ - Image.asset( - 'assets/images/image_1.png', - fit: BoxFit.cover, - ), - SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox(height: Get.height * 0.02), - Card( - margin: const EdgeInsets.symmetric(horizontal: 18), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(19), - side: const BorderSide(color: Color(0xFFFDFDFD)), - ), - color: const Color(0xFFB4D1E5).withOpacity(0.9), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: Get.height * 0.05, - child: ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: _filterList.length, - itemBuilder: (context, index) => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 4), - child: Chip( - label: Text( - _filterList[index], - style: GoogleFonts.roboto( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ), - ), - SizedBox(height: Get.height * 0.02), - SizedBox( - width: Get.width, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - height: Get.height * 0.4, - width: Get.width * 0.7, - decoration: BoxDecoration( - border: Border.all( - width: 4, - color: Colors.white, - ), - borderRadius: BorderRadius.circular(15), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Image.asset( - "assets/images/chart.png", - fit: BoxFit.cover, - ), - ), - ), - ), - ), - SizedBox(height: Get.height * 0.02), - SizedBox( - width: Get.width, - child: Card( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: - const EdgeInsets.fromLTRB(16, 8, 8, 0), - child: Text( - "Report Title: Sales Data Report", - style: GoogleFonts.roboto( - fontSize: 14, - fontWeight: FontWeight.w400, - ), - ), - ), - Padding( - padding: - const EdgeInsets.fromLTRB(16, 8, 8, 8), - child: Text( - "Description: Overview of sales data", - style: GoogleFonts.roboto( - fontSize: 14, - fontWeight: FontWeight.w400, - ), - ), - ), - SizedBox( - width: Get.width * 0.5, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () {}, - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: - const Color(0xFF004791), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(10), - ), - ), - child: Text( - "Generate Report", - style: GoogleFonts.roboto( - fontSize: 14, - fontWeight: FontWeight.w400, - ), - ), - ), - ), - ) - ], - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index f07c320..d727f01 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -28,4 +28,7 @@ class ApiService { data: {'message': 'An unexpected error occurred'}); } } -} + + Future post(String path, {dynamic data}) { + return _dio.post(path, data: data); // Send POST request + }} \ No newline at end of file diff --git a/lib/utils/api_urls.dart b/lib/utils/api_urls.dart index 336c6bf..a35af11 100644 --- a/lib/utils/api_urls.dart +++ b/lib/utils/api_urls.dart @@ -5,14 +5,15 @@ class ApiUrls { static const String profileUrl = '/api/rd-get-me'; static const String forgetPasswordUrl = '/api/v1/user/password/forgot'; static const String changePasswordUrl = '/api/v1/user/password/update'; - static const String fcmUrl = '/api/v1/user/fcm-token'; + static const String fcmUrl = '/api/rd-save-fcm-token'; static const String getCategoryUrl = '/api/category/getCategories'; static const String getProductUrl = '/api/category/getCategories'; static const String getProductManualUrl = '/api/productmanual/getall'; static const String getKycUrl = '/api/kyc/getAll'; - static const String getNotificationUrl = '/api/get-notification-pd'; + static const String getNotificationUrl = '/api/get-notification-rd'; static const String getPlacedOrderUrl ='/api/get-placed-order-pd'; static const String getSinglePlacedOrderUrl ='/api/get-single-placed-order-pd'; static const String placedOrderUrl ='${baseUrl}/api/order-place'; + static const String inventoryManangementOrdersStock ='${baseUrl}/api/stock'; } diff --git a/lib/widgets/inventory_product_card.dart b/lib/widgets/inventory_product_card.dart index 73e337b..5369f10 100644 --- a/lib/widgets/inventory_product_card.dart +++ b/lib/widgets/inventory_product_card.dart @@ -1,3 +1,4 @@ +import 'package:cheminova/models/InventoryManagementResponse.dart'; import 'package:cheminova/models/product_model.dart'; import 'package:cheminova/screens/inventory/inventory_product_detail_screen.dart'; import 'package:cheminova/screens/inventory/update_stock_screen.dart'; @@ -6,7 +7,8 @@ import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; class InventoryProductCard extends StatelessWidget { - final ProductModel product; + final Products product; + const InventoryProductCard({ super.key, required this.product, @@ -26,7 +28,7 @@ class InventoryProductCard extends StatelessWidget { width: Get.width * 0.30, decoration: BoxDecoration( image: DecorationImage( - image: Image.asset(product.image).image, + image: Image.asset('assets/images/product.png').image, fit: BoxFit.cover, ), ), @@ -36,70 +38,74 @@ class InventoryProductCard extends StatelessWidget { const SizedBox( width: 10, ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - product.name, - style: GoogleFonts.roboto( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - Text( - "Stock: ${product.quantity}", - style: GoogleFonts.roboto( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ElevatedButton( - onPressed: () => Get.to( - () => InventoryProductDetailScreen( - product: product, - ), - ), - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: const Color(0xFF004791), - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: Text( - "Details", + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name ?? '', + maxLines: 2, + overflow: TextOverflow.ellipsis, style: GoogleFonts.roboto( - fontSize: 14, - fontWeight: FontWeight.w600, + fontSize: 16, + fontWeight: FontWeight.w500, ), ), - ), - ElevatedButton( - onPressed: () => Get.to( - () => UpdateStockScreen( - product: product, - ), - ), - style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: const Color(0xFF00784C), - padding: - const EdgeInsets.symmetric(horizontal: 18, vertical: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - child: Text( - "Update", + Text( + "Stock: ${product.stock ?? 0}", style: GoogleFonts.roboto( - fontSize: 14, - fontWeight: FontWeight.w600, + fontSize: 16, + fontWeight: FontWeight.w500, ), ), - ), - ], + ElevatedButton( + onPressed: () => Get.to( + () => InventoryProductDetailScreen( + product: product, + ), + ), + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: const Color(0xFF004791), + padding: + const EdgeInsets.symmetric(horizontal: 18, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Text( + "Details", + style: GoogleFonts.roboto( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ElevatedButton( + onPressed: () => Get.to( + () => UpdateStockScreen( + product: product, + ), + ), + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: const Color(0xFF00784C), + padding: + const EdgeInsets.symmetric(horizontal: 18, vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: Text( + "Update", + style: GoogleFonts.roboto( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), ) ], ),