feat(fcm): support firebase-admin v14 modular API and google-services integration

This commit is contained in:
ssbg04 committed 2026-08-18 15:35:47 +08:00
1 parent 0725f4ecf9
commit 0c859d7142
12 files changed
+182 -25

No files matched your search

+29
View File
@@ -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;
+56
View File
@@ -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