first commit
This commit is contained in:
15
lib/service/bindings.dart
Normal file
15
lib/service/bindings.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
|
||||
import '../controllers/authentication/auth_controller.dart';
|
||||
import '../controllers/intro_controller/intro_screen_controller.dart';
|
||||
import '../modules/authentication/auth.dart';
|
||||
|
||||
class GlobalBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
|
||||
Get.lazyPut(()=>IntroScreenController(),fenix:true);}
|
||||
|
||||
|
||||
}
|
||||
27
lib/service/connectivity/connectivity_controller.dart
Normal file
27
lib/service/connectivity/connectivity_controller.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class ConnectivityController extends GetxController {
|
||||
var isConnected = true.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_checkInitialConnection();
|
||||
|
||||
// Listen to connection changes
|
||||
Connectivity().onConnectivityChanged.listen((status) {
|
||||
isConnected.value = (status != ConnectivityResult.none);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _checkInitialConnection() async {
|
||||
final status = await Connectivity().checkConnectivity();
|
||||
isConnected.value = (status != ConnectivityResult.none);
|
||||
}
|
||||
|
||||
Future<void> retryConnection() async {
|
||||
final status = await Connectivity().checkConnectivity();
|
||||
isConnected.value = (status != ConnectivityResult.none);
|
||||
}
|
||||
}
|
||||
47
lib/service/device_info/device_info.dart
Normal file
47
lib/service/device_info/device_info.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'dart:io';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class DeviceInfo {
|
||||
Future<Map<String, dynamic>> getDeviceInfo() async {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
Map<String, dynamic> info;
|
||||
String? currentDeviceId;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
currentDeviceId = androidInfo.id; // ✅ Device ID for Android
|
||||
info = {
|
||||
"device": "Android",
|
||||
"modules": androidInfo.model,
|
||||
"manufacturer": androidInfo.manufacturer,
|
||||
"androidVersion": androidInfo.version.release,
|
||||
"deviceId": currentDeviceId,
|
||||
};
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||
currentDeviceId = iosInfo.identifierForVendor; // ✅ Device ID for iOS
|
||||
info = {
|
||||
"device": "iOS",
|
||||
"name": iosInfo.name,
|
||||
"systemName": iosInfo.systemName,
|
||||
"systemVersion": iosInfo.systemVersion,
|
||||
"modules": iosInfo.model,
|
||||
"identifierForVendor": currentDeviceId,
|
||||
};
|
||||
} else {
|
||||
info = {"device": "Unknown"};
|
||||
}
|
||||
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
// Save full info as string (optional)
|
||||
prefs.setString('deviceInfo', info.toString());
|
||||
|
||||
// ✅ Save only current device ID
|
||||
if (currentDeviceId != null) {
|
||||
prefs.setString('currentDeviceId', currentDeviceId);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
86
lib/service/dio.dart
Normal file
86
lib/service/dio.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../constants/api_constants.dart';
|
||||
|
||||
class CustomDio {
|
||||
final Dio _dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: ApiConstants.baseUrl, // Change this to your API base URL
|
||||
connectTimeout: Duration(seconds: 20), // Timeout settings
|
||||
receiveTimeout: Duration(seconds: 40),
|
||||
headers: {'Content-Type': 'application/json'}, // Default headers
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
Future<dynamic> getData(
|
||||
String endpoint, {
|
||||
Map<String, dynamic>? headers,
|
||||
}) async {
|
||||
try {
|
||||
Response response = await _dio.get(
|
||||
endpoint,
|
||||
options: Options(headers: headers),
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} on DioException catch (e) {
|
||||
// 🔥 THIS IS THE FIX
|
||||
if (e.response != null) {
|
||||
print("ERROR DATA: ${e.response?.data}");
|
||||
return e.response?.data; // ✅ return actual backend response
|
||||
} else {
|
||||
print("DIO ERROR: ${e.message}");
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
print("UNKNOWN ERROR: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// POST Request
|
||||
Future<dynamic> postData(String endpoint, Map<String, dynamic> data) async {
|
||||
try {
|
||||
Response response = await _dio.post(endpoint, data: data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
return _handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// PUT Request
|
||||
Future<dynamic> putData(String endpoint, Map<String, dynamic> data) async {
|
||||
try {
|
||||
Response response = await _dio.put(endpoint, data: data);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
return _handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// DELETE Request
|
||||
Future<dynamic> deleteData(String endpoint) async {
|
||||
try {
|
||||
Response response = await _dio.delete(endpoint);
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
return _handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle Errors
|
||||
dynamic _handleError(dynamic error) {
|
||||
if (error is DioException) {
|
||||
return "Error: ${error.message}";
|
||||
}
|
||||
return "Unexpected Error";
|
||||
}
|
||||
}
|
||||
32
lib/service/location/location.dart
Normal file
32
lib/service/location/location.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
|
||||
class LocationService {
|
||||
static Future<Position> getCurrentLocation() async {
|
||||
bool serviceEnabled;
|
||||
LocationPermission permission;
|
||||
|
||||
// Check if location services are enabled
|
||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return Future.error('Location services are disabled.');
|
||||
}
|
||||
|
||||
// Check for permission
|
||||
permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
return Future.error('Location permissions are denied');
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
return Future.error(
|
||||
'Location permissions are permanently denied.');
|
||||
}
|
||||
|
||||
// Get current location
|
||||
return await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
}
|
||||
}
|
||||
331
lib/service/notification.dart
Normal file
331
lib/service/notification.dart
Normal file
@@ -0,0 +1,331 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../Helper/Logger.dart';
|
||||
|
||||
|
||||
class LocalNotificationService {
|
||||
static final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
|
||||
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
static const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||||
'Nearle', // Channel ID
|
||||
'Nearle Notification', // Channel name
|
||||
description: 'Channel for Nearle notifications', // Channel description
|
||||
importance: Importance.max,
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
||||
enableVibration: true,
|
||||
showBadge: true,
|
||||
);
|
||||
|
||||
static Future<void> initialize(BuildContext context, String status) async {
|
||||
// Create Android notification channel
|
||||
await _notificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.createNotificationChannel(channel);
|
||||
|
||||
// Initialize local notifications
|
||||
const InitializationSettings initializationSettings = InitializationSettings(
|
||||
android: AndroidInitializationSettings("@mipmap/ic_launcher"),
|
||||
iOS: DarwinInitializationSettings(
|
||||
requestSoundPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestAlertPermission: true,
|
||||
defaultPresentSound: true,
|
||||
defaultPresentBadge: true,
|
||||
defaultPresentBanner: true,
|
||||
defaultPresentAlert: true,
|
||||
defaultPresentList: true,
|
||||
),
|
||||
);
|
||||
|
||||
await _notificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: (NotificationResponse response) async {
|
||||
if (response.payload != null) {
|
||||
await selectNotification(response.payload);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Handle messages when the app is opened from a terminated state
|
||||
RemoteMessage? initialMessage = await _firebaseMessaging.getInitialMessage();
|
||||
if (initialMessage != null) {
|
||||
await _handleInitialMessage(initialMessage);
|
||||
}
|
||||
|
||||
// Handle foreground messages
|
||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
|
||||
logger.i('Received foreground message: ${message.data}');
|
||||
await _handleMessage(message);
|
||||
});
|
||||
|
||||
// Handle messages when the app is opened from a notification
|
||||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
|
||||
logger.i('Message opened app: ${message.data}');
|
||||
await _handleMessageOpenedApp(message);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<void> _handleInitialMessage(RemoteMessage message) async {
|
||||
if (message.notification != null) {
|
||||
// Cancel the notification
|
||||
final notificationId = int.parse(message.data['notification_id'] ?? '0');
|
||||
await _notificationsPlugin.cancel(notificationId);
|
||||
await display(message);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _handleMessage(RemoteMessage message) async {
|
||||
if (message.notification != null) {
|
||||
logger.i('Displaying notification: ${message.notification!.body}');
|
||||
await display(message);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _handleMessageOpenedApp(RemoteMessage message) async {
|
||||
try {
|
||||
// Cancel the notification using the ID from message.data
|
||||
final notificationId = int.parse(message.data['notification_id'] ?? '0');
|
||||
await _notificationsPlugin.cancel(notificationId);
|
||||
|
||||
if (message.notification != null) {
|
||||
// Navigator.of(Get.context!).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (BuildContext context) => HomeView(selectedIndex: 1),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
logger.e('Error in handleMessageOpenedApp: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> selectNotification(String? payload) async {
|
||||
try {
|
||||
if (payload != null) {
|
||||
final decodedPayload = jsonDecode(payload);
|
||||
final notificationId = int.parse(decodedPayload['id'] ?? '0');
|
||||
|
||||
// Cancel the specific notification
|
||||
await _notificationsPlugin.cancel(notificationId);
|
||||
|
||||
// Navigate to the desired page
|
||||
// Navigator.of(Get.context!).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (BuildContext context) => HomeView(selectedIndex: 1),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
logger.e('Error in selectNotification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> _downloadAndSaveImage(String imageUrl, String fileName) async {
|
||||
try {
|
||||
final directory = await getTemporaryDirectory();
|
||||
final filePath = '${directory.path}/$fileName';
|
||||
final response = await http.get(Uri.parse(imageUrl));
|
||||
if (response.statusCode == 200) {
|
||||
final file = File(filePath);
|
||||
await file.writeAsBytes(response.bodyBytes);
|
||||
logger.i('Image downloaded to: $filePath');
|
||||
return filePath;
|
||||
} else {
|
||||
logger.e('Failed to download image: ${response.statusCode}');
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e('Error downloading image: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String? _extractImageUrl(RemoteMessage message) {
|
||||
// General data field
|
||||
String? imageUrl = message.data['image'] as String?;
|
||||
|
||||
// Android-specific
|
||||
if (imageUrl == null && message.notification?.android?.imageUrl != null) {
|
||||
imageUrl = message.notification!.android!.imageUrl;
|
||||
}
|
||||
|
||||
// iOS-specific
|
||||
if (imageUrl == null && message.notification?.apple?.imageUrl != null) {
|
||||
imageUrl = message.notification!.apple!.imageUrl;
|
||||
}
|
||||
|
||||
logger.i('Extracted image URL: $imageUrl');
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
static Future<void> display(RemoteMessage message) async {
|
||||
if (message.notification != null) {
|
||||
// Navigator.of(Get.context!).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (BuildContext context) => HomeView(selectedIndex: 1),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
try {
|
||||
final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
// Create a payload that includes the notification ID
|
||||
final payload = {
|
||||
'id': id.toString(),
|
||||
'data': message.data,
|
||||
};
|
||||
|
||||
NotificationDetails notificationDetails;
|
||||
|
||||
// Extract image URL from data or android-specific field
|
||||
final imageUrl = _extractImageUrl(message);
|
||||
|
||||
if (imageUrl != null && imageUrl.isNotEmpty) {
|
||||
// Download the image
|
||||
final imagePath = await _downloadAndSaveImage(imageUrl, 'notification_image.jpg');
|
||||
if (imagePath != null) {
|
||||
if (Platform.isAndroid) {
|
||||
|
||||
String? bodyText = message.notification?.body ?? message.data['body'] ?? '';
|
||||
List<String>? lines = bodyText?.split('\n'); // or split on ',' or build manually
|
||||
|
||||
final inboxStyle = InboxStyleInformation(
|
||||
lines ?? [],
|
||||
contentTitle: message.notification?.title ?? message.data['title'],
|
||||
summaryText: '', // optional, can be empty or a short summary
|
||||
);
|
||||
|
||||
notificationDetails = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'Nearle',
|
||||
'Nearle Notification',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
icon: 'notification',
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
||||
enableVibration: true,
|
||||
fullScreenIntent: true,
|
||||
channelShowBadge: true,
|
||||
ongoing: false, // Allow dismissal
|
||||
autoCancel: true, // Cancel when tapped
|
||||
styleInformation: inboxStyle,
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
presentList: true,
|
||||
presentBanner: true,
|
||||
attachments: [
|
||||
DarwinNotificationAttachment(imagePath),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// iOS or other platforms
|
||||
notificationDetails = NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'Nearle',
|
||||
'Nearle Notification',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
icon: 'notification',
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
||||
enableVibration: true,
|
||||
fullScreenIntent: true,
|
||||
channelShowBadge: true,
|
||||
ongoing: false, // Allow dismissal
|
||||
autoCancel: true, // Cancel when tapped
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
presentList: true,
|
||||
presentBanner: true,
|
||||
attachments: [
|
||||
DarwinNotificationAttachment(imagePath),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Fallback if image download fails
|
||||
notificationDetails = const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'Nearle',
|
||||
'Nearle Notification',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
icon: 'notification',
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
||||
enableVibration: true,
|
||||
fullScreenIntent: true,
|
||||
channelShowBadge: true,
|
||||
ongoing: false, // Allow dismissal
|
||||
autoCancel: true, // Cancel when tapped
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
presentList: true,
|
||||
presentBanner: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// No image in payload, use default notification details
|
||||
notificationDetails = const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'Nearle',
|
||||
'Nearle Notification',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
icon: 'notification',
|
||||
playSound: true,
|
||||
sound: RawResourceAndroidNotificationSound('notification_ring'),
|
||||
enableVibration: true,
|
||||
fullScreenIntent: true,
|
||||
channelShowBadge: true,
|
||||
ongoing: false, // Allow dismissal
|
||||
autoCancel: true, // Cancel when tapped
|
||||
),
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
presentList: true,
|
||||
presentBanner: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await _notificationsPlugin.show(
|
||||
id,
|
||||
message.notification?.title ?? message.data['title'] ?? 'Nearle',
|
||||
message.notification?.body ?? message.data['body'] ?? 'Notification',
|
||||
notificationDetails,
|
||||
payload: jsonEncode(payload),
|
||||
);
|
||||
}
|
||||
on Exception catch (e) {
|
||||
logger.e('Error displaying notification: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user