427 lines
14 KiB
Dart
427 lines
14 KiB
Dart
|
||
import 'dart:convert';
|
||
import 'dart:io';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||
import 'package:firebase_core/firebase_core.dart';
|
||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import 'package:package_info_plus/package_info_plus.dart';
|
||
import 'package:new_version_plus/new_version_plus.dart';
|
||
import 'package:http/http.dart' as http;
|
||
|
||
import 'Controller/Internetcheck/check_internet.dart';
|
||
import 'Globalwidgets/Localnotificationservice.dart';
|
||
import 'Globalwidgets/binding.dart';
|
||
import 'Helper/Constants/Apiconstants.dart';
|
||
import 'Helper/Constants/Colorconstants.dart';
|
||
import 'Helper/Constants/api_config.dart';
|
||
import 'Helper/Logger.dart';
|
||
import 'Helper/Locationservice/app_config_service.dart';
|
||
import 'View/Home/Homeview.dart';
|
||
import 'View/Introscreen/Introscreenview.dart';
|
||
import 'View/Update/Updateview.dart';
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 1. GLOBAL NOTIFICATION OBJECTS
|
||
/// ---------------------------------------------------------------------------
|
||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||
FlutterLocalNotificationsPlugin();
|
||
|
||
final AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||
'Nearlexpress Business', // id
|
||
'Nearlexpress Business Notifications', // name
|
||
description: 'Nearlexpress Business',
|
||
importance: Importance.high,
|
||
sound: const RawResourceAndroidNotificationSound('ring'), // raw/ring.mp3
|
||
enableLights: true,
|
||
enableVibration: true,
|
||
playSound: true,
|
||
showBadge: true,
|
||
vibrationPattern: Int64List.fromList(const [500, 1000, 500]),
|
||
);
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 2. BACKGROUND MESSAGE HANDLER (must be top-level)
|
||
/// ---------------------------------------------------------------------------
|
||
@pragma('vm:entry-point')
|
||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||
await Firebase.initializeApp();
|
||
|
||
// Initialize local notifications in background isolate
|
||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||
|
||
const DarwinInitializationSettings initializationSettingsIOS =
|
||
DarwinInitializationSettings();
|
||
|
||
const InitializationSettings initializationSettings = InitializationSettings(
|
||
android: initializationSettingsAndroid,
|
||
iOS: initializationSettingsIOS,
|
||
);
|
||
|
||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||
|
||
// Create notification channel
|
||
final AndroidNotificationChannel channel = AndroidNotificationChannel(
|
||
'Nearlexpress Business',
|
||
'Nearlexpress Business Notifications',
|
||
description: 'Nearlexpress Business',
|
||
importance: Importance.high,
|
||
sound: const RawResourceAndroidNotificationSound('ring'),
|
||
playSound: true,
|
||
enableVibration: true,
|
||
vibrationPattern: Int64List.fromList(const [500, 1000, 500]),
|
||
);
|
||
|
||
final androidPlugin = flutterLocalNotificationsPlugin
|
||
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
|
||
await androidPlugin?.createNotificationChannel(channel);
|
||
|
||
// === LOAD LOGO FROM ASSETS IN BACKGROUND ===
|
||
final ByteData logoData = await rootBundle.load('assets/images/nearlebusiness.png');
|
||
final Uint8List logoBytes = logoData.buffer.asUint8List();
|
||
|
||
// Show notification if it has title/body
|
||
if (message.notification != null) {
|
||
await flutterLocalNotificationsPlugin.show(
|
||
message.hashCode,
|
||
message.notification!.title,
|
||
message.notification!.body,
|
||
NotificationDetails(
|
||
android: AndroidNotificationDetails(
|
||
channel.id,
|
||
channel.name,
|
||
channelDescription: channel.description,
|
||
importance: Importance.high,
|
||
priority: Priority.high,
|
||
icon: '@mipmap/ic_launcher',
|
||
sound: const RawResourceAndroidNotificationSound('ring'),
|
||
enableVibration: true,
|
||
vibrationPattern: Int64List.fromList([500, 1000, 500]),
|
||
playSound: true,
|
||
color: const Color(0xFF662582),
|
||
largeIcon: ByteArrayAndroidBitmap(logoBytes), // LOGO ON RIGHT
|
||
styleInformation: BigPictureStyleInformation(
|
||
ByteArrayAndroidBitmap(logoBytes),
|
||
largeIcon: ByteArrayAndroidBitmap(logoBytes),
|
||
contentTitle: message.notification!.title,
|
||
htmlFormatContentTitle: true,
|
||
),
|
||
),
|
||
iOS: const DarwinNotificationDetails(
|
||
sound: 'ring.wav',
|
||
),
|
||
),
|
||
payload: jsonEncode(message.data.isNotEmpty ? message.data : {
|
||
"type": message.notification?.android?.tag ?? ""
|
||
}),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 3. LOCAL NOTIFICATION HELPER (foreground + background)
|
||
/// ---------------------------------------------------------------------------
|
||
Future<void> _showLocalNotification(RemoteMessage message) async {
|
||
final notification = message.notification;
|
||
if (notification == null) return;
|
||
|
||
// === LOAD LOGO FROM ASSETS ===
|
||
final ByteData logoData = await rootBundle.load('assets/images/nearle_logo.jpeg');
|
||
final Uint8List logoBytes = logoData.buffer.asUint8List();
|
||
|
||
final AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
|
||
channel.id,
|
||
channel.name,
|
||
channelDescription: channel.description,
|
||
importance: channel.importance,
|
||
priority: Priority.high,
|
||
sound: channel.sound,
|
||
enableVibration: channel.enableVibration,
|
||
vibrationPattern: channel.vibrationPattern,
|
||
playSound: channel.playSound,
|
||
enableLights: channel.enableLights,
|
||
icon: '@mipmap/ic_launcher',
|
||
color: const Color(0xFF662582),
|
||
largeIcon: ByteArrayAndroidBitmap(logoBytes), // LOGO ON RIGHT
|
||
styleInformation: BigPictureStyleInformation(
|
||
ByteArrayAndroidBitmap(logoBytes),
|
||
largeIcon: ByteArrayAndroidBitmap(logoBytes),
|
||
contentTitle: notification.title,
|
||
summaryText: notification.body,
|
||
htmlFormatContentTitle: true,
|
||
htmlFormatSummaryText: true,
|
||
),
|
||
);
|
||
|
||
const DarwinNotificationDetails iOSDetails = DarwinNotificationDetails(
|
||
sound: 'ring.mp3',
|
||
presentAlert: true,
|
||
presentBadge: true,
|
||
presentSound: true,
|
||
);
|
||
|
||
final NotificationDetails platformDetails =
|
||
NotificationDetails(android: androidDetails, iOS: iOSDetails);
|
||
|
||
await flutterLocalNotificationsPlugin.show(
|
||
notification.hashCode,
|
||
notification.title,
|
||
notification.body,
|
||
platformDetails,
|
||
payload: jsonEncode(message.data.isNotEmpty ? message.data : {
|
||
"type": message.notification?.android?.tag ?? ""
|
||
}),
|
||
);
|
||
}
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 4. NOTIFICATION TAP HANDLER
|
||
/// ---------------------------------------------------------------------------
|
||
void _handleNotificationTap(Map<String, dynamic> data) {
|
||
final type = data['type']?.toString();
|
||
print('Notification tapped – type: $type');
|
||
|
||
if (type == 'tojoin') {
|
||
Get.toNamed('/join');
|
||
}
|
||
}
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 5. HTTP OVERRIDE (dev only – remove in production)
|
||
/// ---------------------------------------------------------------------------
|
||
class MyHttpOverrides extends HttpOverrides {
|
||
@override
|
||
HttpClient createHttpClient(SecurityContext? context) {
|
||
return super.createHttpClient(context)
|
||
..badCertificateCallback = (cert, host, port) => true;
|
||
}
|
||
}
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 6. MAIN
|
||
/// ---------------------------------------------------------------------------
|
||
Future<void> main() async {
|
||
WidgetsFlutterBinding.ensureInitialized();
|
||
|
||
|
||
SystemChrome.setSystemUIOverlayStyle(
|
||
const SystemUiOverlayStyle(
|
||
statusBarColor: Colors.transparent, // or your color
|
||
statusBarIconBrightness: Brightness.dark, // Android icons
|
||
statusBarBrightness: Brightness.light, // iOS icons
|
||
),
|
||
);
|
||
|
||
|
||
// Allow self-signed certs (dev only)
|
||
HttpOverrides.global = MyHttpOverrides();
|
||
|
||
await Firebase.initializeApp();
|
||
|
||
// ---------- LOCAL NOTIFICATIONS INITIALISATION ----------
|
||
const AndroidInitializationSettings androidInit =
|
||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||
|
||
const DarwinInitializationSettings iOSInit = DarwinInitializationSettings(
|
||
requestAlertPermission: true,
|
||
requestBadgePermission: true,
|
||
requestSoundPermission: true,
|
||
);
|
||
|
||
const InitializationSettings initSettings =
|
||
InitializationSettings(android: androidInit, iOS: iOSInit);
|
||
|
||
await flutterLocalNotificationsPlugin.initialize(
|
||
initSettings,
|
||
);
|
||
|
||
// Create Android channel (mandatory for Android 8+)
|
||
final androidPlugin = flutterLocalNotificationsPlugin
|
||
.resolvePlatformSpecificImplementation<
|
||
AndroidFlutterLocalNotificationsPlugin>();
|
||
await androidPlugin?.createNotificationChannel(channel);
|
||
|
||
// ---------- FCM ----------
|
||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||
|
||
await FirebaseMessaging.instance
|
||
.setForegroundNotificationPresentationOptions(
|
||
alert: true,
|
||
badge: true,
|
||
sound: true,
|
||
);
|
||
|
||
SystemChrome.setPreferredOrientations([
|
||
DeviceOrientation.portraitUp,
|
||
DeviceOrientation.portraitDown,
|
||
]);
|
||
|
||
ApiConfig.setLive(); // 👈 clean call
|
||
// Dependency injection
|
||
Get.put(ConnectivityController());
|
||
|
||
runApp(const MyApp());
|
||
}
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 7. APP WIDGET
|
||
/// ---------------------------------------------------------------------------
|
||
class MyApp extends StatelessWidget {
|
||
const MyApp({Key? key}) : super(key: key);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return GetMaterialApp(
|
||
title: 'Nearlexpress Business',
|
||
debugShowCheckedModeBanner: false,
|
||
initialBinding: GlobalBinding(),
|
||
navigatorKey: MyHomePageOne.mNavigationState,
|
||
theme: ThemeData(
|
||
primaryColor: ColorConstants.primaryColor,
|
||
useMaterial3: false,
|
||
scaffoldBackgroundColor: Colors.grey.shade200,
|
||
appBarTheme: AppBarTheme(
|
||
iconTheme:
|
||
IconThemeData(color: ColorConstants.secondaryColor),
|
||
color: ColorConstants.secondaryColor,
|
||
elevation: 0,
|
||
titleTextStyle: TextStyle(
|
||
color: ColorConstants.secondaryColor,
|
||
fontSize: 18,
|
||
),
|
||
),
|
||
fontFamily: 'Lato',
|
||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||
),
|
||
home: const SafeArea(top: false, child: MyHomePageOne()),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// ---------------------------------------------------------------------------
|
||
/// 8. HOME PAGE (FCM + UI logic)
|
||
/// ---------------------------------------------------------------------------
|
||
class MyHomePageOne extends StatefulWidget {
|
||
static final GlobalKey<NavigatorState> mNavigationState =
|
||
GlobalKey<NavigatorState>();
|
||
const MyHomePageOne({Key? key}) : super(key: key);
|
||
|
||
@override
|
||
State<MyHomePageOne> createState() => _MyHomePageOneState();
|
||
}
|
||
|
||
class _MyHomePageOneState extends State<MyHomePageOne> {
|
||
final AppConfigurationService _appConfig = AppConfigurationService();
|
||
|
||
String? _tenantFcmToken;
|
||
String? _tenantContact;
|
||
int? _moduleId;
|
||
int? _tenantId;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_initApp();
|
||
}
|
||
|
||
Future<void> _initApp() async {
|
||
await _loadPrefs();
|
||
await _setupFCM();
|
||
await _getAppVersion();
|
||
_checkForUpdate(context);
|
||
_fetchLocation();
|
||
_appConfig.getAppConfig("${ApiConstants.configUrl}?configid=1");
|
||
LocalNotificationService.initialize(context);
|
||
}
|
||
|
||
Future<void> _loadPrefs() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
setState(() {
|
||
_tenantFcmToken = prefs.getString('tenantFcmToken');
|
||
_tenantContact = prefs.getString('tenantContactNo');
|
||
_moduleId = prefs.getInt('moduleId');
|
||
_tenantId = prefs.getInt('tenantId');
|
||
});
|
||
}
|
||
|
||
Future<void> _setupFCM() async {
|
||
final settings = await FirebaseMessaging.instance.requestPermission(
|
||
alert: true,
|
||
badge: true,
|
||
sound: true,
|
||
);
|
||
print('FCM permission: ${settings.authorizationStatus}');
|
||
|
||
final token = await FirebaseMessaging.instance.getToken();
|
||
print('=== FCM TOKEN ===\n$token\n=================');
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('fcmToken', token!);
|
||
|
||
FirebaseMessaging.onMessage.listen((RemoteMessage msg) async {
|
||
print('FOREGROUND: ${msg.notification?.title}');
|
||
await _showLocalNotification(msg);
|
||
});
|
||
|
||
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage msg) {
|
||
print('OPENED FROM NOTIFICATION');
|
||
_handleNotificationTap(msg.data);
|
||
});
|
||
}
|
||
|
||
String currentVersion = '';
|
||
Future<void> _getAppVersion() async {
|
||
final packageInfo = await PackageInfo.fromPlatform();
|
||
currentVersion = packageInfo.version;
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('CurrentVersion', currentVersion);
|
||
logger.i('Current version: $currentVersion');
|
||
}
|
||
|
||
Future<void> _checkForUpdate(BuildContext context) async {
|
||
final newVersion = NewVersionPlus(
|
||
iOSId: '284882215',
|
||
androidId: "com.nearle.bond",
|
||
);
|
||
final status = await newVersion.getVersionStatus();
|
||
if (status != null && status.canUpdate) {
|
||
Get.to(() => UpdateAppPage(
|
||
mCurrentVersion: status.localVersion,
|
||
mUpdateVersion: status.storeVersion,
|
||
));
|
||
}
|
||
}
|
||
|
||
Future<void> _fetchLocation() async {
|
||
final url = Uri.http('ip-api.com', '/json');
|
||
try {
|
||
final response = await http.get(url);
|
||
if (response.statusCode == 200) {
|
||
final data = jsonDecode(response.body);
|
||
final country = data['countryCode'].toString();
|
||
final city = data['city'].toString();
|
||
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString('location_Country', country);
|
||
await prefs.setString('MainCity', city);
|
||
await prefs.setString('location_CountryCode', country);
|
||
}
|
||
} catch (e) {
|
||
print('Location error: $e');
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return (_tenantFcmToken == null && _tenantContact == null)
|
||
? IntroScreenView()
|
||
: HomeView(selectedIndex: 0);
|
||
}
|
||
}
|
||
|