Skip to content

Commit 6cacaaf

Browse files
committed
New giffy notication support added
1 parent a31c1b7 commit 6cacaaf

File tree

15 files changed

+42968
-12715
lines changed

15 files changed

+42968
-12715
lines changed

android/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def getExtOrIntegerDefault(name) {
2929
android {
3030
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
3131
defaultConfig {
32-
minSdkVersion 16
32+
minSdkVersion 21
3333
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
3434
versionCode 1
3535
versionName "1.0"
@@ -45,8 +45,8 @@ android {
4545
disable 'GradleCompatible'
4646
}
4747
compileOptions {
48-
sourceCompatibility JavaVersion.VERSION_1_8
49-
targetCompatibility JavaVersion.VERSION_1_8
48+
sourceCompatibility JavaVersion.VERSION_11
49+
targetCompatibility JavaVersion.VERSION_11
5050
}
5151
}
5252

android/gradle.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
CustomTimerNotification_kotlinVersion=1.3.50
2-
CustomTimerNotification_compileSdkVersion=29
3-
CustomTimerNotification_targetSdkVersion=29
1+
CustomTimerNotification_kotlinVersion=1.8.0
2+
CustomTimerNotification_compileSdkVersion=33
3+
CustomTimerNotification_targetSdkVersion=33
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package com.reactnativecustomtimernotification
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
5+
import android.content.Context
6+
import android.os.Build
7+
import android.os.Handler
8+
import android.os.Looper
9+
import android.os.SystemClock
10+
import android.text.Html
11+
import android.util.Log
12+
import android.view.View
13+
import android.widget.RemoteViews
14+
import androidx.core.app.NotificationCompat
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.launch
18+
import kotlinx.coroutines.withContext
19+
import kotlin.math.abs
20+
import android.app.PendingIntent
21+
import android.content.Intent
22+
23+
import android.content.BroadcastReceiver
24+
import com.facebook.react.bridge.WritableMap
25+
import com.facebook.react.modules.core.DeviceEventManagerModule
26+
import android.content.IntentFilter
27+
import com.facebook.react.bridge.Arguments
28+
import com.facebook.react.bridge.ReactApplicationContext
29+
import androidx.core.content.ContextCompat
30+
31+
32+
data class NotificationConfig(
33+
val notificationId: Int?,
34+
val gifUrl: String?,
35+
val title: String?,
36+
val subtitle: String?,
37+
val smallIcon: Int = android.R.drawable.ic_dialog_info,
38+
val countdownDuration: Long = 5000,
39+
val payload: String?
40+
)
41+
42+
43+
class AnimatedNotificationManager(
44+
private val context: ReactApplicationContext,
45+
private val notificationManager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
46+
) {
47+
companion object {
48+
private const val CHANNEL_ID = "animated_notification_channel"
49+
private const val NOTIFICATION_ID = 1
50+
private const val TAG = "AnimatedNotificationManager"
51+
private const val GIF_MEMORY_LIMIT_MB = 4
52+
private const val FRAME_INTERVAL_MS = 100
53+
}
54+
var disableCurrentNotification:Boolean = false
55+
init {
56+
createNotificationChannel()
57+
58+
context.registerReceiver(object : BroadcastReceiver() {
59+
override fun onReceive(currentContext: Context, intent: Intent) {
60+
try {
61+
val extras = intent.extras
62+
val params: WritableMap = Arguments.createMap()
63+
params.putString("action", extras!!.getString("action"))
64+
params.putString("payload", extras!!.getString("payload"))
65+
Log.d(TAG, extras?.getString("payload")?:"")
66+
if(extras!!.getString("action") == "cancel"){
67+
disableCurrentNotification = true
68+
}
69+
context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
70+
.emit(
71+
"notificationClick",
72+
params
73+
)
74+
} catch (e: Exception) {
75+
Log.i("ReactSystemNotification error", e.toString())
76+
}
77+
}
78+
}, IntentFilter("NotificationEvent"), ContextCompat.RECEIVER_EXPORTED)
79+
}
80+
81+
private fun createNotificationChannel() {
82+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
83+
val channel = NotificationChannel(
84+
CHANNEL_ID,
85+
"Animated Notifications",
86+
NotificationManager.IMPORTANCE_HIGH
87+
).apply {
88+
description = "Channel for animated and interactive notifications"
89+
}
90+
notificationManager.createNotificationChannel(channel)
91+
}
92+
}
93+
94+
fun showAnimatedNotification(config: NotificationConfig) {
95+
val notificationConfig = config
96+
97+
Log.d(TAG, "Initiating animated notification display")
98+
99+
CoroutineScope(Dispatchers.Main).launch {
100+
try {
101+
val notificationLayout = createNotificationLayout(notificationConfig)
102+
val notification = buildNotification(notificationLayout, notificationConfig)
103+
104+
notificationManager.notify(NOTIFICATION_ID, notification.build())
105+
106+
scheduleNotificationUpdate(notificationLayout, notification, notificationConfig)
107+
} catch (e: Exception) {
108+
Log.e(TAG, "Error displaying notification", e)
109+
}
110+
}
111+
}
112+
113+
private suspend fun createNotificationLayout(config: NotificationConfig): RemoteViews = withContext(Dispatchers.Default) {
114+
115+
val remoteViews = RemoteViews(context.packageName, R.layout.gen_notification_open)
116+
117+
if(config.gifUrl !== null){
118+
val frames = processGif(config.gifUrl, memoryLimitMB = GIF_MEMORY_LIMIT_MB)
119+
120+
frames.forEach { frame ->
121+
val frameView = RemoteViews(context.packageName, R.layout.giffy_image)
122+
frameView.setImageViewBitmap(R.id.frameImage, frame)
123+
frameView.setViewVisibility(R.id.frameImage, View.VISIBLE)
124+
remoteViews.addView(R.id.viewFlipper, frameView)
125+
}
126+
remoteViews.setInt(R.id.viewFlipper, "setFlipInterval", FRAME_INTERVAL_MS)
127+
} else {
128+
remoteViews.setViewVisibility(R.id.viewFlipperContainer, View.GONE)
129+
}
130+
131+
configureNotificationText(remoteViews, config)
132+
configureChronometer(remoteViews, config.countdownDuration)
133+
134+
return@withContext remoteViews
135+
}
136+
137+
private fun configureNotificationText(remoteViews: RemoteViews, config: NotificationConfig) {
138+
val titleHtml = if(config.title != null) {
139+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
140+
Html.fromHtml(config.title, Html.FROM_HTML_MODE_COMPACT)
141+
} else {
142+
Html.fromHtml(config.title)
143+
}
144+
} else null
145+
146+
val subtitleHtml = if(config.title != null) {
147+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
148+
Html.fromHtml(config.subtitle, Html.FROM_HTML_MODE_COMPACT)
149+
} else {
150+
Html.fromHtml(config.subtitle)
151+
}
152+
} else null
153+
154+
if(titleHtml != null)
155+
remoteViews.setTextViewText(R.id.title, titleHtml)
156+
157+
if(subtitleHtml != null)
158+
remoteViews.setTextViewText(R.id.subtitle, subtitleHtml)
159+
}
160+
161+
private fun configureChronometer(remoteViews: RemoteViews, countdownDuration: Long) {
162+
val chronometerBaseTime = countdownDuration
163+
remoteViews.setChronometerCountDown(R.id.simpleChronometer, true)
164+
remoteViews.setChronometer(R.id.simpleChronometer, chronometerBaseTime, null, true)
165+
}
166+
167+
private fun buildNotification(remoteViews: RemoteViews, config: NotificationConfig): NotificationCompat.Builder {
168+
val intent = Intent(context, NotificationEventReceiver::class.java)
169+
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
170+
intent.putExtra("id",config.notificationId);
171+
intent.putExtra("action","press");
172+
intent.putExtra("payload",config.payload);
173+
Log.d(TAG, config?.payload ?: "")
174+
var pendingIntent:PendingIntent? = null;
175+
176+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
177+
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE );
178+
} else {
179+
pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
180+
}
181+
182+
val onCancelIntent = Intent(context, OnClickBroadcastReceiver::class.java)
183+
onCancelIntent.putExtra("id",config.notificationId);
184+
onCancelIntent.putExtra("action","cancel");
185+
onCancelIntent.putExtra("payload", config.payload);
186+
var onDismissPendingIntent:PendingIntent? = null;
187+
188+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
189+
onDismissPendingIntent = PendingIntent.getBroadcast(
190+
context,
191+
0,
192+
onCancelIntent,
193+
PendingIntent.FLAG_IMMUTABLE // Set the mutability flag to mutable
194+
);
195+
} else {
196+
onDismissPendingIntent =
197+
PendingIntent.getBroadcast(context, 0, onCancelIntent, 0)
198+
}
199+
200+
return NotificationCompat.Builder(context, CHANNEL_ID)
201+
.setSmallIcon(config.smallIcon)
202+
.setCustomContentView(remoteViews)
203+
.setCustomBigContentView(remoteViews)
204+
.setOnlyAlertOnce(true)
205+
.setAutoCancel(true)
206+
.setDeleteIntent(onDismissPendingIntent)
207+
.setContentIntent(pendingIntent)
208+
209+
210+
}
211+
212+
private fun scheduleNotificationUpdate(
213+
remoteViews: RemoteViews,
214+
notification: NotificationCompat.Builder,
215+
config: NotificationConfig
216+
) {
217+
val chronometerBaseTime = config.countdownDuration
218+
val currentTime = SystemClock.elapsedRealtime()
219+
val delay = abs(chronometerBaseTime - currentTime)
220+
disableCurrentNotification = false
221+
222+
Handler(Looper.getMainLooper()).postDelayed({
223+
if(!disableCurrentNotification){
224+
Log.d(TAG, "Countdown complete, updating notification")
225+
remoteViews.setViewVisibility(R.id.simpleChronometer, View.GONE)
226+
notification.setCustomContentView(remoteViews)
227+
notificationManager.notify(NOTIFICATION_ID, notification.build())
228+
}
229+
}, delay)
230+
}
231+
232+
fun removeNotification (id:Int) {
233+
notificationManager.cancel( id ) ;
234+
}
235+
}

android/src/main/java/com/reactnativecustomtimernotification/Constants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ object Constants {
1010
const val IMAGEVIEW = "ImageView"
1111
const val VIEW = "View"
1212
const val ZEROTIME = "ZeroTime"
13+
const val GIFFY_URl = "giffyUrl"
1314

1415
}
1516
object PADDING {

android/src/main/java/com/reactnativecustomtimernotification/CustomNotificationModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class CustomNotificationModule: ReactContextBaseJavaModule {
183183
val float:Float=item?.getDouble(Constants.VIEW.SIZE)?.toFloat()!!
184184
textView.setTextViewTextSize(R.id.textView1, TypedValue.COMPLEX_UNIT_SP, float);
185185
textView.setTextColor(R.id.textView1,Color.parseColor(item?.getString("color")));
186-
if(item?.getBoolean("setViewVisibility"))
186+
if(item?.getBoolean("setViewVisibility")==true)
187187
textView.setViewVisibility (R.id.textView1,
188188
View.INVISIBLE)
189189
setPadding(item,textView,R.id.textView1);

0 commit comments

Comments
 (0)