diff --git a/android/src/main/java/com/localnotifications/LocalNotificationsModule.kt b/android/src/main/java/com/localnotifications/LocalNotificationsModule.kt index 3ecc675..c264cc4 100644 --- a/android/src/main/java/com/localnotifications/LocalNotificationsModule.kt +++ b/android/src/main/java/com/localnotifications/LocalNotificationsModule.kt @@ -16,31 +16,34 @@ class LocalNotificationsModule internal constructor(val context: ReactApplicatio @ReactMethod override fun scheduleNotification( - notification: ReadableMap?, - trigger: ReadableMap?, + notification: ReadableMap, + trigger: ReadableMap, promise: Promise? ) { - if(notification == null || trigger == null) { + val title = notification.getString("title") + if(title == null) { + promise?.reject("Error", "Title prop is missing.") return } - val title = notification.getString("title") ?: return val body = notification.getString("body") val data = notification.getMap("data") - val scheduleId = notification.getString("id")?: return + val scheduleId = notification.getString("id") val androidParamsMap = notification.getMap("android") val smallIconResName = androidParamsMap?.getString("smallIcon") + val colorHex = androidParamsMap?.getString("color") val dateInMillis = trigger.getDouble("timestamp").toLong() - NotificationScheduler.scheduleNotification( + val scheduledId = NotificationScheduler.scheduleNotification( context, title, body, + colorHex, data, smallIconResName, scheduleId, dateInMillis ) - promise?.resolve(null) + promise?.resolve(scheduledId) } @ReactMethod diff --git a/android/src/main/java/com/localnotifications/NotificationReceiver.kt b/android/src/main/java/com/localnotifications/NotificationReceiver.kt index f60e731..2ad45d8 100644 --- a/android/src/main/java/com/localnotifications/NotificationReceiver.kt +++ b/android/src/main/java/com/localnotifications/NotificationReceiver.kt @@ -5,6 +5,8 @@ import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.graphics.Color +import android.util.Log import androidx.core.app.NotificationCompat import com.localnotifications.util.IntentUtil import com.localnotifications.util.JsonUtil @@ -16,12 +18,13 @@ const val EXTRA_SCHEDULE_ID = "com.localnotifications.EXTRA_SCHEDULE_ID" const val EXTRA_TITLE = "com.localnotifications.EXTRA_TITLE" const val EXTRA_MESSAGE = "com.localnotifications.EXTRA_MESSAGE" const val EXTRA_DATA = "com.localnotifications.EXTRA_DATA" +const val EXTRA_COLOR = "com.localnotifications.EXTRA_COLOR" const val EXTRA_SMALL_ICON_RES_ID = "com.localnotifications.EXTRA_SMALL_ICON_RES_ID" // BroadcastReceiver for handling notifications class NotificationReceiver : BroadcastReceiver() { - + private val TAG = "NotificationReceiver" override fun onReceive(context: Context, intent: Intent) { val id = intent.getIntExtra(EXTRA_SCHEDULE_ID, -1) @@ -35,18 +38,27 @@ class NotificationReceiver : BroadcastReceiver() { receiverIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE ) + val notification = NotificationCompat.Builder(context, channelID) + + try { + val colorHex = intent.getStringExtra(EXTRA_COLOR) + if(colorHex != null) { + notification.setColor(Color.parseColor(colorHex)) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to parse the HEX color string.", e) + } val jsonObject = JSONObject(intent.getStringExtra(EXTRA_DATA) ?: "{}") - val notification = NotificationCompat.Builder(context, channelID) + notification .setSmallIcon(intent.getIntExtra(EXTRA_SMALL_ICON_RES_ID, 0)) - .setContentTitle(intent.getStringExtra(EXTRA_TITLE)) // Set title from intent - .setContentText(intent.getStringExtra(EXTRA_MESSAGE)) // Set content text from intent + .setContentTitle(intent.getStringExtra(EXTRA_TITLE)) + .setContentText(intent.getStringExtra(EXTRA_MESSAGE)) .setExtras(JsonUtil.convertJsonToBundle(jsonObject)) .setContentIntent(pendingIntent) .setAutoCancel(true) - .build() val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - manager.notify(id, notification) + manager.notify(id, notification.build()) } } diff --git a/android/src/main/java/com/localnotifications/NotificationScheduler.kt b/android/src/main/java/com/localnotifications/NotificationScheduler.kt index b3a4263..e30213d 100644 --- a/android/src/main/java/com/localnotifications/NotificationScheduler.kt +++ b/android/src/main/java/com/localnotifications/NotificationScheduler.kt @@ -14,28 +14,33 @@ import com.localnotifications.util.MapUtil import com.localnotifications.util.ResourceUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import java.util.UUID object NotificationScheduler { @SuppressLint("ScheduleExactAlarm") - public fun scheduleNotification( + fun scheduleNotification( context: ReactApplicationContext, title: String, body: String?, + colorHex: String?, data: ReadableMap?, smallIconResName: String?, - scheduleId: String, + scheduleId: String?, triggerDateInMillis: Long - ) { + ): String { createNotificationChannel(context) // Create an intent for the Notification BroadcastReceiver val intent = getNotificationIntent(context) intent.putExtra(EXTRA_TITLE, title) intent.putExtra(EXTRA_MESSAGE, body) - intent.putExtra(EXTRA_SCHEDULE_ID, scheduleId.hashCode()) - intent.putExtra(EXTRA_DATA, MapUtil.toJSONObject(data).toString()) + val safeScheduleId = scheduleId ?: UUID.randomUUID().toString() + intent.putExtra(EXTRA_SCHEDULE_ID, safeScheduleId.hashCode()) + if(data != null) { + intent.putExtra(EXTRA_DATA, MapUtil.toJSONObject(data).toString()) + } val safeSmallIconResName = ResourceUtil.getImageResourceId( smallIconResName ?: "ic_launcher", @@ -43,8 +48,12 @@ object NotificationScheduler { ) intent.putExtra(EXTRA_SMALL_ICON_RES_ID, safeSmallIconResName) + if(colorHex != null) { + intent.putExtra(EXTRA_COLOR, colorHex) + } + // Create a PendingIntent for the broadcast - val pendingIntent = getBroadcastPendingIntent(context, scheduleId, intent) + val pendingIntent = getBroadcastPendingIntent(context, safeScheduleId, intent) // Get the AlarmManager service val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager @@ -63,9 +72,10 @@ object NotificationScheduler { } runBlocking(Dispatchers.IO) { - LocalStorage.addScheduleIds(arrayOf(scheduleId), context) + LocalStorage.addScheduleIds(arrayOf(safeScheduleId), context) } + return safeScheduleId } private fun createNotificationChannel(context: Context) { diff --git a/android/src/oldarch/LocalNotificationsSpec.kt b/android/src/oldarch/LocalNotificationsSpec.kt index 6d9bb76..00d71c9 100644 --- a/android/src/oldarch/LocalNotificationsSpec.kt +++ b/android/src/oldarch/LocalNotificationsSpec.kt @@ -10,8 +10,8 @@ abstract class LocalNotificationsSpec internal constructor(context: ReactApplica ReactContextBaseJavaModule(context) { abstract fun scheduleNotification( - notification: ReadableMap?, - trigger: ReadableMap?, + notification: ReadableMap, + trigger: ReadableMap, promise: Promise? ) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index d59be24..f25266b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -944,7 +944,7 @@ PODS: - React-Mapbuffer (0.73.6): - glog - React-debug - - react-native-local-notifications (0.2.0): + - react-native-local-notifications (0.3.0): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1351,7 +1351,7 @@ SPEC CHECKSUMS: React-jsinspector: 85583ef014ce53d731a98c66a0e24496f7a83066 React-logger: 3eb80a977f0d9669468ef641a5e1fabbc50a09ec React-Mapbuffer: 84ea43c6c6232049135b1550b8c60b2faac19fab - react-native-local-notifications: e59a16b137462d558332fa928b2374ad845ee466 + react-native-local-notifications: 8a2a81edea8c3d4cddc1874eaeae1e359da913a7 React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f React-NativeModulesApple: cd26e56d56350e123da0c1e3e4c76cb58a05e1ee React-perflogger: 5f49905de275bac07ac7ea7f575a70611fa988f2 diff --git a/example/src/App.tsx b/example/src/App.tsx index 8c73f5c..74a1827 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -9,22 +9,20 @@ import { export default function App() { const onPress = useCallback(async () => { - await scheduleNotification( + const id = await scheduleNotification( { - id: 'my_id', title: 'Title', body: 'New', - data: { - url: 'https://example.com', - }, android: { smallIcon: 'ic_launcher', + color: '#0000ff', }, }, { timestamp: Date.now() + 5000, } ); + console.log('Scheduled notification with id:', id); }, []); const cancelById = useCallback(async () => { diff --git a/ios/LocalNotifications.mm b/ios/LocalNotifications.mm index 4e624d9..269e9bb 100644 --- a/ios/LocalNotifications.mm +++ b/ios/LocalNotifications.mm @@ -4,6 +4,9 @@ @implementation LocalNotifications RCT_EXPORT_MODULE() + +NSString *TAG = @"[react-native-local-notifications]"; + #ifdef RCT_NEW_ARCH_ENABLED RCT_EXPORT_METHOD(scheduleNotification: (JS::NativeLocalNotifications::Notification &)notification @@ -13,11 +16,11 @@ @implementation LocalNotifications { [NotificationScheduler - scheduleNotificationWithTitle: notification.title() - body: notification.body() - data: (NSMutableDictionary * _Nullable) notification.data() - scheduleId: notification.id_() - triggerDate: [NSDate dateWithTimeIntervalSince1970: trigger.timestamp() / 1000] + scheduleNotificationWithTitle: notification.title() + body: notification.body() + data: (NSMutableDictionary * _Nullable) notification.data() + scheduleId: notification.id_() + triggerDate: [NSDate dateWithTimeIntervalSince1970: trigger.timestamp() / 1000] ]; resolve(NULL); } @@ -30,16 +33,30 @@ @implementation LocalNotifications reject: (RCTPromiseRejectBlock) reject) { - [NotificationScheduler - scheduleNotificationWithTitle: [notification objectForKey:@"title"] - body: [notification objectForKey:@"body"] - data: (NSMutableDictionary * _Nullable) [notification objectForKey:@"data"] - scheduleId: [notification valueForKey:@"id"] - triggerDate: [NSDate dateWithTimeIntervalSince1970: - [[trigger valueForKey:@"timestamp"] longValue] / 1000 - ] + + if(notification == NULL || trigger == NULL) { + NSString *message = [NSString stringWithFormat:@"%@ Missing notification or trigger config.", TAG]; + reject(@"error", message, NULL); + return; + } + + NSString *title = [notification objectForKey:@"title"]; + if(title == NULL) { + NSString *message = [NSString stringWithFormat:@"%@ Title prop is missing.", TAG]; + reject(@"error", message, NULL); + return; + } + + NSString *scheduleId = [NotificationScheduler + scheduleNotificationWithTitle: title + body: [notification objectForKey:@"body"] + data: (NSMutableDictionary * _Nullable) [notification objectForKey:@"data"] + scheduleId: [notification valueForKey:@"id"] + triggerDate: [NSDate dateWithTimeIntervalSince1970: + [[trigger valueForKey:@"timestamp"] longValue] / 1000 + ] ]; - resolve(NULL); + resolve(scheduleId); } #endif @@ -63,7 +80,7 @@ - (void)cancelAllScheduledNotifications:(RCTPromiseResolveBlock)resolve reject:( // Don't compile this code when we build for the old architecture. #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: - (const facebook::react::ObjCTurboModule::InitParams &)params +(const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); } diff --git a/ios/NotificationScheduler.swift b/ios/NotificationScheduler.swift index 66198cc..4a69ca1 100644 --- a/ios/NotificationScheduler.swift +++ b/ios/NotificationScheduler.swift @@ -6,10 +6,9 @@ import UserNotifications title: String, body: String, data: NSMutableDictionary?, - scheduleId: String, + scheduleId: String?, triggerDate: Date - ) { - + ) -> String { let content = UNMutableNotificationContent() content.title = title content.body = body @@ -26,15 +25,17 @@ import UserNotifications ), repeats: false ) - - let request = UNNotificationRequest(identifier: scheduleId, content: content, trigger: trigger) + let safeScheduleId = scheduleId ?? UUID().uuidString + let request = UNNotificationRequest(identifier: safeScheduleId, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) { error in + let tag = "[react-native-local-notifications]" if let error = error { - print("Error scheduling notification: \(error.localizedDescription)") + print("\(tag) Error scheduling notification: \(error.localizedDescription)") } else { - print("Notification scheduled successfully at " + triggerDate.description) + print("\(tag) Notification scheduled successfully at \(triggerDate.description)") } } + return safeScheduleId } @objc public static func cancelScheduledNotifications(scheduleIds: [String]) { diff --git a/src/NativeLocalNotifications.ts b/src/NativeLocalNotifications.ts index 4e00ee6..90cc9a9 100644 --- a/src/NativeLocalNotifications.ts +++ b/src/NativeLocalNotifications.ts @@ -3,6 +3,7 @@ import { TurboModuleRegistry } from 'react-native'; export interface NotificationAndroid { smallIcon?: string; + color?: string; } export interface Notification {