1)Push notification added

This commit is contained in:
saritabirare 2024-09-20 15:38:21 +05:30
parent e40371ad6a
commit 410c8903ae
24 changed files with 646 additions and 85 deletions

View File

@ -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
}

View File

@ -0,0 +1,36 @@
import 'package:cheminova/models/notification_model.dart';
import 'package:cheminova/utils/api_urls.dart';
import '../utils/common_api_service.dart';
class NotificationApiService {
Future<List<NotificationModel>?> getNotification(String token) async {
try {
String url =ApiUrls.getNotificationUrl; // Base URL to fetch product manuals
final response = await commonApiService<List<NotificationModel>>(
method: "GET",
url: url,
additionalHeaders: { // Pass the token here
'Authorization': 'Bearer $token',
},
fromJson: (json) {
if (json['notifications'] != null) {
// Map the list of product manuals from the response
final List<NotificationModel> notification = (json['notifications'] as List)
.map((manualJson) => NotificationModel.fromJson(manualJson as Map<String, dynamic>))
.toList();
return notification;
} else {
return [];
}
},
);
return response;
} catch (e) {
print(e.toString());
return null;
}
}
}

View File

@ -0,0 +1,50 @@
import 'package:cheminova/controller/notification_api_service.dart';
import 'package:cheminova/controller/product_mannual_service.dart';
import 'package:cheminova/models/notification_model.dart';
import 'package:cheminova/notification_service.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/product_mannual_model.dart'; // Your model import
// Your service import
class NotificationController extends GetxController {
var notificationList = <NotificationModel>[].obs;
// Service to fetch data
final NotificationApiService notificationApiService = NotificationApiService();
// Loading state
var isLoading = false.obs;
// Method to fetch product manuals from the service
void fetchNotificationApiService() async {
try {
// Set loading to true
isLoading.value = true;
SharedPreferences prefs = await SharedPreferences.getInstance();
String? token = prefs.getString('token');
var manuals = await notificationApiService.getNotification(token!);
// If data is returned, update the list
if (manuals != null) {
notificationList .value = manuals;
} else {
notificationList.value = []; // If no data, set an empty list
}
} catch (e) {
// Handle error here, for example logging or showing an error message
print("Error fetching product manuals: $e");
} finally {
// Set loading to false
isLoading.value = false;
}
}
@override
void onInit() {
// Fetch product manuals when the controller is initialized
fetchNotificationApiService();
super.onInit();
}
}

View File

@ -1,12 +1,14 @@
import 'package:cheminova/utils/api_urls.dart';
import '../models/product_mannual_model.dart';
import '../utils/common_api_service.dart'; // Replace with your actual common API service import
class ProductMannualService {
Future<List<ProductManualModel>?> getProductManuals(String token) async {
try {
String url = "/api/productmanual/getall"; // Base URL to fetch product manuals
String url = ApiUrls.getProductManualUrl; // Base URL to fetch product manuals
final response = await commonApiService<List<ProductManualModel>>(
method: "GET",

View File

@ -1,3 +1,5 @@
import 'package:cheminova/utils/api_urls.dart';
import '../models/product_model1.dart';
import '../utils/common_api_service.dart';
import '../utils/show_snackbar.dart';
@ -7,7 +9,7 @@ class ProductService {
try {
String url;
if (category != null && category.isNotEmpty) {
url = "/api/product/getAll/user?page=$page&category=$category";
url = "ApiUrls.getProductUrl?page=$page&category=$category";
} else {
url = "/api/product/getAll/user?page=$page"; // URL without category filter
}
@ -39,7 +41,7 @@ class ProductService {
try {
final response = await commonApiService<List<Map<String, dynamic>>>(
method: "GET",
url: "/api/category/getCategories",
url: ApiUrls.getCategoryUrl,
fromJson: (json) {
if (json['categories'] != null) {
final List<Map<String, dynamic>> category = (json['categories'] as List)

View File

@ -1,8 +1,23 @@
import 'package:cheminova/screens/splash_screen.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive_flutter/adapters.dart';
import 'firebase_options.dart';
var box;
void main()async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await Hive.initFlutter();
await Hive.openBox('cartBox');
runApp(const MyApp());
}

View File

@ -0,0 +1,67 @@
class NotificationModel {
String? id;
String? title;
String? msg;
String? addedFor;
String? createdAt;
String? updatedAt;
int? v;
NotificationModel({
this.id,
this.title,
this.msg,
this.addedFor,
this.createdAt,
this.updatedAt,
this.v,
});
factory NotificationModel.fromJson(Map<String, dynamic> json) {
return NotificationModel(
id: json['_id'],
title: json['title'],
msg: json['msg'],
addedFor: json['added_for'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
v: json['__v'],
);
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'title': title,
'msg': msg,
'added_for': addedFor,
'createdAt': createdAt,
'updatedAt': updatedAt,
'__v': v,
};
}
}
class NotificationResponse {
String? returnMessage;
List<NotificationModel>? notifications;
NotificationResponse({this.returnMessage, this.notifications});
factory NotificationResponse.fromJson(Map<String, dynamic> json) {
return NotificationResponse(
returnMessage: json['return_message'],
notifications: json['notifications'] != null
? List<NotificationModel>.from(json['notifications']
.map((notification) => NotificationModel.fromJson(notification)))
: null,
);
}
Map<String, dynamic> toJson() {
return {
'return_message': returnMessage,
'notifications': notifications?.map((notification) => notification.toJson()).toList(),
};
}
}

View File

@ -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<void> 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<String> 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<void> 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,
);
}
}

View File

@ -1,8 +1,11 @@
import 'package:cheminova/controller/home_controller.dart';
import 'package:cheminova/screens/inventory/inventory_management_screen.dart';
import 'package:cheminova/screens/kyc/kyc_screen.dart';
import 'package:cheminova/screens/notification/notification_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/product/product_mannual.dart';
import 'package:cheminova/screens/report/order_history_report_screen.dart';
import 'package:cheminova/screens/report/reporting_analytics_screen.dart';
import 'package:cheminova/screens/retail/retail_distributer_on_boarding_screen.dart';
@ -13,6 +16,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'kyc/kyc_retailer_info_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@ -46,8 +51,23 @@ class _HomeScreenState extends State<HomeScreen> {
title: const Text(
"Welcome",
),
actions: [
GestureDetector(
onTap: () {
// Action for notification icon tap
Get.to(() => NotificationScreen());
},
child: Padding(
padding: const EdgeInsets.only(right: 16.0), // Add padding to align with the AppBar edges
child: const Icon(
Icons.notifications, // Notification icon
color: Colors.white, // Icon color (customize as needed)
),
drawer: const MyDrawer(),
),
),
],
),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [
@ -59,6 +79,8 @@ class _HomeScreenState extends State<HomeScreen> {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(left: 15.0,),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
@ -112,6 +134,7 @@ class _HomeScreenState extends State<HomeScreen> {
() => const ReportingAnalyticsScreen(),
),
),
],
),
const SizedBox(height: 10),
@ -131,9 +154,32 @@ class _HomeScreenState extends State<HomeScreen> {
),
),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
HomeCard(
title: 'Product Mannual',
onTap: () => Get.to(
() => const ProductsManualScreen(),
),
),
HomeCard(
title: 'Kyc',
onTap: () => Get.to(
() => KycRetailerInfoScreen(),
),
),
],
),
],
),
),
),
),
)

View File

@ -67,7 +67,7 @@ class _InventoryManagementScreenState extends State<InventoryManagementScreen> {
"Inventory Management",
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -0,0 +1,131 @@
import 'package:cheminova/controller/notification_controller.dart';
import 'package:cheminova/models/notification_model.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:intl/intl.dart';
import '../../widgets/comman_background.dart';
import '../../widgets/common_appbar.dart';
class NotificationScreen extends StatelessWidget {
NotificationScreen({super.key});
// Initialize the controller
final NotificationController notificationController = Get.put(NotificationController());
@override
Widget build(BuildContext context) {
// Fetch notifications when the screen is first built
notificationController.fetchNotificationApiService();
return CommonBackground(
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: CommonAppBar(
actions: [
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: SvgPicture.asset('assets/svg/back_arrow.svg'),
padding: const EdgeInsets.only(right: 20),
),
],
title: const Text('Notification',
style: TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Anek')),
backgroundColor: Colors.transparent,
elevation: 0,
),
drawer: MyDrawer(),
body: Obx(() {
// Show a loading indicator while data is being fetched
if (notificationController.isLoading.value) {
return const Center(child: CircularProgressIndicator());
} else if (notificationController.notificationList.isEmpty) {
// Handle empty state
return const Center(child: Text("No notifications available"));
} else {
// Show the notification list once data is fetched
return MyListView(notificationList: notificationController.notificationList);
}
}),
),
);
}
}
class MyListView extends StatelessWidget {
final List<NotificationModel> notificationList;
const MyListView({super.key, required this.notificationList});
@override
Widget build(BuildContext context) {
// Group notifications by date
Map<String, List<NotificationModel>> groupedNotifications = {};
for (var notification in notificationList) {
// Ensure that 'notifications' is not null
if (notification != null) {
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<NotificationModel> notificationsForDate = groupedNotifications[date]!;
return Padding(
padding: const EdgeInsets.only(bottom: 10, left: 10, right: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Display the date once
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
date,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
// Display notifications for the date
...notificationsForDate.map(
(notification) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: ExpansionTile(
collapsedBackgroundColor: Colors.white,
backgroundColor: Colors.white,
trailing: const SizedBox.shrink(),
title: Text(
notification.title ?? 'No title',
style: const TextStyle(
fontSize: 17, fontWeight: FontWeight.w500),
),
subtitle: Text(notification.msg ?? 'No message'),
),
);
},
).toList(),
],
),
);
},
);
}
}

View File

@ -204,7 +204,7 @@ class _CheckoutScreenState extends State<CheckoutScreen> {
],
title: const Text("Checkout"),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -80,7 +80,7 @@ class _OrderConfermationScreenState extends State<OrderConfermationScreen> {
"Order Confirmation",
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -61,7 +61,7 @@ class _OrderDetailScreenState extends State<OrderDetailScreen> {
"Order Detail",
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -56,7 +56,7 @@ class _OrderTrackingScreenState extends State<OrderTrackingScreen> {
"Order Tracking",
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -48,7 +48,7 @@ class _ShipmentTrackingScreenState extends State<ShipmentTrackingScreen> {
"Shipment Tracking",
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -87,7 +87,7 @@ class _OrderManagementScreenState extends State<OrderManagementScreen> {
],
title: const Text("Order Management"),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -90,7 +90,7 @@ class _CartScreenState extends State<CartScreen> {
),
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -175,7 +175,7 @@ class _ProductCatalogScreenState extends State<ProductCatalogScreen> {
),
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -64,7 +64,7 @@ class _ProductDetailScreenState extends State<ProductDetailScreen> {
"Product Detail",
),
),
drawer: const MyDrawer(),
drawer: MyDrawer(),
body: Stack(
fit: StackFit.expand,
children: [

View File

@ -46,6 +46,7 @@ class _ProductsManualScreenState extends State<ProductsManualScreen> {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Center(child: Text("Product Manual")),
leading: GestureDetector(
onTap: () => Get.back(),
child: Padding(

View File

@ -50,6 +50,7 @@ Future<BodyType?> commonApiService<BodyType>({
if (method == "POST") {
response = await dio.post("$baseUrl$url",
data: isformData ? formData : body, options: options);
} else if (method == "PUT") {
response = await dio.put("$baseUrl$url",
data: isformData ? formData : body, options: options);
@ -69,13 +70,14 @@ Future<BodyType?> commonApiService<BodyType>({
prefs.setString('token', response.data['token']);
}
if (url == "/api/territorymanager/my-profile") {
return fromJson(response.data['myData']);
}
// if (url == "/api/territorymanager/my-profile") {
// return fromJson(response.data['myData']);
// }
return fromJson(response.data);
} on DioException catch (e) {
print("dio exception $url ${e.response?.data}}");
print("dio exception details: ${e.message} ${e.response?.statusCode}");
String errorMessage = "An error occurred";

View File

@ -0,0 +1,37 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureStorageService {
// Create a private constructor
SecureStorageService._privateConstructor();
// The single instance of the class
static final SecureStorageService _instance = SecureStorageService._privateConstructor();
// The single instance of FlutterSecureStorage
final FlutterSecureStorage _storage = const FlutterSecureStorage();
// Factory constructor to return the same instance
factory SecureStorageService() {
return _instance;
}
// Method to write data to secure storage
Future<void> write({required String key, required String value}) async {
await _storage.write(key: key, value: value);
}
// Method to read data from secure storage
Future<String?> read({required String key}) async {
return await _storage.read(key: key);
}
// Method to delete data from secure storage
Future<void> delete({required String key}) async {
await _storage.delete(key: key);
}
// Method to clear all data from secure storage
Future<void> clear() async {
await _storage.deleteAll();
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class CommonAppBar extends StatelessWidget implements PreferredSizeWidget {
final Widget title;
@ -14,7 +15,9 @@ class CommonAppBar extends StatelessWidget implements PreferredSizeWidget {
leading: Builder(builder: (context) {
return IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(Icons.menu),
icon: SvgPicture.asset(
'assets/svg/menu.svg',
),
color: Colors.white);
}),
backgroundColor: Colors.transparent,