* Push Notification Setup and attempt
* Office Ordering * Allow editing of Task and Ticket Details after creation
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
@@ -40,6 +41,72 @@ class NotificationsController {
|
||||
|
||||
final SupabaseClient _client;
|
||||
|
||||
/// Internal helper that inserts notification rows and sends pushes if
|
||||
/// [targetUserIds] is provided.
|
||||
/// Internal helper that inserts notification rows and optionally sends
|
||||
/// FCM pushes. Callers should use [createNotification] instead.
|
||||
Future<void> _createAndPush(
|
||||
List<Map<String, dynamic>> rows, {
|
||||
List<String>? targetUserIds,
|
||||
String? pushTitle,
|
||||
String? pushBody,
|
||||
Map<String, dynamic>? pushData,
|
||||
}) async {
|
||||
if (rows.isEmpty) return;
|
||||
debugPrint(
|
||||
'notifications_provider: inserting ${rows.length} rows; pushTitle=$pushTitle',
|
||||
);
|
||||
await _client.from('notifications').insert(rows);
|
||||
|
||||
// push notifications are now handled by a database trigger that
|
||||
// calls the `send_fcm` edge function. We keep the client-side
|
||||
// `sendPush` method around for manual/integration use, but avoid
|
||||
// invoking it here to prevent CORS issues on web.
|
||||
if (targetUserIds == null || targetUserIds.isEmpty) return;
|
||||
|
||||
debugPrint(
|
||||
'notification rows inserted; server trigger will perform pushes',
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a typed notification in the database. This method handles
|
||||
/// inserting the row(s); a PostgreSQL trigger will forward the new row to
|
||||
/// the `send_fcm` edge function, so clients do **not** directly invoke it
|
||||
/// (avoids CORS/auth problems on web). The [pushTitle]/[pushBody] values
|
||||
/// are still stored for the trigger payload.
|
||||
Future<void> createNotification({
|
||||
required List<String> userIds,
|
||||
required String type,
|
||||
required String actorId,
|
||||
Map<String, dynamic>? fields,
|
||||
String? pushTitle,
|
||||
String? pushBody,
|
||||
Map<String, dynamic>? pushData,
|
||||
}) async {
|
||||
debugPrint(
|
||||
'createNotification called type=$type users=${userIds.length} pushTitle=$pushTitle pushBody=$pushBody',
|
||||
);
|
||||
if (userIds.isEmpty) return;
|
||||
|
||||
final rows = userIds.map((userId) {
|
||||
return <String, dynamic>{
|
||||
'user_id': userId,
|
||||
'actor_id': actorId,
|
||||
'type': type,
|
||||
...?fields,
|
||||
};
|
||||
}).toList();
|
||||
|
||||
await _createAndPush(
|
||||
rows,
|
||||
targetUserIds: userIds,
|
||||
pushTitle: pushTitle,
|
||||
pushBody: pushBody,
|
||||
pushData: pushData,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convenience for mention-specific case; left for compatibility.
|
||||
Future<void> createMentionNotifications({
|
||||
required List<String> userIds,
|
||||
required String actorId,
|
||||
@@ -47,24 +114,22 @@ class NotificationsController {
|
||||
String? ticketId,
|
||||
String? taskId,
|
||||
}) async {
|
||||
if (userIds.isEmpty) return;
|
||||
if ((ticketId == null || ticketId.isEmpty) &&
|
||||
(taskId == null || taskId.isEmpty)) {
|
||||
return;
|
||||
}
|
||||
final rows = userIds
|
||||
.map(
|
||||
(userId) => {
|
||||
'user_id': userId,
|
||||
'actor_id': actorId,
|
||||
'ticket_id': ticketId,
|
||||
'task_id': taskId,
|
||||
'message_id': messageId,
|
||||
'type': 'mention',
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
await _client.from('notifications').insert(rows);
|
||||
return createNotification(
|
||||
userIds: userIds,
|
||||
type: 'mention',
|
||||
actorId: actorId,
|
||||
fields: {
|
||||
'message_id': messageId,
|
||||
if (ticketId != null) 'ticket_id': ticketId,
|
||||
if (taskId != null) 'task_id': taskId,
|
||||
},
|
||||
pushTitle: 'New mention',
|
||||
pushBody: 'You were mentioned in a message',
|
||||
pushData: {
|
||||
if (ticketId != null) 'ticket_id': ticketId,
|
||||
if (taskId != null) 'task_id': taskId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markRead(String id) async {
|
||||
@@ -95,4 +160,85 @@ class NotificationsController {
|
||||
.eq('user_id', userId)
|
||||
.filter('read_at', 'is', null);
|
||||
}
|
||||
|
||||
/// Store or update an FCM token for the current user.
|
||||
Future<void> registerFcmToken(String token) async {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) return;
|
||||
final res = await _client.from('fcm_tokens').insert({
|
||||
'user_id': userId,
|
||||
'token': token,
|
||||
});
|
||||
if (res == null) {
|
||||
debugPrint(
|
||||
'registerFcmToken: null response for user=$userId token=$token',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final dyn = res as dynamic;
|
||||
if (dyn.error != null) {
|
||||
// duplicate key or RLS issue - just log it
|
||||
debugPrint('registerFcmToken error: ${dyn.error?.message ?? dyn.error}');
|
||||
} else {
|
||||
debugPrint('registerFcmToken success for user=$userId token=$token');
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove an FCM token (e.g. when the user logs out or uninstalls).
|
||||
Future<void> unregisterFcmToken(String token) async {
|
||||
final userId = _client.auth.currentUser?.id;
|
||||
if (userId == null) return;
|
||||
final res = await _client
|
||||
.from('fcm_tokens')
|
||||
.delete()
|
||||
.eq('user_id', userId)
|
||||
.eq('token', token);
|
||||
if (res == null) {
|
||||
debugPrint(
|
||||
'unregisterFcmToken: null response for user=$userId token=$token',
|
||||
);
|
||||
return;
|
||||
}
|
||||
final uDyn = res as dynamic;
|
||||
if (uDyn.error != null) {
|
||||
debugPrint(
|
||||
'unregisterFcmToken error: ${uDyn.error?.message ?? uDyn.error}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a push message via the `send_fcm` edge function.
|
||||
Future<void> sendPush({
|
||||
List<String>? tokens,
|
||||
List<String>? userIds,
|
||||
required String title,
|
||||
required String body,
|
||||
Map<String, dynamic>? data,
|
||||
}) async {
|
||||
try {
|
||||
if (tokens != null) {
|
||||
debugPrint(
|
||||
'invoking send_fcm with ${tokens.length} tokens, title="$title"',
|
||||
);
|
||||
} else if (userIds != null) {
|
||||
debugPrint(
|
||||
'invoking send_fcm with userIds=${userIds.length}, title="$title"',
|
||||
);
|
||||
} else {
|
||||
debugPrint('sendPush called with neither tokens nor userIds');
|
||||
return;
|
||||
}
|
||||
final bodyPayload = <String, dynamic>{
|
||||
if (tokens != null) 'tokens': tokens,
|
||||
if (userIds != null) 'user_ids': userIds,
|
||||
'title': title,
|
||||
'body': body,
|
||||
'data': data ?? {},
|
||||
};
|
||||
await _client.functions.invoke('send_fcm', body: bodyPayload);
|
||||
} catch (err) {
|
||||
debugPrint('sendPush invocation error: $err');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user