feat(fcm): support firebase-admin v14 modular API and google-services integration
This commit is contained in:
1 parent
0725f4ecf9
commit
0c859d7142
12 files changed
+182
-25
No files matched your search
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "893519269983",
|
||||
"project_id": "tis-rms-e05b9",
|
||||
"storage_bucket": "tis-rms-e05b9.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:893519269983:android:dc7f51f83b890700390bea",
|
||||
"android_client_info": {
|
||||
"package_name": "com.teamsb.tis_rms"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyCrT0WQVyVYyELlw0qN0IT48R_aJMqQBKI"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -15,12 +15,16 @@ void callbackDispatcher() {
|
||||
|
||||
// 1. Retrieve the saved base URL and Auth Token
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.reload();
|
||||
String rawUrl = prefs.getString('server_url') ?? ApiConstants.baseUrl;
|
||||
final clean = rawUrl.replaceAll(RegExp(r'/+$'), '');
|
||||
final baseUrl = clean.endsWith('/api') ? clean : '$clean/api';
|
||||
|
||||
const storage = FlutterSecureStorage();
|
||||
final token = await storage.read(key: 'jwt_token');
|
||||
String? token = prefs.getString('jwt_token');
|
||||
if (token == null || token.isEmpty) {
|
||||
const storage = FlutterSecureStorage();
|
||||
token = await storage.read(key: 'jwt_token');
|
||||
}
|
||||
if (token == null || token.isEmpty) {
|
||||
return Future.value(true); // Not logged in
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ class NotificationService {
|
||||
'Recent Activities',
|
||||
description: 'Notifications for recent activities and system events',
|
||||
importance: Importance.max,
|
||||
playSound: true,
|
||||
enableVibration: true,
|
||||
);
|
||||
await androidImplementation?.createNotificationChannel(channel);
|
||||
}
|
||||
@@ -88,7 +90,10 @@ class NotificationService {
|
||||
channelDescription:
|
||||
'Notifications for recent activities and system events',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
priority: Priority.max,
|
||||
playSound: true,
|
||||
enableVibration: true,
|
||||
visibility: NotificationVisibility.public,
|
||||
showWhen: true,
|
||||
);
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ class AuthRepository {
|
||||
key: _rememberMeKey,
|
||||
value: rememberMe ? 'true' : 'false',
|
||||
);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_tokenKey, token);
|
||||
|
||||
final userData = response.data['user'];
|
||||
return UserModel(
|
||||
@@ -60,6 +62,9 @@ class AuthRepository {
|
||||
if (token == null) return null;
|
||||
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_tokenKey, token);
|
||||
|
||||
final options = Options(headers: {'Authorization': 'Bearer $token'});
|
||||
final response = await _dio.get('/auth/profile', options: options);
|
||||
return UserModel.fromJson(response.data);
|
||||
@@ -80,6 +85,9 @@ class AuthRepository {
|
||||
Future<void> logout() async {
|
||||
await _storage.delete(key: _tokenKey);
|
||||
await _storage.delete(key: _rememberMeKey);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_tokenKey);
|
||||
await prefs.remove(_rememberMeKey);
|
||||
}
|
||||
|
||||
Future<Options> _getAuthOptions() async {
|
||||
|
||||
@@ -20,9 +20,9 @@ class NotificationNotifier extends AsyncNotifier<List<NotificationModel>> {
|
||||
|
||||
@override
|
||||
FutureOr<List<NotificationModel>> build() async {
|
||||
// Start real-time polling every 30 seconds
|
||||
// Start real-time polling every 10 seconds while app is open
|
||||
_pollingTimer?.cancel();
|
||||
_pollingTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
_pollingTimer = Timer.periodic(const Duration(seconds: 10), (_) {
|
||||
refreshNotifications();
|
||||
});
|
||||
|
||||
@@ -32,32 +32,67 @@ class NotificationNotifier extends AsyncNotifier<List<NotificationModel>> {
|
||||
});
|
||||
|
||||
final repo = ref.read(notificationRepositoryProvider);
|
||||
return await repo.getNotifications();
|
||||
}
|
||||
final list = await repo.getNotifications();
|
||||
|
||||
Future<void> refreshNotifications() async {
|
||||
try {
|
||||
final oldList = state.value ?? [];
|
||||
final repo = ref.read(notificationRepositoryProvider);
|
||||
final list = await repo.getNotifications();
|
||||
// Check if there are any unread notifications on initial load
|
||||
if (list.isNotEmpty) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final highestOldId = prefs.getInt('last_seen_notification_id') ?? 0;
|
||||
|
||||
// Trigger native notification for newly fetched items
|
||||
if (oldList.isNotEmpty && list.isNotEmpty) {
|
||||
final highestOldId = oldList.map((e) => e.id).reduce(math.max);
|
||||
final newNotes = list.where((e) => e.id > highestOldId).toList();
|
||||
final newNotes = list.where((e) {
|
||||
if (!e.isRead) {
|
||||
if (highestOldId == 0) return true;
|
||||
return e.id > highestOldId;
|
||||
}
|
||||
return false;
|
||||
}).toList();
|
||||
|
||||
if (newNotes.isNotEmpty) {
|
||||
for (var note in newNotes) {
|
||||
NotificationService().showNotification(
|
||||
await NotificationService().showNotification(
|
||||
id: note.id,
|
||||
title: note.title,
|
||||
body: note.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final highestId = list.map((e) => e.id).reduce(math.max);
|
||||
await prefs.setInt('last_seen_notification_id', math.max(highestOldId, highestId));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
Future<void> refreshNotifications() async {
|
||||
try {
|
||||
final repo = ref.read(notificationRepositoryProvider);
|
||||
final list = await repo.getNotifications();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final highestOldId = prefs.getInt('last_seen_notification_id') ?? 0;
|
||||
|
||||
if (list.isNotEmpty) {
|
||||
final newNotes = list.where((e) {
|
||||
if (!e.isRead) {
|
||||
if (highestOldId == 0) return true;
|
||||
return e.id > highestOldId;
|
||||
}
|
||||
return false;
|
||||
}).toList();
|
||||
|
||||
if (newNotes.isNotEmpty) {
|
||||
for (var note in newNotes) {
|
||||
await NotificationService().showNotification(
|
||||
id: note.id,
|
||||
title: note.title,
|
||||
body: note.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final highestId = list.map((e) => e.id).reduce(math.max);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('last_seen_notification_id', highestId);
|
||||
await prefs.setInt('last_seen_notification_id', math.max(highestOldId, highestId));
|
||||
}
|
||||
|
||||
state = AsyncData(list);
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../login/login_screen.dart';
|
||||
import '../../layouts/windows_sidebar_layout.dart';
|
||||
import '../../layouts/android_bottom_nav_layout.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../../core/services/foreground_sync_service.dart';
|
||||
|
||||
class SplashScreen extends ConsumerStatefulWidget {
|
||||
const SplashScreen({super.key});
|
||||
@@ -48,6 +49,7 @@ class _SplashScreenState extends ConsumerState<SplashScreen>
|
||||
}
|
||||
|
||||
Future<void> _initializeApp() async {
|
||||
await ForegroundSyncService.requestPermissions();
|
||||
await Future.delayed(const Duration(milliseconds: 800));
|
||||
if (!mounted) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_flutterfire_internals:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.59"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -329,6 +337,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3+5"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.15.2"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.24.1"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.10"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.10"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.10"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
@@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_drop
|
||||
file_selector_windows
|
||||
firebase_core
|
||||
flutter_secure_storage_windows
|
||||
permission_handler_windows
|
||||
printing
|
||||
|
||||
Reference in new issue
Block a user