-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
@@ -110,14 +107,24 @@ CustomNotification({ ] }); ``` -#### ViewOptions + +#### Options +| Property | Description | +| --- | --- | +| `eventData` | sent data will be received when clicked or canceled | +| `title` |Title of the notification| +| `body` |Body of the notification | +| `id` |unique number| +| `View` |View that needs to be added (Array)| + +#### View Options | Property | Description | | --- | --- | | `name` | text that needs to be displayed | | `size` |Size of text| -| `type` |Type of view (Text,Image, Cronometer) | -| `bold` |Font (NORMAL,BOLD,ITALIC,BOLD_ITALIC)| +| `type` |Type of view (Text, Image, Cronometer) | +| `bold` |Font (NORMAL, BOLD,ITALIC, BOLD_ITALIC)| | `uri` |Image in base64| | `PaddingLeft` |Left Padding| | `PaddingTop` |PaddingTop| @@ -131,9 +138,12 @@ CustomNotification({ See the contributing guide to learn how to contribute to the repository and the development workflow. +## 📄 License + +MIT Licensed. See LICENSE file for details. -## License +## 🔍 Keywords -MIT +react-native, notifications, timer-notifications, gif-notifications, android-notifications, custom-notifications, countdown-timer, animated-notifications, react-native-notifications, mobile-notifications, push-notifications, notification-system, react-native-android, notification-timer, countdown-notifications, custom-notification-layout From b8af45673a95f5be97c1300dfd2adbbeb296d3b8 Mon Sep 17 00:00:00 2001 From: Nilavan Raj <58332892+nilavanraj@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:08:44 +0530 Subject: [PATCH 5/8] version bump up (#11) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fa3fe45..0576582 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-custom-timer-notification", - "version": "0.9.0", + "version": "0.9.1", "description": "custom timer notification", "main": "lib/commonjs/index", "module": "lib/module/index", From 27601d7d65cdd31c253f647527a58fb36b3941e1 Mon Sep 17 00:00:00 2001 From: Nilavan Raj <58332892+nilavanraj@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:14:42 +0530 Subject: [PATCH 6/8] Update README.md --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index e61e979..56022ee 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,35 @@ CustomNotification({ | `color` |Text color| | `ZeroTime` |Time at which zero comes| +### Event Handling + +```javascript +import { onEvent } from "react-native-custom-timer-notification"; + +// Listen for notification interactions (press/cancel) +onEvent(event => { + const { action, payload } = event; + + switch(action) { + case 'press': + // Handle notification press/click + console.log('Notification pressed:', payload); + break; + case 'cancel': + // Handle notification dismissal + console.log('Notification cancelled:', payload); + break; + } +}); + +### Remove Notifications + +```javascript +import { RemoveTimer } from "react-native-custom-timer-notification"; + +// Remove a specific notification by ID +RemoveTimer(1); +``` ## 🤝 Contributing From 3eb55c1af2c7c7f31c6ae74f27d2c5dc263b3b4c Mon Sep 17 00:00:00 2001 From: Nilavan Raj <58332892+nilavanraj@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:17:10 +0530 Subject: [PATCH 7/8] Update README.md --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56022ee..982e220 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ onEvent(event => { break; } }); - +``` ### Remove Notifications ```javascript @@ -163,9 +163,12 @@ import { RemoveTimer } from "react-native-custom-timer-notification"; RemoveTimer(1); ``` -## 🤝 Contributing +## ⭐️ Support the Project -See the contributing guide to learn how to contribute to the repository and the development workflow. +If you find this library helpful, please consider: +- Giving it a GitHub star ⭐️ +- Creating issues for bug reports and feature requests +- Contributing with pull requests ## 📄 License From f1b26658efeb3cdce15fc97beb97d45728fee297 Mon Sep 17 00:00:00 2001 From: Nilavan Raj <58332892+nilavanraj@users.noreply.github.com> Date: Sat, 8 Feb 2025 18:20:08 +0530 Subject: [PATCH 8/8] layout fix (#12) --- README.md | 23 +- .../AnimationManager.kt | 29 ++- .../CustomTimerNotificationModule.kt | 60 +++-- .../GiffyHelper.kt | 205 ++++++++++++------ .../main/res/layout/gen_notification_open.xml | 40 ++-- package.json | 2 +- src/index.js | 1 + 7 files changed, 228 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 982e220..b60d832 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,14 @@ TimerNotification({ ```javascript -CustomTimerNotification.TimerNotification({ - id: 2, - title: "Special Offer!", - body: "Limited time offer ends in:", - date: "25-12-2024 23:59:59", - gifUrl: "https://example.com/animation.gif", - payload: "offer-456" +TimerNotification({ + id: 2, + title:
🔥 Limited-Time Deal! Hurry Up! ⏳
, + body:⏳ Time is running out! Claim your exclusive discount before it's too late.
, + subtitle: "💸", + date: new Date(Date.now() + 20000), + giffyUrl: "https://media1.tenor.com/m/EBdqcf-JxpYAAAAC/6m-rain.gif", + payload: "offer-456" }); ``` #### Options @@ -74,10 +75,10 @@ CustomTimerNotification.TimerNotification({ | Parameter | Type | Required | Description | |-----------|----------|----------|---------------------------------| | id | number | Yes | Unique notification identifier | -| title | string | Yes | Notification title | -| body | string | Yes | Notification message | -| date | string | Yes | End date (dd-MM-yyyy HH:mm:ss) | -| gifUrl | string | No | URL to GIF animation | +| title | string | Yes | Notification title with HTML support | +| body | string | Yes | Notification message with HTML support | +| date | Date | No | End date with time (dd-MM-yyyy HH:mm:ss) | +| giffyUrl | string | No | URL to GIF animation | | payload | string | No | Custom data payload | ## Full Custom Notification diff --git a/android/src/main/java/com/reactnativecustomtimernotification/AnimationManager.kt b/android/src/main/java/com/reactnativecustomtimernotification/AnimationManager.kt index e283fa5..0074e70 100644 --- a/android/src/main/java/com/reactnativecustomtimernotification/AnimationManager.kt +++ b/android/src/main/java/com/reactnativecustomtimernotification/AnimationManager.kt @@ -36,7 +36,8 @@ data class NotificationConfig( val subtitle: String?, val smallIcon: Int = android.R.drawable.ic_dialog_info, val countdownDuration: Long = 5000, - val payload: String? + val payload: String?, + val body: String? ) @@ -60,12 +61,11 @@ class AnimatedNotificationManager( try { val extras = intent.extras val params: WritableMap = Arguments.createMap() + params.putString("id", extras!!.getString("id")) params.putString("action", extras!!.getString("action")) params.putString("payload", extras!!.getString("payload")) Log.d(TAG, extras?.getString("payload")?:"") - if(extras!!.getString("action") == "cancel"){ - disableCurrentNotification = true - } + disableCurrentNotification = true context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit( "notificationClick", @@ -115,7 +115,8 @@ class AnimatedNotificationManager( val remoteViews = RemoteViews(context.packageName, R.layout.gen_notification_open) if(config.gifUrl !== null){ - val frames = processGif(config.gifUrl, memoryLimitMB = GIF_MEMORY_LIMIT_MB) + val gifProcessor = GifProcessor() + val frames = gifProcessor.processGif(config.gifUrl, memoryLimitMB = GIF_MEMORY_LIMIT_MB) frames.forEach { frame -> val frameView = RemoteViews(context.packageName, R.layout.giffy_image) @@ -143,25 +144,30 @@ class AnimatedNotificationManager( } } else null - val subtitleHtml = if(config.title != null) { + val bodyHtml = if(config.title != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - Html.fromHtml(config.subtitle, Html.FROM_HTML_MODE_COMPACT) + Html.fromHtml(config.body, Html.FROM_HTML_MODE_COMPACT) } else { - Html.fromHtml(config.subtitle) + Html.fromHtml(config.body) } } else null if(titleHtml != null) remoteViews.setTextViewText(R.id.title, titleHtml) - if(subtitleHtml != null) - remoteViews.setTextViewText(R.id.subtitle, subtitleHtml) + if(bodyHtml != null) + remoteViews.setTextViewText(R.id.body, bodyHtml) } private fun configureChronometer(remoteViews: RemoteViews, countdownDuration: Long) { + if(countdownDuration !== null){ val chronometerBaseTime = countdownDuration remoteViews.setChronometerCountDown(R.id.simpleChronometer, true) remoteViews.setChronometer(R.id.simpleChronometer, chronometerBaseTime, null, true) + } else { + remoteViews.setViewVisibility(R.id.simpleChronometer, View.GONE) + } + } private fun buildNotification(remoteViews: RemoteViews, config: NotificationConfig): NotificationCompat.Builder { @@ -204,6 +210,9 @@ class AnimatedNotificationManager( .setOnlyAlertOnce(true) .setAutoCancel(true) .setDeleteIntent(onDismissPendingIntent) + .apply { + config.subtitle?.let { setSubText(it) } + } .setContentIntent(pendingIntent) diff --git a/android/src/main/java/com/reactnativecustomtimernotification/CustomTimerNotificationModule.kt b/android/src/main/java/com/reactnativecustomtimernotification/CustomTimerNotificationModule.kt index c6c394c..179d534 100644 --- a/android/src/main/java/com/reactnativecustomtimernotification/CustomTimerNotificationModule.kt +++ b/android/src/main/java/com/reactnativecustomtimernotification/CustomTimerNotificationModule.kt @@ -54,29 +54,47 @@ var removedNotification = false; return "CustomTimerNotification" } - @ReactMethod - fun TimerNotification(objectData:ReadableMap) { - val payload = objectData.getString(Constants.NOTIFICATION.PAYLOAD); - val title = objectData.getString(Constants.NOTIFICATION.TITLE); - val body = objectData.getString(Constants.NOTIFICATION.BODY); - val id = objectData.getInt(Constants.NOTIFICATION.ID); - val gifUrl = objectData.getString(Constants.NOTIFICATION.GIFFY_URl) - val notificationHelper = AnimatedNotificationManager(myContext) - - val datetime = objectData.getString("date") - val sdf = SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.ENGLISH) - - val startTime = SystemClock.elapsedRealtime() - val endTime: Calendar = Calendar.getInstance() - endTime.time = sdf.parse(datetime) - - val now = Date() - val elapsed: Long = now.getTime() - endTime.timeInMillis - val remainingTime = startTime - elapsed - - notificationHelper.showAnimatedNotification(NotificationConfig(gifUrl = gifUrl, title=title, subtitle=body, payload=payload, notificationId=id, countdownDuration = remainingTime)) + fun TimerNotification(objectData: ReadableMap) { + val payload = if (objectData.hasKey(Constants.NOTIFICATION.PAYLOAD)) objectData.getString(Constants.NOTIFICATION.PAYLOAD) else "" + val title = if (objectData.hasKey(Constants.NOTIFICATION.TITLE)) objectData.getString(Constants.NOTIFICATION.TITLE) else "Default Title" + val body = if (objectData.hasKey(Constants.NOTIFICATION.BODY)) objectData.getString(Constants.NOTIFICATION.BODY) else "Default Body" + val subtitle = if (objectData.hasKey("subtitle")) objectData.getString("subtitle") else null + val id = if (objectData.hasKey(Constants.NOTIFICATION.ID)) objectData.getInt(Constants.NOTIFICATION.ID) else 0 + val gifUrl = if (objectData.hasKey(Constants.NOTIFICATION.GIFFY_URl)) objectData.getString(Constants.NOTIFICATION.GIFFY_URl) else null + + val datetime = if (objectData.hasKey("date")) objectData.getString("date") else null + val sdf = SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.ENGLISH) + + val endTime: Calendar = Calendar.getInstance() + try { + if (!datetime.isNullOrEmpty()) { + endTime.time = sdf.parse(datetime) ?: Date() + } + } catch (e: Exception) { + Log.e("TimerNotification", "Date parsing failed: ${e.message}") + endTime.time = Date() + } + + val startTime = SystemClock.elapsedRealtime() + val now = System.currentTimeMillis() + val elapsed: Long = now - endTime.timeInMillis + val remainingTime = maxOf(startTime - elapsed, 0L) + + val notificationHelper = AnimatedNotificationManager(myContext) + notificationHelper.showAnimatedNotification( + NotificationConfig( + gifUrl = gifUrl, + title = title, + subtitle = subtitle, + body = body, + payload = payload, + notificationId = id, + countdownDuration = remainingTime + ) + ) } + @ReactMethod diff --git a/android/src/main/java/com/reactnativecustomtimernotification/GiffyHelper.kt b/android/src/main/java/com/reactnativecustomtimernotification/GiffyHelper.kt index 7f4dfe8..53bd258 100644 --- a/android/src/main/java/com/reactnativecustomtimernotification/GiffyHelper.kt +++ b/android/src/main/java/com/reactnativecustomtimernotification/GiffyHelper.kt @@ -12,92 +12,167 @@ import java.io.InputStream import java.io.IOException import kotlin.math.min -private suspend fun fetchGifFromUrl(gifUrl: String): InputStream = withContext(Dispatchers.IO) { - Log.d("GifProcessor", "Fetching GIF from URL: $gifUrl") +class GifProcessor { + companion object { + private const val TAG = "GifProcessor" + private const val MAX_FRAMES = 50 + private const val MIN_FRAME_INTERVAL = 100 + private const val MAX_DIMENSION = 1024 + private const val BYTES_PER_PIXEL = 4L + } - val client = OkHttpClient.Builder() - .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS) - .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS) - .build() + suspend fun fetchGifFromUrl(gifUrl: String): InputStream = withContext(Dispatchers.IO) { + Log.d(TAG, "Starting GIF fetch from URL: $gifUrl") - val request = Request.Builder().url(gifUrl).build() + val client = OkHttpClient.Builder() + .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS) + .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS) + .build() - val response = client.newCall(request).execute() - if (!response.isSuccessful) { - throw IOException("Failed to download GIF: ${response.message}") - } + try { + Log.d(TAG, "Creating request...") + val request = Request.Builder() + .url(gifUrl) + .build() - response.body?.byteStream() ?: throw IOException("No response body available") -} + Log.d(TAG, "Executing request...") + val response = client.newCall(request).execute() -private suspend fun gifToFrames(gifInputStream: InputStream, memoryLimitMB: Int = 2): List