html2apk 0.1.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 +472 -0
- package/bin/html2apk-desktop.js +23 -0
- package/bin/html2apk.js +19 -0
- package/examples/minimal/app.json +27 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/examples/minimal/index.html +41 -0
- package/html2apk.png +0 -0
- package/index.js +3 -0
- package/package.json +76 -0
- package/src/android/README.md +7 -0
- package/src/bridge/install-bridge.js +16 -0
- package/src/cli/index.js +163 -0
- package/src/cordova/apk-finder.js +45 -0
- package/src/cordova/config-xml.js +110 -0
- package/src/cordova/project.js +56 -0
- package/src/core/build-apk.js +189 -0
- package/src/core/config.js +99 -0
- package/src/core/defaults.js +37 -0
- package/src/core/validation.js +58 -0
- package/src/desktop/main.js +522 -0
- package/src/desktop/preload.js +30 -0
- package/src/desktop/renderer/index.html +323 -0
- package/src/desktop/renderer/renderer.js +1074 -0
- package/src/desktop/renderer/styles.css +1208 -0
- package/src/index.js +12 -0
- package/src/runtime-manager/doctor.js +164 -0
- package/src/runtime-manager/index.js +190 -0
- package/src/templates/cordova-plugin-html2apk-bridge/package.json +16 -0
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +39 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/BootReceiver.java +20 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +375 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationReceiver.java +112 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationStore.java +91 -0
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +129 -0
- package/src/utils/command-runner.js +124 -0
- package/src/utils/fs-extra.js +111 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
package dev.html2apk.bridge;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.Activity;
|
|
5
|
+
import android.app.AlarmManager;
|
|
6
|
+
import android.app.NotificationChannel;
|
|
7
|
+
import android.app.NotificationManager;
|
|
8
|
+
import android.app.PendingIntent;
|
|
9
|
+
import android.content.Context;
|
|
10
|
+
import android.content.Intent;
|
|
11
|
+
import android.content.pm.PackageManager;
|
|
12
|
+
import android.net.Uri;
|
|
13
|
+
import android.os.Build;
|
|
14
|
+
import android.os.VibrationEffect;
|
|
15
|
+
import android.os.Vibrator;
|
|
16
|
+
import android.provider.Settings;
|
|
17
|
+
import android.view.View;
|
|
18
|
+
import android.widget.Toast;
|
|
19
|
+
|
|
20
|
+
import androidx.core.app.NotificationCompat;
|
|
21
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
22
|
+
|
|
23
|
+
import org.apache.cordova.CallbackContext;
|
|
24
|
+
import org.apache.cordova.CordovaInterface;
|
|
25
|
+
import org.apache.cordova.CordovaPlugin;
|
|
26
|
+
import org.apache.cordova.CordovaWebView;
|
|
27
|
+
import org.json.JSONArray;
|
|
28
|
+
import org.json.JSONObject;
|
|
29
|
+
|
|
30
|
+
public class Html2ApkBridge extends CordovaPlugin {
|
|
31
|
+
static final String CHANNEL_ID = "html2apk_default";
|
|
32
|
+
static final String EXTRA_NOTIFICATION_CLICKED = "html2apk_notification_clicked";
|
|
33
|
+
static final String EXTRA_NOTIFICATION_DETAIL = "html2apk_notification_detail";
|
|
34
|
+
|
|
35
|
+
private static final int REQUEST_POST_NOTIFICATIONS = 7311;
|
|
36
|
+
|
|
37
|
+
private CallbackContext notificationPermissionCallback;
|
|
38
|
+
private JSONObject initialNotification;
|
|
39
|
+
|
|
40
|
+
@Override
|
|
41
|
+
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
|
42
|
+
super.initialize(cordova, webView);
|
|
43
|
+
handleNotificationIntent(cordova.getActivity().getIntent(), false);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Override
|
|
47
|
+
public void onNewIntent(Intent intent) {
|
|
48
|
+
handleNotificationIntent(intent, true);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Override
|
|
52
|
+
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
|
|
53
|
+
try {
|
|
54
|
+
if ("notify".equals(action)) {
|
|
55
|
+
JSONObject options = args.optJSONObject(0);
|
|
56
|
+
showNotification(options == null ? new JSONObject() : options);
|
|
57
|
+
callbackContext.success();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ("scheduleNotification".equals(action)) {
|
|
62
|
+
JSONObject options = args.optJSONObject(0);
|
|
63
|
+
scheduleNotification(options == null ? new JSONObject() : options);
|
|
64
|
+
callbackContext.success();
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ("vibrate".equals(action)) {
|
|
69
|
+
vibrate(args.optLong(0, 200));
|
|
70
|
+
callbackContext.success();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if ("toast".equals(action)) {
|
|
75
|
+
toast(args.optString(0, ""));
|
|
76
|
+
callbackContext.success();
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ("fullscreen".equals(action)) {
|
|
81
|
+
setFullscreen(args.optBoolean(0, true));
|
|
82
|
+
callbackContext.success();
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if ("requestNotificationPermission".equals(action)) {
|
|
87
|
+
requestNotificationPermission(callbackContext);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ("notificationPermissionStatus".equals(action)) {
|
|
92
|
+
callbackContext.success(notificationPermissionStatus());
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ("canScheduleExactAlarms".equals(action)) {
|
|
97
|
+
callbackContext.success(canScheduleExactAlarms() ? 1 : 0);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ("openExactAlarmSettings".equals(action)) {
|
|
102
|
+
openExactAlarmSettings();
|
|
103
|
+
callbackContext.success();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ("getInitialNotification".equals(action)) {
|
|
108
|
+
callbackContext.success(initialNotification == null ? new JSONObject() : initialNotification);
|
|
109
|
+
initialNotification = null;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
} catch (Exception error) {
|
|
113
|
+
callbackContext.error(error.getMessage());
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Override
|
|
121
|
+
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
122
|
+
if (requestCode != REQUEST_POST_NOTIFICATIONS || notificationPermissionCallback == null) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
127
|
+
try {
|
|
128
|
+
JSONObject result = notificationPermissionStatus();
|
|
129
|
+
result.put("requested", true);
|
|
130
|
+
result.put("granted", granted);
|
|
131
|
+
notificationPermissionCallback.success(result);
|
|
132
|
+
} catch (Exception error) {
|
|
133
|
+
notificationPermissionCallback.error(error.getMessage());
|
|
134
|
+
} finally {
|
|
135
|
+
notificationPermissionCallback = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private Context context() {
|
|
140
|
+
return this.cordova.getActivity().getApplicationContext();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static void ensureNotificationChannel(Context context) {
|
|
144
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
145
|
+
NotificationChannel channel = new NotificationChannel(
|
|
146
|
+
CHANNEL_ID,
|
|
147
|
+
"html2apk",
|
|
148
|
+
NotificationManager.IMPORTANCE_DEFAULT
|
|
149
|
+
);
|
|
150
|
+
channel.setDescription("Default channel for html2apk notifications.");
|
|
151
|
+
NotificationManager manager = context.getSystemService(NotificationManager.class);
|
|
152
|
+
if (manager != null) {
|
|
153
|
+
manager.createNotificationChannel(channel);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private boolean hasNotificationPermission() {
|
|
159
|
+
return Build.VERSION.SDK_INT < 33
|
|
160
|
+
|| context().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private void requestNotificationPermission(CallbackContext callbackContext) throws Exception {
|
|
164
|
+
if (Build.VERSION.SDK_INT < 33 || hasNotificationPermission()) {
|
|
165
|
+
callbackContext.success(notificationPermissionStatus());
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
notificationPermissionCallback = callbackContext;
|
|
170
|
+
cordova.requestPermission(this, REQUEST_POST_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private JSONObject notificationPermissionStatus() throws Exception {
|
|
174
|
+
JSONObject result = new JSONObject();
|
|
175
|
+
result.put("required", Build.VERSION.SDK_INT >= 33);
|
|
176
|
+
result.put("granted", hasNotificationPermission());
|
|
177
|
+
result.put("permission", "android.permission.POST_NOTIFICATIONS");
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private void showNotification(JSONObject options) throws Exception {
|
|
182
|
+
if (!hasNotificationPermission()) {
|
|
183
|
+
throw new Exception("POST_NOTIFICATIONS permission is not granted. Call solicitarPermissaoNotificacoes() first.");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
ensureNotificationChannel(context());
|
|
187
|
+
int id = notificationId(options);
|
|
188
|
+
String title = title(options);
|
|
189
|
+
String text = text(options);
|
|
190
|
+
|
|
191
|
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context(), CHANNEL_ID)
|
|
192
|
+
.setSmallIcon(context().getApplicationInfo().icon)
|
|
193
|
+
.setContentTitle(title)
|
|
194
|
+
.setContentText(text)
|
|
195
|
+
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
|
|
196
|
+
.setAutoCancel(true)
|
|
197
|
+
.setContentIntent(createContentIntent(context(), id, detailPayload(options)))
|
|
198
|
+
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
|
199
|
+
|
|
200
|
+
NotificationManagerCompat.from(context()).notify(id, builder.build());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private void scheduleNotification(JSONObject options) throws Exception {
|
|
204
|
+
long when = options.optLong("quando", options.optLong("when", System.currentTimeMillis() + 60000));
|
|
205
|
+
if (when < System.currentTimeMillis()) {
|
|
206
|
+
when = System.currentTimeMillis() + 1000;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
int id = notificationId(options);
|
|
210
|
+
NotificationStore.save(context(), id, when, options);
|
|
211
|
+
NotificationReceiver.schedule(context(), id, when, options, canScheduleExactAlarms());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private boolean canScheduleExactAlarms() {
|
|
215
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
AlarmManager alarmManager = (AlarmManager) context().getSystemService(Context.ALARM_SERVICE);
|
|
220
|
+
return alarmManager != null && alarmManager.canScheduleExactAlarms();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private void openExactAlarmSettings() {
|
|
224
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
|
|
229
|
+
intent.setData(Uri.parse("package:" + context().getPackageName()));
|
|
230
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
231
|
+
context().startActivity(intent);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private void vibrate(long ms) {
|
|
235
|
+
Vibrator vibrator = (Vibrator) context().getSystemService(Context.VIBRATOR_SERVICE);
|
|
236
|
+
if (vibrator == null) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
241
|
+
vibrator.vibrate(VibrationEffect.createOneShot(ms, VibrationEffect.DEFAULT_AMPLITUDE));
|
|
242
|
+
} else {
|
|
243
|
+
vibrator.vibrate(ms);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private void toast(final String message) {
|
|
248
|
+
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
|
249
|
+
@Override
|
|
250
|
+
public void run() {
|
|
251
|
+
Toast.makeText(context(), message, Toast.LENGTH_SHORT).show();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private void setFullscreen(final boolean enabled) {
|
|
257
|
+
this.cordova.getActivity().runOnUiThread(new Runnable() {
|
|
258
|
+
@Override
|
|
259
|
+
public void run() {
|
|
260
|
+
View decor = cordova.getActivity().getWindow().getDecorView();
|
|
261
|
+
if (enabled) {
|
|
262
|
+
decor.setSystemUiVisibility(
|
|
263
|
+
View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
264
|
+
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
265
|
+
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
266
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
267
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
268
|
+
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
269
|
+
);
|
|
270
|
+
} else {
|
|
271
|
+
decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private void handleNotificationIntent(Intent intent, boolean dispatchToJs) {
|
|
278
|
+
if (intent == null || !intent.getBooleanExtra(EXTRA_NOTIFICATION_CLICKED, false)) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
JSONObject detail = parseDetail(intent.getStringExtra(EXTRA_NOTIFICATION_DETAIL));
|
|
283
|
+
initialNotification = detail;
|
|
284
|
+
|
|
285
|
+
if (dispatchToJs) {
|
|
286
|
+
dispatchNotificationClick(detail);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
intent.removeExtra(EXTRA_NOTIFICATION_CLICKED);
|
|
290
|
+
intent.removeExtra(EXTRA_NOTIFICATION_DETAIL);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private void dispatchNotificationClick(JSONObject detail) {
|
|
294
|
+
if (detail == null || webView == null) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
final String script = "(function(){var detail=" + detail.toString()
|
|
299
|
+
+ ";window.dispatchEvent(new CustomEvent('html2apk:notification',{detail:detail}));"
|
|
300
|
+
+ "if(window.Html2ApkNative&&typeof window.Html2ApkNative.__emitNotificationClick==='function'){"
|
|
301
|
+
+ "window.Html2ApkNative.__emitNotificationClick(detail);}})();";
|
|
302
|
+
|
|
303
|
+
cordova.getActivity().runOnUiThread(new Runnable() {
|
|
304
|
+
@Override
|
|
305
|
+
public void run() {
|
|
306
|
+
webView.getEngine().evaluateJavascript(script, null);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private JSONObject parseDetail(String raw) {
|
|
312
|
+
try {
|
|
313
|
+
if (raw == null || raw.length() == 0) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
return new JSONObject(raw);
|
|
317
|
+
} catch (Exception ignored) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static PendingIntent createContentIntent(Context context, int id, JSONObject detail) {
|
|
323
|
+
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
|
|
324
|
+
if (launchIntent == null) {
|
|
325
|
+
launchIntent = new Intent();
|
|
326
|
+
launchIntent.setPackage(context.getPackageName());
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
330
|
+
launchIntent.putExtra(EXTRA_NOTIFICATION_CLICKED, true);
|
|
331
|
+
launchIntent.putExtra(EXTRA_NOTIFICATION_DETAIL, detail == null ? "{}" : detail.toString());
|
|
332
|
+
|
|
333
|
+
return PendingIntent.getActivity(
|
|
334
|
+
context,
|
|
335
|
+
id,
|
|
336
|
+
launchIntent,
|
|
337
|
+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
static JSONObject detailPayload(JSONObject options) throws Exception {
|
|
342
|
+
JSONObject detail = new JSONObject();
|
|
343
|
+
JSONObject click = options.optJSONObject("aoClicar");
|
|
344
|
+
if (click == null) {
|
|
345
|
+
click = options.optJSONObject("onClick");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
detail.put("id", notificationId(options));
|
|
349
|
+
detail.put("title", title(options));
|
|
350
|
+
detail.put("titulo", title(options));
|
|
351
|
+
detail.put("text", text(options));
|
|
352
|
+
detail.put("texto", text(options));
|
|
353
|
+
detail.put("when", options.optLong("quando", options.optLong("when", System.currentTimeMillis())));
|
|
354
|
+
detail.put("clickedAt", System.currentTimeMillis());
|
|
355
|
+
detail.put("onClick", click == null ? new JSONObject().put("action", "open-app") : click);
|
|
356
|
+
detail.put("aoClicar", click == null ? new JSONObject().put("acao", "abrir-app") : click);
|
|
357
|
+
return detail;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
static int notificationId(JSONObject options) {
|
|
361
|
+
int id = options.optInt("id", 0);
|
|
362
|
+
if (id != 0) {
|
|
363
|
+
return id;
|
|
364
|
+
}
|
|
365
|
+
return (int) (System.currentTimeMillis() & 0x0fffffff);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
static String title(JSONObject options) {
|
|
369
|
+
return options.optString("titulo", options.optString("title", "Notificacao"));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
static String text(JSONObject options) {
|
|
373
|
+
return options.optString("texto", options.optString("text", ""));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package dev.html2apk.bridge;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.AlarmManager;
|
|
5
|
+
import android.app.NotificationManager;
|
|
6
|
+
import android.app.PendingIntent;
|
|
7
|
+
import android.content.BroadcastReceiver;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.content.Intent;
|
|
10
|
+
import android.content.pm.PackageManager;
|
|
11
|
+
import android.os.Build;
|
|
12
|
+
|
|
13
|
+
import androidx.core.app.NotificationCompat;
|
|
14
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
15
|
+
|
|
16
|
+
import org.json.JSONObject;
|
|
17
|
+
|
|
18
|
+
public class NotificationReceiver extends BroadcastReceiver {
|
|
19
|
+
private static final String EXTRA_NOTIFICATION_ID = "html2apk_notification_id";
|
|
20
|
+
private static final String EXTRA_NOTIFICATION_OPTIONS = "html2apk_notification_options";
|
|
21
|
+
|
|
22
|
+
@Override
|
|
23
|
+
public void onReceive(Context context, Intent intent) {
|
|
24
|
+
if (Build.VERSION.SDK_INT >= 33 && context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
JSONObject options = parseOptions(intent);
|
|
29
|
+
int id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, Html2ApkBridge.notificationId(options));
|
|
30
|
+
NotificationStore.remove(context, id);
|
|
31
|
+
|
|
32
|
+
Html2ApkBridge.ensureNotificationChannel(context);
|
|
33
|
+
|
|
34
|
+
String title = Html2ApkBridge.title(options);
|
|
35
|
+
String text = Html2ApkBridge.text(options);
|
|
36
|
+
JSONObject detail;
|
|
37
|
+
try {
|
|
38
|
+
detail = Html2ApkBridge.detailPayload(options);
|
|
39
|
+
} catch (Exception error) {
|
|
40
|
+
detail = new JSONObject();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Html2ApkBridge.CHANNEL_ID)
|
|
44
|
+
.setSmallIcon(context.getApplicationInfo().icon)
|
|
45
|
+
.setContentTitle(title)
|
|
46
|
+
.setContentText(text)
|
|
47
|
+
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
|
|
48
|
+
.setAutoCancel(true)
|
|
49
|
+
.setContentIntent(Html2ApkBridge.createContentIntent(context, id, detail))
|
|
50
|
+
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
|
|
51
|
+
|
|
52
|
+
NotificationManagerCompat.from(context).notify(id, builder.build());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static void schedule(Context context, int id, long when, JSONObject options, boolean exactAllowed) {
|
|
56
|
+
Intent intent = new Intent(context, NotificationReceiver.class);
|
|
57
|
+
intent.putExtra(EXTRA_NOTIFICATION_ID, id);
|
|
58
|
+
intent.putExtra(EXTRA_NOTIFICATION_OPTIONS, options.toString());
|
|
59
|
+
|
|
60
|
+
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
|
61
|
+
context,
|
|
62
|
+
id,
|
|
63
|
+
intent,
|
|
64
|
+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
68
|
+
if (alarmManager == null) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (exactAllowed) {
|
|
73
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
74
|
+
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, when, pendingIntent);
|
|
75
|
+
} else {
|
|
76
|
+
alarmManager.setExact(AlarmManager.RTC_WAKEUP, when, pendingIntent);
|
|
77
|
+
}
|
|
78
|
+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
79
|
+
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, when, pendingIntent);
|
|
80
|
+
} else {
|
|
81
|
+
alarmManager.set(AlarmManager.RTC_WAKEUP, when, pendingIntent);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static void cancel(Context context, int id) {
|
|
86
|
+
Intent intent = new Intent(context, NotificationReceiver.class);
|
|
87
|
+
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
|
88
|
+
context,
|
|
89
|
+
id,
|
|
90
|
+
intent,
|
|
91
|
+
PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (pendingIntent == null) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
99
|
+
if (alarmManager != null) {
|
|
100
|
+
alarmManager.cancel(pendingIntent);
|
|
101
|
+
}
|
|
102
|
+
pendingIntent.cancel();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private static JSONObject parseOptions(Intent intent) {
|
|
106
|
+
try {
|
|
107
|
+
return new JSONObject(intent.getStringExtra(EXTRA_NOTIFICATION_OPTIONS));
|
|
108
|
+
} catch (Exception ignored) {
|
|
109
|
+
return new JSONObject();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
package dev.html2apk.bridge;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.content.SharedPreferences;
|
|
5
|
+
|
|
6
|
+
import org.json.JSONArray;
|
|
7
|
+
import org.json.JSONObject;
|
|
8
|
+
|
|
9
|
+
public final class NotificationStore {
|
|
10
|
+
private static final String PREFS = "html2apk_notifications";
|
|
11
|
+
private static final String KEY_ITEMS = "items";
|
|
12
|
+
|
|
13
|
+
private NotificationStore() {}
|
|
14
|
+
|
|
15
|
+
static void save(Context context, int id, long when, JSONObject options) throws Exception {
|
|
16
|
+
JSONArray items = all(context);
|
|
17
|
+
JSONArray next = new JSONArray();
|
|
18
|
+
|
|
19
|
+
for (int index = 0; index < items.length(); index += 1) {
|
|
20
|
+
JSONObject item = items.optJSONObject(index);
|
|
21
|
+
if (item != null && item.optInt("id") != id) {
|
|
22
|
+
next.put(item);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
JSONObject item = new JSONObject();
|
|
27
|
+
item.put("id", id);
|
|
28
|
+
item.put("when", when);
|
|
29
|
+
item.put("options", options);
|
|
30
|
+
next.put(item);
|
|
31
|
+
write(context, next);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static void remove(Context context, int id) {
|
|
35
|
+
try {
|
|
36
|
+
JSONArray items = all(context);
|
|
37
|
+
JSONArray next = new JSONArray();
|
|
38
|
+
|
|
39
|
+
for (int index = 0; index < items.length(); index += 1) {
|
|
40
|
+
JSONObject item = items.optJSONObject(index);
|
|
41
|
+
if (item != null && item.optInt("id") != id) {
|
|
42
|
+
next.put(item);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
write(context, next);
|
|
47
|
+
} catch (Exception ignored) {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static void rescheduleAll(Context context, boolean exactAllowed) {
|
|
52
|
+
try {
|
|
53
|
+
long now = System.currentTimeMillis();
|
|
54
|
+
JSONArray items = all(context);
|
|
55
|
+
JSONArray futureItems = new JSONArray();
|
|
56
|
+
|
|
57
|
+
for (int index = 0; index < items.length(); index += 1) {
|
|
58
|
+
JSONObject item = items.optJSONObject(index);
|
|
59
|
+
if (item == null) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
int id = item.optInt("id");
|
|
64
|
+
long when = item.optLong("when");
|
|
65
|
+
JSONObject options = item.optJSONObject("options");
|
|
66
|
+
if (options == null || when <= now) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
NotificationReceiver.schedule(context, id, when, options, exactAllowed);
|
|
71
|
+
futureItems.put(item);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
write(context, futureItems);
|
|
75
|
+
} catch (Exception ignored) {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private static JSONArray all(Context context) throws Exception {
|
|
80
|
+
String raw = prefs(context).getString(KEY_ITEMS, "[]");
|
|
81
|
+
return new JSONArray(raw);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private static void write(Context context, JSONArray items) {
|
|
85
|
+
prefs(context).edit().putString(KEY_ITEMS, items.toString()).apply();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private static SharedPreferences prefs(Context context) {
|
|
89
|
+
return context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var exec = require("cordova/exec");
|
|
4
|
+
|
|
5
|
+
var notificationListeners = [];
|
|
6
|
+
var initialNotification = null;
|
|
7
|
+
|
|
8
|
+
function call(action, args) {
|
|
9
|
+
return new Promise(function (resolve, reject) {
|
|
10
|
+
exec(resolve, reject, "Html2ApkBridge", action, args || []);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeNotificationOptions(messageOrOptions) {
|
|
15
|
+
if (typeof messageOrOptions === "string") {
|
|
16
|
+
return {
|
|
17
|
+
title: "Notificacao",
|
|
18
|
+
text: messageOrOptions,
|
|
19
|
+
onClick: {
|
|
20
|
+
action: "open-app"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var options = messageOrOptions || {};
|
|
26
|
+
if (!options.onClick && !options.aoClicar) {
|
|
27
|
+
options.onClick = {
|
|
28
|
+
action: "open-app"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return options;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function emitNotificationClick(detail) {
|
|
35
|
+
initialNotification = detail || null;
|
|
36
|
+
|
|
37
|
+
notificationListeners.slice().forEach(function (listener) {
|
|
38
|
+
try {
|
|
39
|
+
listener(initialNotification);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
setTimeout(function () {
|
|
42
|
+
throw error;
|
|
43
|
+
}, 0);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var api = {
|
|
49
|
+
notificar: function (messageOrOptions) {
|
|
50
|
+
return call("notify", [normalizeNotificationOptions(messageOrOptions)]);
|
|
51
|
+
},
|
|
52
|
+
agendarNotificacao: function (options) {
|
|
53
|
+
return call("scheduleNotification", [normalizeNotificationOptions(options || {})]);
|
|
54
|
+
},
|
|
55
|
+
vibrar: function (ms) {
|
|
56
|
+
return call("vibrate", [Number(ms) || 200]);
|
|
57
|
+
},
|
|
58
|
+
toast: function (message) {
|
|
59
|
+
return call("toast", [String(message || "")]);
|
|
60
|
+
},
|
|
61
|
+
fullscreen: function (enabled) {
|
|
62
|
+
return call("fullscreen", [Boolean(enabled)]);
|
|
63
|
+
},
|
|
64
|
+
solicitarPermissaoNotificacoes: function () {
|
|
65
|
+
return call("requestNotificationPermission");
|
|
66
|
+
},
|
|
67
|
+
statusPermissaoNotificacoes: function () {
|
|
68
|
+
return call("notificationPermissionStatus");
|
|
69
|
+
},
|
|
70
|
+
podeAgendarNotificacaoExata: function () {
|
|
71
|
+
return call("canScheduleExactAlarms");
|
|
72
|
+
},
|
|
73
|
+
abrirConfiguracaoAlarmeExato: function () {
|
|
74
|
+
return call("openExactAlarmSettings");
|
|
75
|
+
},
|
|
76
|
+
obterNotificacaoInicial: function () {
|
|
77
|
+
return call("getInitialNotification").then(function (notification) {
|
|
78
|
+
initialNotification = notification && notification.id ? notification : null;
|
|
79
|
+
return initialNotification;
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
aoClicarNotificacao: function (listener) {
|
|
83
|
+
if (typeof listener !== "function") {
|
|
84
|
+
throw new TypeError("listener must be a function");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
notificationListeners.push(listener);
|
|
88
|
+
if (initialNotification) {
|
|
89
|
+
listener(initialNotification);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return function unsubscribe() {
|
|
93
|
+
notificationListeners = notificationListeners.filter(function (item) {
|
|
94
|
+
return item !== listener;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
__emitNotificationClick: function (detail) {
|
|
99
|
+
emitNotificationClick(detail);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (typeof window !== "undefined") {
|
|
104
|
+
window.notificar = api.notificar;
|
|
105
|
+
window.agendarNotificacao = api.agendarNotificacao;
|
|
106
|
+
window.vibrar = api.vibrar;
|
|
107
|
+
window.toast = api.toast;
|
|
108
|
+
window.fullscreen = api.fullscreen;
|
|
109
|
+
window.solicitarPermissaoNotificacoes = api.solicitarPermissaoNotificacoes;
|
|
110
|
+
window.statusPermissaoNotificacoes = api.statusPermissaoNotificacoes;
|
|
111
|
+
window.podeAgendarNotificacaoExata = api.podeAgendarNotificacaoExata;
|
|
112
|
+
window.abrirConfiguracaoAlarmeExato = api.abrirConfiguracaoAlarmeExato;
|
|
113
|
+
window.obterNotificacaoInicial = api.obterNotificacaoInicial;
|
|
114
|
+
window.aoClicarNotificacao = api.aoClicarNotificacao;
|
|
115
|
+
|
|
116
|
+
window.addEventListener("html2apk:notification", function (event) {
|
|
117
|
+
emitNotificationClick(event.detail);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
document.addEventListener("deviceready", function () {
|
|
121
|
+
api.obterNotificacaoInicial().then(function (notification) {
|
|
122
|
+
if (notification) {
|
|
123
|
+
emitNotificationClick(notification);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}, false);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = api;
|