react-native-notify-kit 10.1.0 → 10.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -95
- package/android/build.gradle +2 -2
- package/android/src/main/java/app/notifee/core/ForegroundService.java +1 -0
- package/android/src/main/java/app/notifee/core/NotificationManager.java +6 -0
- package/android/src/main/java/app/notifee/core/RebootBroadcastReceiver.java +16 -0
- package/android/src/main/java/app/notifee/core/model/TimestampTriggerModel.java +42 -27
- package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +2 -1
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +6 -6
- package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +154 -0
- package/dist/types/Module.d.ts +7 -7
- package/dist/types/Notification.d.ts +1 -1
- package/dist/types/NotificationIOS.d.ts +44 -21
- package/dist/types/NotificationIOS.js +15 -1
- package/dist/types/NotificationIOS.js.map +1 -1
- package/dist/types/Trigger.d.ts +45 -2
- package/dist/types/Trigger.js +22 -0
- package/dist/types/Trigger.js.map +1 -1
- package/dist/validators/validateIOSCategory.js +2 -2
- package/dist/validators/validateIOSCategory.js.map +1 -1
- package/dist/validators/validateTrigger.js +26 -1
- package/dist/validators/validateTrigger.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m +20 -10
- package/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.h +1 -2
- package/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +44 -35
- package/ios/NotifeeCore/NotifeeCore.m +1035 -99
- package/ios/NotifeeCore/NotifeeCoreUtil.h +36 -1
- package/ios/NotifeeCore/NotifeeCoreUtil.m +533 -9
- package/ios/RNNotifee/NotifeeApiModule.mm +2 -2
- package/package.json +1 -1
- package/src/types/Module.ts +7 -7
- package/src/types/Notification.ts +1 -1
- package/src/types/NotificationIOS.ts +43 -28
- package/src/types/Trigger.ts +45 -1
- package/src/validators/validateIOSCategory.ts +4 -2
- package/src/validators/validateTrigger.ts +38 -0
- package/src/version.ts +1 -1
|
@@ -30,13 +30,13 @@ public class TimestampTriggerModel {
|
|
|
30
30
|
private Boolean mWithAlarmManager = false;
|
|
31
31
|
private AlarmType mAlarmType = AlarmType.SET_EXACT;
|
|
32
32
|
private String mRepeatFrequency = null;
|
|
33
|
+
private int mRepeatInterval = 1;
|
|
33
34
|
private Long mTimestamp = null;
|
|
34
35
|
|
|
35
36
|
public static final String HOURLY = "HOURLY";
|
|
36
37
|
public static final String DAILY = "DAILY";
|
|
37
38
|
public static final String WEEKLY = "WEEKLY";
|
|
38
|
-
|
|
39
|
-
private static final long HOUR_IN_MS = 60L * 60 * 1000;
|
|
39
|
+
public static final String MONTHLY = "MONTHLY";
|
|
40
40
|
|
|
41
41
|
private static final String TAG = "TimeTriggerModel";
|
|
42
42
|
|
|
@@ -47,6 +47,7 @@ public class TimestampTriggerModel {
|
|
|
47
47
|
TimeUnit timeUnit = null;
|
|
48
48
|
if (mTimeTriggerBundle.containsKey("repeatFrequency")) {
|
|
49
49
|
int repeatFrequency = ObjectUtils.getInt(mTimeTriggerBundle.get("repeatFrequency"));
|
|
50
|
+
mRepeatInterval = getRepeatInterval(mTimeTriggerBundle.get("repeatInterval"));
|
|
50
51
|
mTimestamp = ObjectUtils.getLong(mTimeTriggerBundle.get("timestamp"));
|
|
51
52
|
|
|
52
53
|
switch (repeatFrequency) {
|
|
@@ -54,21 +55,24 @@ public class TimestampTriggerModel {
|
|
|
54
55
|
// default value for one-time trigger
|
|
55
56
|
break;
|
|
56
57
|
case 0:
|
|
57
|
-
mInterval =
|
|
58
|
+
mInterval = mRepeatInterval;
|
|
58
59
|
mTimeUnit = TimeUnit.HOURS;
|
|
59
60
|
mRepeatFrequency = HOURLY;
|
|
60
61
|
break;
|
|
61
62
|
case 1:
|
|
62
|
-
mInterval =
|
|
63
|
+
mInterval = mRepeatInterval;
|
|
63
64
|
mTimeUnit = TimeUnit.DAYS;
|
|
64
65
|
mRepeatFrequency = DAILY;
|
|
65
66
|
break;
|
|
66
67
|
case 2:
|
|
67
68
|
// weekly, 7 days
|
|
68
|
-
mInterval = 7;
|
|
69
|
+
mInterval = 7 * mRepeatInterval;
|
|
69
70
|
mTimeUnit = TimeUnit.DAYS;
|
|
70
71
|
mRepeatFrequency = WEEKLY;
|
|
71
72
|
break;
|
|
73
|
+
case 3:
|
|
74
|
+
mRepeatFrequency = MONTHLY;
|
|
75
|
+
break;
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -139,33 +143,27 @@ public class TimestampTriggerModel {
|
|
|
139
143
|
return;
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
|
|
146
|
+
Calendar cal = Calendar.getInstance();
|
|
147
|
+
cal.setTimeInMillis(getTimestamp());
|
|
143
148
|
|
|
149
|
+
int field;
|
|
144
150
|
if (HOURLY.equals(mRepeatFrequency)) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
field = Calendar.HOUR_OF_DAY;
|
|
152
|
+
} else if (DAILY.equals(mRepeatFrequency)) {
|
|
153
|
+
field = Calendar.DAY_OF_MONTH;
|
|
154
|
+
} else if (WEEKLY.equals(mRepeatFrequency)) {
|
|
155
|
+
field = Calendar.WEEK_OF_YEAR;
|
|
156
|
+
} else if (MONTHLY.equals(mRepeatFrequency)) {
|
|
157
|
+
field = Calendar.MONTH;
|
|
150
158
|
} else {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Calendar cal = Calendar.getInstance();
|
|
154
|
-
cal.setTimeInMillis(timestamp);
|
|
155
|
-
|
|
156
|
-
int field;
|
|
157
|
-
if (WEEKLY.equals(mRepeatFrequency)) {
|
|
158
|
-
field = Calendar.WEEK_OF_YEAR;
|
|
159
|
-
} else {
|
|
160
|
-
field = Calendar.DAY_OF_MONTH;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
while (cal.getTimeInMillis() < System.currentTimeMillis()) {
|
|
164
|
-
cal.add(field, 1);
|
|
165
|
-
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
166
161
|
|
|
167
|
-
|
|
162
|
+
while (cal.getTimeInMillis() < System.currentTimeMillis()) {
|
|
163
|
+
cal.add(field, mRepeatInterval);
|
|
168
164
|
}
|
|
165
|
+
|
|
166
|
+
this.mTimestamp = cal.getTimeInMillis();
|
|
169
167
|
}
|
|
170
168
|
|
|
171
169
|
public enum AlarmType {
|
|
@@ -196,6 +194,23 @@ public class TimestampTriggerModel {
|
|
|
196
194
|
return mRepeatFrequency;
|
|
197
195
|
}
|
|
198
196
|
|
|
197
|
+
private int getRepeatInterval(Object repeatInterval) {
|
|
198
|
+
if (!(repeatInterval instanceof Number)) {
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
double interval = ((Number) repeatInterval).doubleValue();
|
|
203
|
+
if (Double.isNaN(interval)
|
|
204
|
+
|| Double.isInfinite(interval)
|
|
205
|
+
|| interval <= 0
|
|
206
|
+
|| interval % 1 != 0
|
|
207
|
+
|| interval > Integer.MAX_VALUE) {
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (int) interval;
|
|
212
|
+
}
|
|
213
|
+
|
|
199
214
|
public Bundle toBundle() {
|
|
200
215
|
Bundle bundle = (Bundle) mTimeTriggerBundle.clone();
|
|
201
216
|
if (mTimestamp != null) {
|
|
@@ -44,6 +44,7 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
|
|
44
44
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
45
45
|
import com.google.common.util.concurrent.SettableFuture;
|
|
46
46
|
import java.util.HashMap;
|
|
47
|
+
import java.util.Locale;
|
|
47
48
|
import java.util.Map;
|
|
48
49
|
|
|
49
50
|
public class ResourceUtils {
|
|
@@ -257,7 +258,7 @@ public class ResourceUtils {
|
|
|
257
258
|
return 0;
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
name = name.toLowerCase().replace("-", "_");
|
|
261
|
+
name = name.toLowerCase(Locale.ROOT).replace("-", "_");
|
|
261
262
|
|
|
262
263
|
String key = name + "_" + type;
|
|
263
264
|
|
|
@@ -157,7 +157,7 @@ class NotifeeApiModule(reactContext: ReactApplicationContext) :
|
|
|
157
157
|
|
|
158
158
|
override fun openAlarmPermissionSettings(promise: Promise) {
|
|
159
159
|
Notifee.getInstance()
|
|
160
|
-
.openAlarmPermissionSettings(getCurrentActivity()) { e, _ ->
|
|
160
|
+
.openAlarmPermissionSettings(getReactApplicationContext().getCurrentActivity()) { e, _ ->
|
|
161
161
|
NotifeeReactUtils.promiseResolver(promise, e)
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -220,7 +220,7 @@ class NotifeeApiModule(reactContext: ReactApplicationContext) :
|
|
|
220
220
|
|
|
221
221
|
override fun getInitialNotification(promise: Promise) {
|
|
222
222
|
Notifee.getInstance()
|
|
223
|
-
.getInitialNotification(getCurrentActivity()) { e, bundle ->
|
|
223
|
+
.getInitialNotification(getReactApplicationContext().getCurrentActivity()) { e, bundle ->
|
|
224
224
|
NotifeeReactUtils.promiseResolver(promise, e, bundle)
|
|
225
225
|
}
|
|
226
226
|
}
|
|
@@ -242,7 +242,7 @@ class NotifeeApiModule(reactContext: ReactApplicationContext) :
|
|
|
242
242
|
return
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
val activity = getCurrentActivity() as? PermissionAwareActivity
|
|
245
|
+
val activity = getReactApplicationContext().getCurrentActivity() as? PermissionAwareActivity
|
|
246
246
|
if (activity == null) {
|
|
247
247
|
Logger.d(
|
|
248
248
|
"requestPermission",
|
|
@@ -277,14 +277,14 @@ class NotifeeApiModule(reactContext: ReactApplicationContext) :
|
|
|
277
277
|
|
|
278
278
|
override fun openNotificationSettings(channelId: String?, promise: Promise) {
|
|
279
279
|
Notifee.getInstance()
|
|
280
|
-
.openNotificationSettings(channelId, getCurrentActivity()) { e, _ ->
|
|
280
|
+
.openNotificationSettings(channelId, getReactApplicationContext().getCurrentActivity()) { e, _ ->
|
|
281
281
|
NotifeeReactUtils.promiseResolver(promise, e)
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
override fun openBatteryOptimizationSettings(promise: Promise) {
|
|
286
286
|
Notifee.getInstance()
|
|
287
|
-
.openBatteryOptimizationSettings(getCurrentActivity()) { e, _ ->
|
|
287
|
+
.openBatteryOptimizationSettings(getReactApplicationContext().getCurrentActivity()) { e, _ ->
|
|
288
288
|
NotifeeReactUtils.promiseResolver(promise, e)
|
|
289
289
|
}
|
|
290
290
|
}
|
|
@@ -305,7 +305,7 @@ class NotifeeApiModule(reactContext: ReactApplicationContext) :
|
|
|
305
305
|
|
|
306
306
|
override fun openPowerManagerSettings(promise: Promise) {
|
|
307
307
|
Notifee.getInstance()
|
|
308
|
-
.openPowerManagerSettings(getCurrentActivity()) { e, _ ->
|
|
308
|
+
.openPowerManagerSettings(getReactApplicationContext().getCurrentActivity()) { e, _ ->
|
|
309
309
|
NotifeeReactUtils.promiseResolver(promise, e)
|
|
310
310
|
}
|
|
311
311
|
}
|
|
@@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue;
|
|
|
7
7
|
import android.os.Bundle;
|
|
8
8
|
import java.util.Calendar;
|
|
9
9
|
import java.util.TimeZone;
|
|
10
|
+
import java.util.concurrent.TimeUnit;
|
|
10
11
|
import org.junit.After;
|
|
11
12
|
import org.junit.Assume;
|
|
12
13
|
import org.junit.Before;
|
|
@@ -23,6 +24,7 @@ public class TimestampTriggerModelTest {
|
|
|
23
24
|
private static final int REPEAT_FREQUENCY_HOURLY = 0;
|
|
24
25
|
private static final int REPEAT_FREQUENCY_DAILY = 1;
|
|
25
26
|
private static final int REPEAT_FREQUENCY_WEEKLY = 2;
|
|
27
|
+
private static final int REPEAT_FREQUENCY_MONTHLY = 3;
|
|
26
28
|
|
|
27
29
|
private TimestampTriggerModel mTimestampTriggerModel = null;
|
|
28
30
|
private TimeZone mOriginalTimeZone;
|
|
@@ -77,6 +79,42 @@ public class TimestampTriggerModelTest {
|
|
|
77
79
|
return TimestampTriggerModel.fromBundle(trigger);
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
private TimestampTriggerModel buildRepeatingTrigger(
|
|
83
|
+
long timestamp, int repeatFrequency, int repeatInterval) {
|
|
84
|
+
Bundle trigger = new Bundle();
|
|
85
|
+
trigger.putLong("timestamp", timestamp);
|
|
86
|
+
trigger.putInt("repeatFrequency", repeatFrequency);
|
|
87
|
+
trigger.putInt("repeatInterval", repeatInterval);
|
|
88
|
+
return TimestampTriggerModel.fromBundle(trigger);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private long expectedNextTimestamp(long timestamp, int field, int repeatInterval) {
|
|
92
|
+
Calendar cal = Calendar.getInstance();
|
|
93
|
+
cal.setTimeInMillis(timestamp);
|
|
94
|
+
while (cal.getTimeInMillis() < System.currentTimeMillis()) {
|
|
95
|
+
cal.add(field, repeatInterval);
|
|
96
|
+
}
|
|
97
|
+
return cal.getTimeInMillis();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test
|
|
101
|
+
public void repeatingTrigger_withoutRepeatInterval_defaultsToOne() {
|
|
102
|
+
TimestampTriggerModel trigger =
|
|
103
|
+
buildRepeatingTrigger(mNow + ONE_DAY_MS, REPEAT_FREQUENCY_DAILY);
|
|
104
|
+
|
|
105
|
+
assertEquals("daily repeat interval should default to 1 day", 1, trigger.getInterval());
|
|
106
|
+
assertEquals("daily repeat time unit should be days", TimeUnit.DAYS, trigger.getTimeUnit());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Test
|
|
110
|
+
public void repeatingTrigger_invalidNativeRepeatInterval_fallsBackToOne() {
|
|
111
|
+
TimestampTriggerModel trigger =
|
|
112
|
+
buildRepeatingTrigger(mNow + ONE_DAY_MS, REPEAT_FREQUENCY_DAILY, 0);
|
|
113
|
+
|
|
114
|
+
assertEquals("invalid native repeatInterval should fall back to 1", 1, trigger.getInterval());
|
|
115
|
+
assertEquals(TimeUnit.DAYS, trigger.getTimeUnit());
|
|
116
|
+
}
|
|
117
|
+
|
|
80
118
|
@Test
|
|
81
119
|
public void setNextTimestamp_daily_advancesToTomorrowSameWallClock() {
|
|
82
120
|
long original = mNow - ONE_MINUTE_MS;
|
|
@@ -101,6 +139,23 @@ public class TimestampTriggerModelTest {
|
|
|
101
139
|
assertEquals("wall-clock second preserved", originalSecond, nextCal.get(Calendar.SECOND));
|
|
102
140
|
}
|
|
103
141
|
|
|
142
|
+
@Test
|
|
143
|
+
public void setNextTimestamp_dailyEveryTwoDays_advancesByRepeatInterval() {
|
|
144
|
+
long original = mNow - ONE_MINUTE_MS;
|
|
145
|
+
TimestampTriggerModel trigger =
|
|
146
|
+
buildRepeatingTrigger(original, REPEAT_FREQUENCY_DAILY, 2);
|
|
147
|
+
|
|
148
|
+
trigger.setNextTimestamp();
|
|
149
|
+
long next = trigger.getTimestamp();
|
|
150
|
+
|
|
151
|
+
assertEquals(
|
|
152
|
+
"daily repeatInterval=2 should use Calendar.add(DAY_OF_MONTH, 2)",
|
|
153
|
+
expectedNextTimestamp(original, Calendar.DAY_OF_MONTH, 2),
|
|
154
|
+
next);
|
|
155
|
+
assertEquals("WorkManager interval should be 2 days", 2, trigger.getInterval());
|
|
156
|
+
assertEquals("WorkManager time unit should be days", TimeUnit.DAYS, trigger.getTimeUnit());
|
|
157
|
+
}
|
|
158
|
+
|
|
104
159
|
@Test
|
|
105
160
|
public void setNextTimestamp_daily_multipleMissedFires_skipsToFuture() {
|
|
106
161
|
// Offset by 1 minute so the original does not land exactly on a multiple of 24h from mNow.
|
|
@@ -116,6 +171,23 @@ public class TimestampTriggerModelTest {
|
|
|
116
171
|
assertTrue("next timestamp must not be more than 25h ahead", next < mNow + 25L * ONE_HOUR_MS);
|
|
117
172
|
}
|
|
118
173
|
|
|
174
|
+
@Test
|
|
175
|
+
public void setNextTimestamp_weeklyEveryTwoWeeks_advancesByRepeatInterval() {
|
|
176
|
+
long original = mNow - ONE_MINUTE_MS;
|
|
177
|
+
TimestampTriggerModel trigger =
|
|
178
|
+
buildRepeatingTrigger(original, REPEAT_FREQUENCY_WEEKLY, 2);
|
|
179
|
+
|
|
180
|
+
trigger.setNextTimestamp();
|
|
181
|
+
long next = trigger.getTimestamp();
|
|
182
|
+
|
|
183
|
+
assertEquals(
|
|
184
|
+
"weekly repeatInterval=2 should use Calendar.add(WEEK_OF_YEAR, 2)",
|
|
185
|
+
expectedNextTimestamp(original, Calendar.WEEK_OF_YEAR, 2),
|
|
186
|
+
next);
|
|
187
|
+
assertEquals("WorkManager interval should be 14 days", 14, trigger.getInterval());
|
|
188
|
+
assertEquals("WorkManager time unit should be days", TimeUnit.DAYS, trigger.getTimeUnit());
|
|
189
|
+
}
|
|
190
|
+
|
|
119
191
|
@Test
|
|
120
192
|
public void setNextTimestamp_weekly_advancesOneWeek() {
|
|
121
193
|
long original = mNow - ONE_MINUTE_MS;
|
|
@@ -130,6 +202,50 @@ public class TimestampTriggerModelTest {
|
|
|
130
202
|
assertTrue("weekly next timestamp must be <= now + 7d1h", next <= upper);
|
|
131
203
|
}
|
|
132
204
|
|
|
205
|
+
@Test
|
|
206
|
+
public void setNextTimestamp_monthlyEveryThreeMonths_advancesByRepeatInterval() {
|
|
207
|
+
long original = mNow - ONE_MINUTE_MS;
|
|
208
|
+
TimestampTriggerModel trigger =
|
|
209
|
+
buildRepeatingTrigger(original, REPEAT_FREQUENCY_MONTHLY, 3);
|
|
210
|
+
|
|
211
|
+
trigger.setNextTimestamp();
|
|
212
|
+
long next = trigger.getTimestamp();
|
|
213
|
+
|
|
214
|
+
assertEquals(
|
|
215
|
+
"monthly repeatInterval=3 should use Calendar.add(MONTH, 3)",
|
|
216
|
+
expectedNextTimestamp(original, Calendar.MONTH, 3),
|
|
217
|
+
next);
|
|
218
|
+
assertEquals(TimestampTriggerModel.MONTHLY, trigger.getRepeatFrequency());
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Test
|
|
222
|
+
public void setNextTimestamp_monthlyEndOfMonth_usesCalendarClampSemantics() {
|
|
223
|
+
TimeZone utc = TimeZone.getTimeZone("UTC");
|
|
224
|
+
TimeZone.setDefault(utc);
|
|
225
|
+
|
|
226
|
+
Calendar start = Calendar.getInstance(utc);
|
|
227
|
+
start.clear();
|
|
228
|
+
start.set(2020, Calendar.JANUARY, 31, 12, 45, 0);
|
|
229
|
+
long original = start.getTimeInMillis();
|
|
230
|
+
|
|
231
|
+
TimestampTriggerModel trigger =
|
|
232
|
+
buildRepeatingTrigger(original, REPEAT_FREQUENCY_MONTHLY, 1);
|
|
233
|
+
|
|
234
|
+
trigger.setNextTimestamp();
|
|
235
|
+
long next = trigger.getTimestamp();
|
|
236
|
+
|
|
237
|
+
assertEquals(
|
|
238
|
+
"monthly end-of-month should match native Calendar.add clamp behavior",
|
|
239
|
+
expectedNextTimestamp(original, Calendar.MONTH, 1),
|
|
240
|
+
next);
|
|
241
|
+
|
|
242
|
+
Calendar nextCal = Calendar.getInstance(utc);
|
|
243
|
+
nextCal.setTimeInMillis(next);
|
|
244
|
+
assertTrue(
|
|
245
|
+
"Calendar.add should clamp the Jan 31 anchor before future monthly repeats",
|
|
246
|
+
nextCal.get(Calendar.DAY_OF_MONTH) < 31);
|
|
247
|
+
}
|
|
248
|
+
|
|
133
249
|
@Test
|
|
134
250
|
public void setNextTimestamp_hourly_advancesOneHour() {
|
|
135
251
|
long original = mNow - ONE_MINUTE_MS;
|
|
@@ -144,6 +260,44 @@ public class TimestampTriggerModelTest {
|
|
|
144
260
|
assertTrue("hourly next timestamp must be <= now + 61m", next <= upper);
|
|
145
261
|
}
|
|
146
262
|
|
|
263
|
+
@Test
|
|
264
|
+
public void setNextTimestamp_dailyEveryTwoDaysAcrossDstSpringForward() {
|
|
265
|
+
TimeZone rome = TimeZone.getTimeZone("Europe/Rome");
|
|
266
|
+
TimeZone.setDefault(rome);
|
|
267
|
+
|
|
268
|
+
Calendar start = Calendar.getInstance(rome);
|
|
269
|
+
start.clear();
|
|
270
|
+
start.set(2026, Calendar.MARCH, 28, 4, 30, 0);
|
|
271
|
+
long originalTimestamp = start.getTimeInMillis();
|
|
272
|
+
|
|
273
|
+
Calendar windowStart = Calendar.getInstance(rome);
|
|
274
|
+
windowStart.clear();
|
|
275
|
+
windowStart.set(2026, Calendar.MARCH, 31, 0, 0, 0);
|
|
276
|
+
Calendar windowEnd = Calendar.getInstance(rome);
|
|
277
|
+
windowEnd.clear();
|
|
278
|
+
windowEnd.set(2026, Calendar.OCTOBER, 24, 23, 59, 59);
|
|
279
|
+
long now = System.currentTimeMillis();
|
|
280
|
+
Assume.assumeTrue(
|
|
281
|
+
"spring-forward repeatInterval discrimination requires current time in"
|
|
282
|
+
+ " [2026-03-31, 2026-10-24] Europe/Rome",
|
|
283
|
+
now >= windowStart.getTimeInMillis() && now <= windowEnd.getTimeInMillis());
|
|
284
|
+
|
|
285
|
+
TimestampTriggerModel trigger =
|
|
286
|
+
buildRepeatingTrigger(originalTimestamp, REPEAT_FREQUENCY_DAILY, 2);
|
|
287
|
+
trigger.setNextTimestamp();
|
|
288
|
+
long next = trigger.getTimestamp();
|
|
289
|
+
|
|
290
|
+
Calendar nextCal = Calendar.getInstance(rome);
|
|
291
|
+
nextCal.setTimeInMillis(next);
|
|
292
|
+
assertTrue("next timestamp must be >= now", next >= now);
|
|
293
|
+
assertEquals(
|
|
294
|
+
"wall-clock hour must remain 4 with repeatInterval=2",
|
|
295
|
+
4,
|
|
296
|
+
nextCal.get(Calendar.HOUR_OF_DAY));
|
|
297
|
+
assertEquals("wall-clock minute must remain 30", 30, nextCal.get(Calendar.MINUTE));
|
|
298
|
+
assertEquals("wall-clock second must remain 0", 0, nextCal.get(Calendar.SECOND));
|
|
299
|
+
}
|
|
300
|
+
|
|
147
301
|
@Test
|
|
148
302
|
public void setNextTimestamp_dailyAcrossDstSpringForward() {
|
|
149
303
|
// Europe/Rome spring-forward 2026: 2026-03-29 02:00 local jumps to 03:00 local. The 29
|
package/dist/types/Module.d.ts
CHANGED
|
@@ -490,20 +490,20 @@ export interface Module {
|
|
|
490
490
|
/**
|
|
491
491
|
* API used to configure iOS notification handling behavior.
|
|
492
492
|
*
|
|
493
|
-
* Use this to control whether
|
|
493
|
+
* Use this to control whether Notify Kit should handle remote (push) notifications
|
|
494
494
|
* on iOS. When `handleRemoteNotifications` is set to `false`, remote notifications
|
|
495
495
|
* (e.g. from Firebase Cloud Messaging) will be forwarded to the original
|
|
496
|
-
* notification delegate instead of being processed by
|
|
496
|
+
* notification delegate instead of being processed by Notify Kit.
|
|
497
497
|
*
|
|
498
498
|
* This allows libraries like React Native Firebase Messaging to receive tap events
|
|
499
499
|
* via `onNotificationOpenedApp()` and `getInitialNotification()`.
|
|
500
500
|
*
|
|
501
|
-
* Defaults to `true` (
|
|
501
|
+
* Defaults to `true` (Notify Kit handles all notifications) for backward compatibility.
|
|
502
502
|
*
|
|
503
503
|
* ```js
|
|
504
504
|
* import notifee from 'react-native-notify-kit';
|
|
505
505
|
*
|
|
506
|
-
* // Disable
|
|
506
|
+
* // Disable Notify Kit handling of remote notifications
|
|
507
507
|
* await notifee.setNotificationConfig({
|
|
508
508
|
* ios: { handleRemoteNotifications: false },
|
|
509
509
|
* });
|
|
@@ -606,8 +606,8 @@ export interface Module {
|
|
|
606
606
|
*/
|
|
607
607
|
hideNotificationDrawer(): void;
|
|
608
608
|
/**
|
|
609
|
-
* Processes an FCM remote message produced by the
|
|
610
|
-
* displays a
|
|
609
|
+
* Processes an FCM remote message produced by the Notify Kit server SDK and
|
|
610
|
+
* displays a Notify Kit notification according to the embedded `notifee_options`.
|
|
611
611
|
*
|
|
612
612
|
* Safe to call from both `setBackgroundMessageHandler` and `onMessage`.
|
|
613
613
|
*
|
|
@@ -637,7 +637,7 @@ export interface Module {
|
|
|
637
637
|
*/
|
|
638
638
|
export interface ModuleStatics {
|
|
639
639
|
/**
|
|
640
|
-
* Returns the current
|
|
640
|
+
* Returns the current Notify Kit SDK version in use.
|
|
641
641
|
*/
|
|
642
642
|
SDK_VERSION: string;
|
|
643
643
|
}
|
|
@@ -167,7 +167,7 @@ export interface TriggerNotification {
|
|
|
167
167
|
trigger: Trigger;
|
|
168
168
|
}
|
|
169
169
|
/**
|
|
170
|
-
* An interface representing a
|
|
170
|
+
* An interface representing a Notify Kit event.
|
|
171
171
|
*
|
|
172
172
|
* View the [Events](/react-native/events) documentation to learn more about foreground and
|
|
173
173
|
* background events.
|
|
@@ -66,24 +66,19 @@ export interface NotificationIOS {
|
|
|
66
66
|
*/
|
|
67
67
|
threadId?: string;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* See `IOSCategory.summaryFormat`.
|
|
69
|
+
* Kept for compatibility. No-op on supported iOS versions because notification summary
|
|
70
|
+
* arguments are ignored by iOS 15+.
|
|
72
71
|
*
|
|
73
72
|
* @platform ios iOS >= 12
|
|
73
|
+
* @deprecated May be removed in a future major release.
|
|
74
74
|
*/
|
|
75
75
|
summaryArgument?: string;
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* For example if a messages app sends one notification for 3 new messages in a group chat,
|
|
80
|
-
* the summaryArgument could be the name of the group chat and the summaryArgumentCount should be 3.
|
|
81
|
-
*
|
|
82
|
-
* If set, value cannot be 0 or less.
|
|
83
|
-
*
|
|
84
|
-
* See `IOSCategory.summaryFormat`.
|
|
77
|
+
* Kept for compatibility. No-op on supported iOS versions because notification summary
|
|
78
|
+
* arguments are ignored by iOS 15+.
|
|
85
79
|
*
|
|
86
80
|
* @platform ios iOS >= 12
|
|
81
|
+
* @deprecated May be removed in a future major release.
|
|
87
82
|
*/
|
|
88
83
|
summaryArgumentCount?: number;
|
|
89
84
|
/**
|
|
@@ -97,7 +92,7 @@ export interface NotificationIOS {
|
|
|
97
92
|
/**
|
|
98
93
|
* Optional property to customise how notifications are presented when the app is in the foreground.
|
|
99
94
|
*
|
|
100
|
-
* By default,
|
|
95
|
+
* By default, Notify Kit will show iOS notifications in heads-up mode if your app is currently in the foreground.
|
|
101
96
|
*/
|
|
102
97
|
foregroundPresentationOptions?: IOSForegroundPresentationOptions;
|
|
103
98
|
/**
|
|
@@ -127,7 +122,7 @@ export interface IOSCommunicationInfoPerson {
|
|
|
127
122
|
/**
|
|
128
123
|
* An interface to customise how notifications are shown when the app is in the foreground.
|
|
129
124
|
*
|
|
130
|
-
* By default,
|
|
125
|
+
* By default, Notify Kit will show iOS notifications in heads-up mode if your app is currently in the foreground.
|
|
131
126
|
*
|
|
132
127
|
* View the [Foreground Notifications](/react-native/ios/appearance#foreground-notifications) to learn
|
|
133
128
|
* more.
|
|
@@ -157,16 +152,12 @@ export interface IOSForegroundPresentationOptions {
|
|
|
157
152
|
/**
|
|
158
153
|
* Present the notification as a banner
|
|
159
154
|
*
|
|
160
|
-
* For iOS 13 and lower, will be equivalent to setting `alert` to true
|
|
161
|
-
*
|
|
162
155
|
* Defaults to true
|
|
163
156
|
*/
|
|
164
157
|
banner?: boolean;
|
|
165
158
|
/**
|
|
166
159
|
* Show the notification in Notification Center
|
|
167
160
|
*
|
|
168
|
-
* For iOS 13 and lower, will be equivalent to setting `alert` to true
|
|
169
|
-
*
|
|
170
161
|
* Defaults to true
|
|
171
162
|
*/
|
|
172
163
|
list?: boolean;
|
|
@@ -223,11 +214,13 @@ export interface IOSNotificationPermissions {
|
|
|
223
214
|
*/
|
|
224
215
|
provisional?: boolean;
|
|
225
216
|
/**
|
|
226
|
-
*
|
|
217
|
+
* Explicit announcement authorization requests are no longer needed because announcement
|
|
218
|
+
* authorization is included by iOS on supported versions. Kept for API compatibility.
|
|
227
219
|
*
|
|
228
220
|
* Defaults to false.
|
|
229
221
|
*
|
|
230
222
|
* @platform ios iOS >= 13
|
|
223
|
+
* @deprecated May be removed in a future major release.
|
|
231
224
|
*/
|
|
232
225
|
announcement?: boolean;
|
|
233
226
|
}
|
|
@@ -339,12 +332,26 @@ export interface IOSNotificationSettings {
|
|
|
339
332
|
authorizationStatus: AuthorizationStatus;
|
|
340
333
|
}
|
|
341
334
|
/**
|
|
342
|
-
*
|
|
335
|
+
* Intent identifiers used to provide category context to Siri.
|
|
343
336
|
*
|
|
344
337
|
* @platform ios
|
|
345
338
|
*/
|
|
346
339
|
export declare enum IOSIntentIdentifier {
|
|
340
|
+
/**
|
|
341
|
+
* Start a call intent.
|
|
342
|
+
*
|
|
343
|
+
* Maps to Apple's unified `INStartCallIntentIdentifier`.
|
|
344
|
+
*/
|
|
345
|
+
START_CALL = 25,
|
|
346
|
+
/**
|
|
347
|
+
* @deprecated Use `START_CALL` instead. Apple deprecated the separate audio call
|
|
348
|
+
* intent identifier in favor of a unified start call identifier.
|
|
349
|
+
*/
|
|
347
350
|
START_AUDIO_CALL = 0,
|
|
351
|
+
/**
|
|
352
|
+
* @deprecated Use `START_CALL` instead. Apple deprecated the separate video call
|
|
353
|
+
* intent identifier in favor of a unified start call identifier.
|
|
354
|
+
*/
|
|
348
355
|
START_VIDEO_CALL = 1,
|
|
349
356
|
SEARCH_CALL_HISTORY = 2,
|
|
350
357
|
SET_AUDIO_SOURCE_IN_CAR = 3,
|
|
@@ -396,10 +403,26 @@ export interface IOSNotificationCategory {
|
|
|
396
403
|
* Defaults to `false`.
|
|
397
404
|
*/
|
|
398
405
|
allowInCarPlay?: boolean;
|
|
406
|
+
/**
|
|
407
|
+
* Kept for compatibility. No-op on supported iOS versions because announcement category
|
|
408
|
+
* options are ignored by iOS 15+.
|
|
409
|
+
*
|
|
410
|
+
* Defaults to `false`.
|
|
411
|
+
*
|
|
412
|
+
* @deprecated May be removed in a future major release.
|
|
413
|
+
*/
|
|
399
414
|
allowAnnouncement?: boolean;
|
|
400
415
|
hiddenPreviewsShowTitle?: boolean;
|
|
401
416
|
hiddenPreviewsShowSubtitle?: boolean;
|
|
402
417
|
hiddenPreviewsBodyPlaceholder?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Intent identifiers used by the system to provide category context to Siri.
|
|
420
|
+
*
|
|
421
|
+
* Use `IOSIntentIdentifier.START_CALL` for call categories. Legacy
|
|
422
|
+
* `START_AUDIO_CALL` and `START_VIDEO_CALL` values remain accepted for source
|
|
423
|
+
* compatibility, but both map to Apple's unified start call intent identifier.
|
|
424
|
+
* Categories read back from iOS can return `START_CALL` for call intents.
|
|
425
|
+
*/
|
|
403
426
|
intentIdentifiers?: IOSIntentIdentifier[];
|
|
404
427
|
actions?: IOSNotificationCategoryAction[];
|
|
405
428
|
}
|
|
@@ -544,11 +567,11 @@ export type IOSNotificationInterruptionLevel = 'active' | 'critical' | 'passive'
|
|
|
544
567
|
*/
|
|
545
568
|
export interface IOSNotificationConfig {
|
|
546
569
|
/**
|
|
547
|
-
* Whether
|
|
570
|
+
* Whether Notify Kit should handle remote (push) notifications on iOS.
|
|
548
571
|
*
|
|
549
572
|
* When set to `false`, remote notifications (e.g. from Firebase Cloud Messaging)
|
|
550
573
|
* will be forwarded to the original notification delegate instead of being
|
|
551
|
-
* processed by
|
|
574
|
+
* processed by Notify Kit. This allows libraries like React Native Firebase
|
|
552
575
|
* Messaging to receive tap events via `onNotificationOpenedApp()` and
|
|
553
576
|
* `getInitialNotification()`.
|
|
554
577
|
*
|
|
@@ -54,13 +54,27 @@ export var IOSNotificationSetting;
|
|
|
54
54
|
IOSNotificationSetting[IOSNotificationSetting["ENABLED"] = 1] = "ENABLED";
|
|
55
55
|
})(IOSNotificationSetting || (IOSNotificationSetting = {}));
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* Intent identifiers used to provide category context to Siri.
|
|
58
58
|
*
|
|
59
59
|
* @platform ios
|
|
60
60
|
*/
|
|
61
61
|
export var IOSIntentIdentifier;
|
|
62
62
|
(function (IOSIntentIdentifier) {
|
|
63
|
+
/**
|
|
64
|
+
* Start a call intent.
|
|
65
|
+
*
|
|
66
|
+
* Maps to Apple's unified `INStartCallIntentIdentifier`.
|
|
67
|
+
*/
|
|
68
|
+
IOSIntentIdentifier[IOSIntentIdentifier["START_CALL"] = 25] = "START_CALL";
|
|
69
|
+
/**
|
|
70
|
+
* @deprecated Use `START_CALL` instead. Apple deprecated the separate audio call
|
|
71
|
+
* intent identifier in favor of a unified start call identifier.
|
|
72
|
+
*/
|
|
63
73
|
IOSIntentIdentifier[IOSIntentIdentifier["START_AUDIO_CALL"] = 0] = "START_AUDIO_CALL";
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated Use `START_CALL` instead. Apple deprecated the separate video call
|
|
76
|
+
* intent identifier in favor of a unified start call identifier.
|
|
77
|
+
*/
|
|
64
78
|
IOSIntentIdentifier[IOSIntentIdentifier["START_VIDEO_CALL"] = 1] = "START_VIDEO_CALL";
|
|
65
79
|
IOSIntentIdentifier[IOSIntentIdentifier["SEARCH_CALL_HISTORY"] = 2] = "SEARCH_CALL_HISTORY";
|
|
66
80
|
IOSIntentIdentifier[IOSIntentIdentifier["SET_AUDIO_SOURCE_IN_CAR"] = 3] = "SET_AUDIO_SOURCE_IN_CAR";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationIOS.js","sourceRoot":"","sources":["../../src/types/NotificationIOS.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"NotificationIOS.js","sourceRoot":"","sources":["../../src/types/NotificationIOS.ts"],"names":[],"mappings":"AAAA;;GAEG;AA6QH;;;;;;;GAOG;AACH,MAAM,CAAN,IAAY,sBAqBX;AArBD,WAAY,sBAAsB;IAChC;;;OAGG;IACH,sFAAkB,CAAA;IAElB;;OAEG;IACH,qEAAS,CAAA;IAET;;OAEG;IACH,uEAAU,CAAA;IAEV;;OAEG;IACH,+FAAsB,CAAA;AACxB,CAAC,EArBW,sBAAsB,KAAtB,sBAAsB,QAqBjC;AAED;;;;;;;GAOG;AACH,MAAM,CAAN,IAAY,sBAgBX;AAhBD,WAAY,sBAAsB;IAChC;;;OAGG;IACH,sFAAkB,CAAA;IAElB;;OAEG;IACH,2EAAY,CAAA;IAEZ;;OAEG;IACH,yEAAW,CAAA;AACb,CAAC,EAhBW,sBAAsB,KAAtB,sBAAsB,QAgBjC;AAsED;;;;GAIG;AACH,MAAM,CAAN,IAAY,mBAiEX;AAjED,WAAY,mBAAmB;IAC7B;;;;OAIG;IACH,0EAAe,CAAA;IAEf;;;OAGG;IACH,qFAAoB,CAAA;IAEpB;;;OAGG;IACH,qFAAoB,CAAA;IAEpB,2FAAuB,CAAA;IAEvB,mGAA2B,CAAA;IAE3B,2GAA+B,CAAA;IAE/B,+GAAiC,CAAA;IAEjC,qGAA4B,CAAA;IAE5B,yFAAsB,CAAA;IAEtB,2FAAuB,CAAA;IAEvB,+EAAiB,CAAA;IAEjB,gFAAkB,CAAA;IAElB,4EAAgB,CAAA;IAEhB,kFAAmB,CAAA;IAEnB,kFAAmB,CAAA;IAEnB,wFAAsB,CAAA;IAEtB,8EAAiB,CAAA;IAEjB,4FAAwB,CAAA;IAExB,gGAA0B,CAAA;IAE1B,8EAAiB,CAAA;IAEjB,oFAAoB,CAAA;IAEpB,wFAAsB,CAAA;IAEtB,8FAAyB,CAAA;IAEzB,wFAAsB,CAAA;IAEtB,8EAAiB,CAAA;IAEjB,oFAAoB,CAAA;AACtB,CAAC,EAjEW,mBAAmB,KAAnB,mBAAmB,QAiE9B"}
|