@capgo/capacitor-updater 4.41.0 → 4.43.5

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.
Files changed (67) hide show
  1. package/CapgoCapacitorUpdater.podspec +7 -5
  2. package/Package.swift +40 -0
  3. package/README.md +1913 -303
  4. package/android/build.gradle +41 -8
  5. package/android/proguard-rules.pro +45 -0
  6. package/android/src/main/AndroidManifest.xml +1 -3
  7. package/android/src/main/java/ee/forgr/capacitor_updater/AppLifecycleObserver.java +88 -0
  8. package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
  9. package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
  10. package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
  11. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2720 -1242
  12. package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1854 -0
  13. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +359 -121
  14. package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
  15. package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +44 -49
  16. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
  17. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +296 -0
  18. package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +215 -0
  19. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +858 -117
  20. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
  21. package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +45 -0
  22. package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +360 -0
  23. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
  24. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +603 -0
  25. package/dist/docs.json +3022 -765
  26. package/dist/esm/definitions.d.ts +1717 -198
  27. package/dist/esm/definitions.js +103 -1
  28. package/dist/esm/definitions.js.map +1 -1
  29. package/dist/esm/history.d.ts +1 -0
  30. package/dist/esm/history.js +283 -0
  31. package/dist/esm/history.js.map +1 -0
  32. package/dist/esm/index.d.ts +3 -2
  33. package/dist/esm/index.js +5 -4
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/web.d.ts +43 -42
  36. package/dist/esm/web.js +122 -37
  37. package/dist/esm/web.js.map +1 -1
  38. package/dist/plugin.cjs.js +512 -37
  39. package/dist/plugin.cjs.js.map +1 -1
  40. package/dist/plugin.js +512 -37
  41. package/dist/plugin.js.map +1 -1
  42. package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +87 -0
  43. package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
  44. package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +177 -0
  45. package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +12 -12
  46. package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +2020 -0
  47. package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1959 -0
  48. package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +313 -0
  49. package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +257 -0
  50. package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
  51. package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +392 -0
  52. package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
  53. package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
  54. package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +441 -0
  55. package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +1 -2
  56. package/package.json +49 -41
  57. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1131
  58. package/ios/Plugin/BundleInfo.swift +0 -113
  59. package/ios/Plugin/CapacitorUpdater.swift +0 -850
  60. package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
  61. package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
  62. package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -678
  63. package/ios/Plugin/CryptoCipher.swift +0 -240
  64. /package/{LICENCE → LICENSE} +0 -0
  65. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
  66. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
  67. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
@@ -0,0 +1,603 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package ee.forgr.capacitor_updater;
8
+
9
+ import android.app.Activity;
10
+ import android.app.AlertDialog;
11
+ import android.content.DialogInterface;
12
+ import android.hardware.SensorManager;
13
+ import android.text.Editable;
14
+ import android.text.TextWatcher;
15
+ import android.widget.ArrayAdapter;
16
+ import android.widget.EditText;
17
+ import android.widget.LinearLayout;
18
+ import android.widget.ListView;
19
+ import android.widget.ProgressBar;
20
+ import com.getcapacitor.Bridge;
21
+ import com.getcapacitor.BridgeActivity;
22
+ import java.util.ArrayList;
23
+ import java.util.List;
24
+ import java.util.Map;
25
+ import org.json.JSONArray;
26
+
27
+ public class ShakeMenu implements ShakeDetector.Listener {
28
+
29
+ private CapacitorUpdaterPlugin plugin;
30
+ private BridgeActivity activity;
31
+ private ShakeDetector shakeDetector;
32
+ private boolean isShowing = false;
33
+ private Logger logger;
34
+
35
+ public ShakeMenu(CapacitorUpdaterPlugin plugin, BridgeActivity activity, Logger logger) {
36
+ this.plugin = plugin;
37
+ this.activity = activity;
38
+ this.logger = logger;
39
+
40
+ SensorManager sensorManager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE);
41
+ this.shakeDetector = new ShakeDetector(this);
42
+ this.shakeDetector.start(sensorManager);
43
+ }
44
+
45
+ public void stop() {
46
+ if (shakeDetector != null) {
47
+ shakeDetector.stop();
48
+ }
49
+ }
50
+
51
+ @Override
52
+ public void onShakeDetected() {
53
+ logger.info("Shake detected");
54
+
55
+ // Check if shake menu is enabled
56
+ if (!plugin.shakeMenuEnabled) {
57
+ logger.info("Shake menu is disabled");
58
+ return;
59
+ }
60
+
61
+ // Prevent multiple dialogs
62
+ if (isShowing) {
63
+ logger.info("Dialog already showing");
64
+ return;
65
+ }
66
+
67
+ isShowing = true;
68
+
69
+ // Check if channel selector mode is enabled
70
+ if (plugin.shakeChannelSelectorEnabled) {
71
+ showChannelSelector();
72
+ } else {
73
+ showDefaultMenu();
74
+ }
75
+ }
76
+
77
+ private void showDefaultMenu() {
78
+ activity.runOnUiThread(() -> {
79
+ try {
80
+ String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
81
+ String title = "Preview " + appName + " Menu";
82
+ String message = "What would you like to do?";
83
+ String okButtonTitle = "Go Home";
84
+ String reloadButtonTitle = "Reload app";
85
+ String cancelButtonTitle = "Close menu";
86
+
87
+ CapgoUpdater updater = plugin.implementation;
88
+ Bridge bridge = activity.getBridge();
89
+
90
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
91
+ builder.setTitle(title);
92
+ builder.setMessage(message);
93
+
94
+ // Go Home button
95
+ builder.setPositiveButton(
96
+ okButtonTitle,
97
+ new DialogInterface.OnClickListener() {
98
+ public void onClick(DialogInterface dialog, int id) {
99
+ try {
100
+ BundleInfo current = updater.getCurrentBundle();
101
+ logger.info("Current bundle: " + current.toString());
102
+
103
+ BundleInfo next = updater.getNextBundle();
104
+ logger.info("Next bundle: " + (next != null ? next.toString() : "null"));
105
+
106
+ if (next != null && !next.isBuiltin()) {
107
+ logger.info("Setting bundle to: " + next.toString());
108
+ updater.set(next);
109
+ String path = updater.getCurrentBundlePath();
110
+ logger.info("Setting server path: " + path);
111
+ if (updater.isUsingBuiltin()) {
112
+ bridge.setServerAssetPath(path);
113
+ } else {
114
+ bridge.setServerBasePath(path);
115
+ }
116
+ } else {
117
+ logger.info("Resetting to builtin");
118
+ updater.reset();
119
+ String path = updater.getCurrentBundlePath();
120
+ bridge.setServerAssetPath(path);
121
+ }
122
+
123
+ // Try to delete the current bundle
124
+ try {
125
+ updater.delete(current.getId());
126
+ } catch (Exception err) {
127
+ logger.warn("Cannot delete version " + current.getId() + ": " + err.getMessage());
128
+ }
129
+
130
+ logger.info("Reload app done");
131
+ } catch (Exception e) {
132
+ logger.error("Error in Go Home action: " + e.getMessage());
133
+ } finally {
134
+ dialog.dismiss();
135
+ isShowing = false;
136
+ }
137
+ }
138
+ }
139
+ );
140
+
141
+ // Reload button
142
+ builder.setNeutralButton(
143
+ reloadButtonTitle,
144
+ new DialogInterface.OnClickListener() {
145
+ public void onClick(DialogInterface dialog, int id) {
146
+ try {
147
+ logger.info("Reloading webview");
148
+ String pathHot = updater.getCurrentBundlePath();
149
+ bridge.setServerBasePath(pathHot);
150
+ activity.runOnUiThread(() -> {
151
+ if (bridge.getWebView() != null) {
152
+ bridge.getWebView().reload();
153
+ }
154
+ });
155
+ } catch (Exception e) {
156
+ logger.error("Error in Reload action: " + e.getMessage());
157
+ } finally {
158
+ dialog.dismiss();
159
+ isShowing = false;
160
+ }
161
+ }
162
+ }
163
+ );
164
+
165
+ // Cancel button
166
+ builder.setNegativeButton(
167
+ cancelButtonTitle,
168
+ new DialogInterface.OnClickListener() {
169
+ public void onClick(DialogInterface dialog, int id) {
170
+ logger.info("Shake menu cancelled");
171
+ dialog.dismiss();
172
+ isShowing = false;
173
+ }
174
+ }
175
+ );
176
+
177
+ AlertDialog dialog = builder.create();
178
+ dialog.setOnDismissListener((dialogInterface) -> isShowing = false);
179
+ dialog.show();
180
+ } catch (Exception e) {
181
+ logger.error("Error showing shake menu: " + e.getMessage());
182
+ isShowing = false;
183
+ }
184
+ });
185
+ }
186
+
187
+ private void showChannelSelector() {
188
+ activity.runOnUiThread(() -> {
189
+ try {
190
+ // Show loading dialog
191
+ AlertDialog.Builder loadingBuilder = new AlertDialog.Builder(activity);
192
+ loadingBuilder.setTitle("Loading Channels...");
193
+ loadingBuilder.setCancelable(true);
194
+
195
+ ProgressBar progressBar = new ProgressBar(activity);
196
+ progressBar.setIndeterminate(true);
197
+ int padding = dpToPx(20);
198
+ progressBar.setPadding(padding, padding, padding, padding);
199
+ loadingBuilder.setView(progressBar);
200
+
201
+ final boolean[] didCancel = { false };
202
+
203
+ loadingBuilder.setNegativeButton("Cancel", (dialog, which) -> {
204
+ didCancel[0] = true;
205
+ dialog.dismiss();
206
+ isShowing = false;
207
+ });
208
+
209
+ AlertDialog loadingDialog = loadingBuilder.create();
210
+ loadingDialog.setOnCancelListener((d) -> {
211
+ didCancel[0] = true;
212
+ isShowing = false;
213
+ });
214
+ loadingDialog.setOnDismissListener((d) -> {
215
+ if (didCancel[0]) {
216
+ isShowing = false;
217
+ }
218
+ });
219
+ loadingDialog.show();
220
+
221
+ // Fetch channels in background
222
+ new Thread(() -> {
223
+ final CapgoUpdater updater = plugin.implementation;
224
+ updater.listChannels((res) -> {
225
+ activity.runOnUiThread(() -> {
226
+ loadingDialog.dismiss();
227
+
228
+ if (didCancel[0]) {
229
+ return;
230
+ }
231
+
232
+ if (res == null) {
233
+ showError("Failed to load channels: unknown error");
234
+ return;
235
+ }
236
+
237
+ Object errorObj = res.get("error");
238
+ if (errorObj != null) {
239
+ Object messageObj = res.get("message");
240
+ String message = messageObj != null ? messageObj.toString() : errorObj.toString();
241
+ showError("Failed to load channels: " + message);
242
+ return;
243
+ }
244
+
245
+ Object channelsObj = res.get("channels");
246
+ if (!(channelsObj instanceof List)) {
247
+ showError("No channels available for self-assignment");
248
+ return;
249
+ }
250
+
251
+ List<?> channelsRaw = (List<?>) channelsObj;
252
+ List<Map<String, Object>> channels = new ArrayList<>();
253
+ for (Object item : channelsRaw) {
254
+ if (item instanceof Map) {
255
+ channels.add((Map<String, Object>) item);
256
+ }
257
+ }
258
+
259
+ if (channels.isEmpty()) {
260
+ showError("No channels available for self-assignment");
261
+ return;
262
+ }
263
+
264
+ presentChannelPicker(channels);
265
+ });
266
+ });
267
+ })
268
+ .start();
269
+ } catch (Exception e) {
270
+ logger.error("Error showing channel selector: " + e.getMessage());
271
+ isShowing = false;
272
+ }
273
+ });
274
+ }
275
+
276
+ private void presentChannelPicker(List<Map<String, Object>> channels) {
277
+ try {
278
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
279
+ builder.setTitle("Select Channel");
280
+
281
+ // Create custom layout with search and channel list
282
+ LinearLayout layout = new LinearLayout(activity);
283
+ layout.setOrientation(LinearLayout.VERTICAL);
284
+ int padding = dpToPx(16);
285
+ layout.setPadding(padding, padding, padding, padding);
286
+
287
+ // Search field
288
+ EditText searchField = new EditText(activity);
289
+ searchField.setHint("Search channels...");
290
+ searchField.setSingleLine(true);
291
+ layout.addView(searchField);
292
+
293
+ // Create list of channel names
294
+ List<String> allChannelNames = new ArrayList<>();
295
+ for (Map<String, Object> channel : channels) {
296
+ Object nameObj = channel.get("name");
297
+ if (nameObj instanceof String) {
298
+ allChannelNames.add((String) nameObj);
299
+ }
300
+ }
301
+
302
+ // Displayed channels (first 5 by default)
303
+ final List<String> displayedChannels = new ArrayList<>();
304
+ displayedChannels.addAll(allChannelNames.subList(0, Math.min(5, allChannelNames.size())));
305
+
306
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, displayedChannels);
307
+
308
+ ListView listView = new ListView(activity);
309
+ listView.setAdapter(adapter);
310
+
311
+ // Set fixed height for list (about 5 items)
312
+ LinearLayout.LayoutParams listParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dpToPx(250));
313
+ listView.setLayoutParams(listParams);
314
+ layout.addView(listView);
315
+
316
+ builder.setView(layout);
317
+ builder.setNegativeButton("Cancel", (dialog, which) -> {
318
+ dialog.dismiss();
319
+ isShowing = false;
320
+ });
321
+
322
+ AlertDialog dialog = builder.create();
323
+ dialog.setOnDismissListener((d) -> isShowing = false);
324
+
325
+ // Search filter
326
+ searchField.addTextChangedListener(
327
+ new TextWatcher() {
328
+ @Override
329
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
330
+
331
+ @Override
332
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
333
+
334
+ @Override
335
+ public void afterTextChanged(Editable s) {
336
+ String query = s.toString().toLowerCase();
337
+ displayedChannels.clear();
338
+
339
+ int count = 0;
340
+ for (String name : allChannelNames) {
341
+ if (name.toLowerCase().contains(query)) {
342
+ displayedChannels.add(name);
343
+ count++;
344
+ if (count >= 5) break;
345
+ }
346
+ }
347
+
348
+ adapter.notifyDataSetChanged();
349
+ }
350
+ }
351
+ );
352
+
353
+ // Channel selection
354
+ listView.setOnItemClickListener((parent, view, position, id) -> {
355
+ String selectedChannel = displayedChannels.get(position);
356
+ dialog.dismiss();
357
+ selectChannel(selectedChannel);
358
+ });
359
+
360
+ dialog.show();
361
+ } catch (Exception e) {
362
+ logger.error("Error presenting channel picker: " + e.getMessage());
363
+ isShowing = false;
364
+ }
365
+ }
366
+
367
+ private void selectChannel(String channelName) {
368
+ activity.runOnUiThread(() -> {
369
+ try {
370
+ // Show progress dialog
371
+ AlertDialog.Builder progressBuilder = new AlertDialog.Builder(activity);
372
+ progressBuilder.setTitle("Switching to " + channelName);
373
+ progressBuilder.setMessage("Setting channel...");
374
+ progressBuilder.setCancelable(false);
375
+
376
+ ProgressBar progressBar = new ProgressBar(activity);
377
+ progressBar.setIndeterminate(true);
378
+ int padding = dpToPx(20);
379
+ progressBar.setPadding(padding, padding, padding, padding);
380
+ progressBuilder.setView(progressBar);
381
+
382
+ AlertDialog progressDialog = progressBuilder.create();
383
+ progressDialog.show();
384
+
385
+ new Thread(() -> {
386
+ final CapgoUpdater updater = plugin.implementation;
387
+ final Bridge bridge = activity.getBridge();
388
+
389
+ // Set the channel - respect plugin's allowSetDefaultChannel config
390
+ updater.setChannel(
391
+ channelName,
392
+ updater.editor,
393
+ "CapacitorUpdater.defaultChannel",
394
+ plugin.allowSetDefaultChannel,
395
+ (setRes) -> {
396
+ if (setRes == null) {
397
+ activity.runOnUiThread(() -> {
398
+ progressDialog.dismiss();
399
+ showError("Failed to set channel: unknown error");
400
+ });
401
+ return;
402
+ }
403
+
404
+ Object errorObj = setRes.get("error");
405
+ if (errorObj != null) {
406
+ Object messageObj = setRes.get("message");
407
+ String message = messageObj != null ? messageObj.toString() : errorObj.toString();
408
+ activity.runOnUiThread(() -> {
409
+ progressDialog.dismiss();
410
+ showError("Failed to set channel: " + message);
411
+ });
412
+ return;
413
+ }
414
+
415
+ // Update progress message
416
+ activity.runOnUiThread(() -> progressDialog.setMessage("Checking for updates..."));
417
+
418
+ // Check for updates
419
+ String updateUrlStr = plugin.getUpdateUrl();
420
+ if (updateUrlStr == null || updateUrlStr.isEmpty()) {
421
+ updateUrlStr = "https://plugin.capgo.app/updates";
422
+ }
423
+
424
+ final String finalUpdateUrlStr = updateUrlStr;
425
+ updater.getLatest(finalUpdateUrlStr, channelName, (latestRes) -> {
426
+ if (latestRes == null) {
427
+ activity.runOnUiThread(() -> {
428
+ progressDialog.dismiss();
429
+ showSuccess("Channel set to " + channelName + ". Could not check for updates.");
430
+ });
431
+ return;
432
+ }
433
+
434
+ String latestError = getString(latestRes, "error");
435
+
436
+ // Handle update errors first (before "no new version" check)
437
+ if (latestError != null && !latestError.isEmpty() && !"no_new_version_available".equals(latestError)) {
438
+ activity.runOnUiThread(() -> {
439
+ progressDialog.dismiss();
440
+ showError("Channel set to " + channelName + ". Update check failed: " + latestError);
441
+ });
442
+ return;
443
+ }
444
+
445
+ String latestUrl = getString(latestRes, "url");
446
+
447
+ // Check if there's an actual update available
448
+ if ("no_new_version_available".equals(latestError) || latestUrl == null || latestUrl.isEmpty()) {
449
+ activity.runOnUiThread(() -> {
450
+ progressDialog.dismiss();
451
+ showSuccess("Channel set to " + channelName + ". Already on latest version.");
452
+ });
453
+ return;
454
+ }
455
+
456
+ String version = getString(latestRes, "version");
457
+ if (version == null || version.isEmpty()) {
458
+ activity.runOnUiThread(() -> {
459
+ progressDialog.dismiss();
460
+ showError("Channel set to " + channelName + ". Update check failed: missing version.");
461
+ });
462
+ return;
463
+ }
464
+
465
+ // Update message
466
+ final String versionForUi = version;
467
+ activity.runOnUiThread(() -> progressDialog.setMessage("Downloading update " + versionForUi + "..."));
468
+
469
+ String sessionKey = getString(latestRes, "sessionKey");
470
+ String checksum = getString(latestRes, "checksum");
471
+ Object manifestObj = latestRes.get("manifest");
472
+
473
+ // Download the update
474
+ try {
475
+ BundleInfo bundle;
476
+ if (manifestObj != null) {
477
+ JSONArray manifestArray = null;
478
+ if (manifestObj instanceof JSONArray) {
479
+ manifestArray = (JSONArray) manifestObj;
480
+ } else if (manifestObj instanceof List) {
481
+ manifestArray = new JSONArray((List<?>) manifestObj);
482
+ }
483
+
484
+ if (manifestArray == null) {
485
+ throw new IllegalArgumentException("Invalid manifest format");
486
+ }
487
+
488
+ bundle = updater.downloadManifest(
489
+ latestUrl,
490
+ versionForUi,
491
+ sessionKey != null ? sessionKey : "",
492
+ checksum != null ? checksum : "",
493
+ manifestArray
494
+ );
495
+ } else {
496
+ bundle = updater.download(
497
+ latestUrl,
498
+ versionForUi,
499
+ sessionKey != null ? sessionKey : "",
500
+ checksum != null ? checksum : ""
501
+ );
502
+ }
503
+
504
+ // Set as next bundle
505
+ updater.setNextBundle(bundle.getId());
506
+
507
+ activity.runOnUiThread(() -> {
508
+ progressDialog.dismiss();
509
+ showSuccessWithReload("Update downloaded! Reload to apply version " + versionForUi + "?", () -> {
510
+ try {
511
+ if (bridge == null) {
512
+ logger.warn("Bridge is null, cannot reload app");
513
+ return;
514
+ }
515
+ updater.set(bundle);
516
+ String path = updater.getCurrentBundlePath();
517
+ if (updater.isUsingBuiltin()) {
518
+ bridge.setServerAssetPath(path);
519
+ } else {
520
+ bridge.setServerBasePath(path);
521
+ }
522
+ if (bridge.getWebView() != null) {
523
+ bridge.getWebView().reload();
524
+ }
525
+ } catch (Exception e) {
526
+ logger.error("Error applying bundle before reload: " + e.getMessage());
527
+ }
528
+ });
529
+ });
530
+ } catch (Exception e) {
531
+ activity.runOnUiThread(() -> {
532
+ progressDialog.dismiss();
533
+ showError("Failed to download update: " + e.getMessage());
534
+ });
535
+ }
536
+ });
537
+ }
538
+ );
539
+ })
540
+ .start();
541
+ } catch (Exception e) {
542
+ logger.error("Error selecting channel: " + e.getMessage());
543
+ isShowing = false;
544
+ }
545
+ });
546
+ }
547
+
548
+ private void showError(String message) {
549
+ logger.error(message);
550
+ new AlertDialog.Builder(activity)
551
+ .setTitle("Error")
552
+ .setMessage(message)
553
+ .setPositiveButton("OK", (d, w) -> {
554
+ d.dismiss();
555
+ isShowing = false;
556
+ })
557
+ .setOnDismissListener((d) -> isShowing = false)
558
+ .show();
559
+ }
560
+
561
+ private void showSuccess(String message) {
562
+ logger.info(message);
563
+ new AlertDialog.Builder(activity)
564
+ .setTitle("Success")
565
+ .setMessage(message)
566
+ .setPositiveButton("OK", (d, w) -> {
567
+ d.dismiss();
568
+ isShowing = false;
569
+ })
570
+ .setOnDismissListener((d) -> isShowing = false)
571
+ .show();
572
+ }
573
+
574
+ private void showSuccessWithReload(String message, Runnable onReload) {
575
+ logger.info(message);
576
+ new AlertDialog.Builder(activity)
577
+ .setTitle("Update Ready")
578
+ .setMessage(message)
579
+ .setPositiveButton("Reload Now", (d, w) -> {
580
+ d.dismiss();
581
+ isShowing = false;
582
+ if (onReload != null) {
583
+ onReload.run();
584
+ }
585
+ })
586
+ .setNegativeButton("Later", (d, w) -> {
587
+ d.dismiss();
588
+ isShowing = false;
589
+ })
590
+ .setOnDismissListener((d) -> isShowing = false)
591
+ .show();
592
+ }
593
+
594
+ private String getString(Map<String, Object> map, String key) {
595
+ Object value = map.get(key);
596
+ return value != null ? value.toString() : null;
597
+ }
598
+
599
+ private int dpToPx(int dp) {
600
+ float density = activity.getResources().getDisplayMetrics().density;
601
+ return Math.round(dp * density);
602
+ }
603
+ }