@capgo/capacitor-updater 7.42.3 → 7.43.3
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/Package.swift +1 -1
- package/README.md +104 -41
- package/android/build.gradle +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +153 -49
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +60 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +23 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +436 -2
- package/dist/docs.json +116 -4
- package/dist/esm/definitions.d.ts +54 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -1
- package/dist/esm/web.js +6 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +6 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +6 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +97 -6
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +23 -11
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +1 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +330 -2
- package/package.json +3 -2
|
@@ -10,8 +10,19 @@ import android.app.Activity;
|
|
|
10
10
|
import android.app.AlertDialog;
|
|
11
11
|
import android.content.DialogInterface;
|
|
12
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;
|
|
13
20
|
import com.getcapacitor.Bridge;
|
|
14
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;
|
|
15
26
|
|
|
16
27
|
public class ShakeMenu implements ShakeDetector.Listener {
|
|
17
28
|
|
|
@@ -54,10 +65,16 @@ public class ShakeMenu implements ShakeDetector.Listener {
|
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
isShowing = true;
|
|
57
|
-
|
|
68
|
+
|
|
69
|
+
// Check if channel selector mode is enabled
|
|
70
|
+
if (plugin.shakeChannelSelectorEnabled) {
|
|
71
|
+
showChannelSelector();
|
|
72
|
+
} else {
|
|
73
|
+
showDefaultMenu();
|
|
74
|
+
}
|
|
58
75
|
}
|
|
59
76
|
|
|
60
|
-
private void
|
|
77
|
+
private void showDefaultMenu() {
|
|
61
78
|
activity.runOnUiThread(() -> {
|
|
62
79
|
try {
|
|
63
80
|
String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
|
|
@@ -166,4 +183,421 @@ public class ShakeMenu implements ShakeDetector.Listener {
|
|
|
166
183
|
}
|
|
167
184
|
});
|
|
168
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
|
+
}
|
|
169
603
|
}
|
package/dist/docs.json
CHANGED
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"text": "{Error} If the download fails or the bundle is invalid."
|
|
157
157
|
}
|
|
158
158
|
],
|
|
159
|
-
"docs": "Download a new bundle from the provided URL for later installation.\n\nThe downloaded bundle is stored locally but not activated. To use it:\n- Call {@link next} to set it for installation on next app backgrounding/restart\n- Call {@link set} to activate it immediately (destroys current JavaScript context)\n\nThe URL should point to a zip file containing either:\n- Your app files directly in the zip root, or\n- A single folder containing all your app files\n\nThe bundle must include an `index.html` file at the root level.\n\nFor encrypted bundles, provide the `sessionKey` and `checksum` parameters.\nFor multi-file
|
|
159
|
+
"docs": "Download a new bundle from the provided URL for later installation.\n\nThe downloaded bundle is stored locally but not activated. To use it:\n- Call {@link next} to set it for installation on next app backgrounding/restart\n- Call {@link set} to activate it immediately (destroys current JavaScript context)\n\nThe URL should point to a zip file containing either:\n- Your app files directly in the zip root, or\n- A single folder containing all your app files\n\nThe bundle must include an `index.html` file at the root level.\n\nFor encrypted bundles, provide the `sessionKey` and `checksum` parameters.\nFor multi-file delta updates, provide the `manifest` array.",
|
|
160
160
|
"complexTypes": [
|
|
161
161
|
"BundleInfo",
|
|
162
162
|
"DownloadOptions"
|
|
@@ -499,7 +499,7 @@
|
|
|
499
499
|
"text": "4.0.0"
|
|
500
500
|
}
|
|
501
501
|
],
|
|
502
|
-
"docs": "Check the update server for the latest available bundle version.\n\nThis queries your configured update URL (or Capgo backend) to see if a newer bundle\nis available for download. It does NOT download the bundle automatically.\n\nThe response includes:\n- `version`: The latest available version identifier\n- `url`: Download URL for the bundle (if available)\n- `breaking`: Whether this update is marked as incompatible (requires native app update)\n- `message`: Optional message from the server\n- `manifest`: File list for
|
|
502
|
+
"docs": "Check the update server for the latest available bundle version.\n\nThis queries your configured update URL (or Capgo backend) to see if a newer bundle\nis available for download. It does NOT download the bundle automatically.\n\nThe response includes:\n- `version`: The latest available version identifier\n- `url`: Download URL for the bundle (if available)\n- `breaking`: Whether this update is marked as incompatible (requires native app update)\n- `message`: Optional message from the server\n- `manifest`: File list for delta updates (if using multi-file downloads)\n\nAfter receiving the latest version info, you can:\n1. Compare it with your current version\n2. Download it using {@link download}\n3. Apply it using {@link next} or {@link set}\n\n**Important: Error handling for \"no new version available\"**\n\nWhen the device's current version matches the latest version on the server (i.e., the device is already\nup-to-date), the server returns a 200 response with `error: \"no_new_version_available\"` and\n`message: \"No new version available\"`. **This causes `getLatest()` to throw an error**, even though\nthis is a normal, expected condition.\n\nYou should catch this specific error to handle it gracefully:\n\n```typescript\ntry {\n const latest = await CapacitorUpdater.getLatest();\n // New version is available, proceed with download\n} catch (error) {\n if (error.message === 'No new version available') {\n // Device is already on the latest version - this is normal\n console.log('Already up to date');\n } else {\n // Actual error occurred\n console.error('Failed to check for updates:', error);\n }\n}\n```\n\nIn this scenario, the server:\n- Logs the request with a \"No new version available\" message\n- Sends a \"noNew\" stat action to track that the device checked for updates but was already current (done on the backend)",
|
|
503
503
|
"complexTypes": [
|
|
504
504
|
"LatestVersion",
|
|
505
505
|
"GetLatestOptions"
|
|
@@ -1255,6 +1255,70 @@
|
|
|
1255
1255
|
],
|
|
1256
1256
|
"slug": "isshakemenuenabled"
|
|
1257
1257
|
},
|
|
1258
|
+
{
|
|
1259
|
+
"name": "setShakeChannelSelector",
|
|
1260
|
+
"signature": "(options: SetShakeChannelSelectorOptions) => Promise<void>",
|
|
1261
|
+
"parameters": [
|
|
1262
|
+
{
|
|
1263
|
+
"name": "options",
|
|
1264
|
+
"docs": "",
|
|
1265
|
+
"type": "SetShakeChannelSelectorOptions"
|
|
1266
|
+
}
|
|
1267
|
+
],
|
|
1268
|
+
"returns": "Promise<void>",
|
|
1269
|
+
"tags": [
|
|
1270
|
+
{
|
|
1271
|
+
"name": "param",
|
|
1272
|
+
"text": "options"
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
"name": "link",
|
|
1276
|
+
"text": "SetShakeChannelSelectorOptions} with `enabled: true` to enable or `enabled: false` to disable."
|
|
1277
|
+
},
|
|
1278
|
+
{
|
|
1279
|
+
"name": "returns",
|
|
1280
|
+
"text": "Resolves when the setting is applied."
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
"name": "throws",
|
|
1284
|
+
"text": "{Error} If the operation fails."
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
"name": "since",
|
|
1288
|
+
"text": "8.43.0"
|
|
1289
|
+
}
|
|
1290
|
+
],
|
|
1291
|
+
"docs": "Enable or disable the shake channel selector at runtime.\n\nWhen enabled AND shakeMenu is true, shaking the device shows a channel\nselector instead of the debug menu. This allows users to switch between\nupdate channels by shaking their device.\n\nAfter selecting a channel, the app automatically checks for updates\nand downloads if available.\n\nCan also be configured via {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector}.",
|
|
1292
|
+
"complexTypes": [
|
|
1293
|
+
"SetShakeChannelSelectorOptions"
|
|
1294
|
+
],
|
|
1295
|
+
"slug": "setshakechannelselector"
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
"name": "isShakeChannelSelectorEnabled",
|
|
1299
|
+
"signature": "() => Promise<ShakeChannelSelectorEnabled>",
|
|
1300
|
+
"parameters": [],
|
|
1301
|
+
"returns": "Promise<ShakeChannelSelectorEnabled>",
|
|
1302
|
+
"tags": [
|
|
1303
|
+
{
|
|
1304
|
+
"name": "returns",
|
|
1305
|
+
"text": "Object with `enabled: true` or `enabled: false`."
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
"name": "throws",
|
|
1309
|
+
"text": "{Error} If the operation fails."
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
"name": "since",
|
|
1313
|
+
"text": "8.43.0"
|
|
1314
|
+
}
|
|
1315
|
+
],
|
|
1316
|
+
"docs": "Check if the shake channel selector is currently enabled.\n\nReturns the current state of the shake channel selector feature that can be toggled via\n{@link setShakeChannelSelector} or configured via {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector}.",
|
|
1317
|
+
"complexTypes": [
|
|
1318
|
+
"ShakeChannelSelectorEnabled"
|
|
1319
|
+
],
|
|
1320
|
+
"slug": "isshakechannelselectorenabled"
|
|
1321
|
+
},
|
|
1258
1322
|
{
|
|
1259
1323
|
"name": "getAppId",
|
|
1260
1324
|
"signature": "() => Promise<GetAppIdRes>",
|
|
@@ -1932,7 +1996,7 @@
|
|
|
1932
1996
|
"name": "since"
|
|
1933
1997
|
}
|
|
1934
1998
|
],
|
|
1935
|
-
"docs": "File list for
|
|
1999
|
+
"docs": "File list for delta updates (when using multi-file downloads).",
|
|
1936
2000
|
"complexTypes": [
|
|
1937
2001
|
"ManifestEntry"
|
|
1938
2002
|
],
|
|
@@ -2614,6 +2678,38 @@
|
|
|
2614
2678
|
}
|
|
2615
2679
|
]
|
|
2616
2680
|
},
|
|
2681
|
+
{
|
|
2682
|
+
"name": "SetShakeChannelSelectorOptions",
|
|
2683
|
+
"slug": "setshakechannelselectoroptions",
|
|
2684
|
+
"docs": "",
|
|
2685
|
+
"tags": [],
|
|
2686
|
+
"methods": [],
|
|
2687
|
+
"properties": [
|
|
2688
|
+
{
|
|
2689
|
+
"name": "enabled",
|
|
2690
|
+
"tags": [],
|
|
2691
|
+
"docs": "",
|
|
2692
|
+
"complexTypes": [],
|
|
2693
|
+
"type": "boolean"
|
|
2694
|
+
}
|
|
2695
|
+
]
|
|
2696
|
+
},
|
|
2697
|
+
{
|
|
2698
|
+
"name": "ShakeChannelSelectorEnabled",
|
|
2699
|
+
"slug": "shakechannelselectorenabled",
|
|
2700
|
+
"docs": "",
|
|
2701
|
+
"tags": [],
|
|
2702
|
+
"methods": [],
|
|
2703
|
+
"properties": [
|
|
2704
|
+
{
|
|
2705
|
+
"name": "enabled",
|
|
2706
|
+
"tags": [],
|
|
2707
|
+
"docs": "",
|
|
2708
|
+
"complexTypes": [],
|
|
2709
|
+
"type": "boolean"
|
|
2710
|
+
}
|
|
2711
|
+
]
|
|
2712
|
+
},
|
|
2617
2713
|
{
|
|
2618
2714
|
"name": "GetAppIdRes",
|
|
2619
2715
|
"slug": "getappidres",
|
|
@@ -3283,7 +3379,7 @@
|
|
|
3283
3379
|
"name": "since"
|
|
3284
3380
|
}
|
|
3285
3381
|
],
|
|
3286
|
-
"docs": "Configure when the plugin should direct install updates. Only for autoUpdate mode.\nWorks well for apps less than 10MB and with uploads done using --
|
|
3382
|
+
"docs": "Configure when the plugin should direct install updates. Only for autoUpdate mode.\nWorks well for apps less than 10MB and with uploads done using --delta flag.\nZip or apps more than 10MB will be relatively slow for users to update.\n- false: Never do direct updates (use default behavior: download at start, set when backgrounded)\n- atInstall: Direct update only when app is installed, updated from store, otherwise act as directUpdate = false\n- onLaunch: Direct update only on app installed, updated from store or after app kill, otherwise act as directUpdate = false\n- always: Direct update in all previous cases (app installed, updated from store, after app kill or app resume), never act as directUpdate = false\n- true: (deprecated) Same as \"always\" for backward compatibility\n\nActivate this flag will automatically make the CLI upload delta in CICD envs and will ask for confirmation in local uploads.\nOnly available for Android and iOS.",
|
|
3287
3383
|
"complexTypes": [],
|
|
3288
3384
|
"type": "boolean | 'always' | 'atInstall' | 'onLaunch' | undefined"
|
|
3289
3385
|
},
|
|
@@ -3658,6 +3754,22 @@
|
|
|
3658
3754
|
"docs": "Enable shake gesture to show update menu for debugging/testing purposes",
|
|
3659
3755
|
"complexTypes": [],
|
|
3660
3756
|
"type": "boolean | undefined"
|
|
3757
|
+
},
|
|
3758
|
+
{
|
|
3759
|
+
"name": "allowShakeChannelSelector",
|
|
3760
|
+
"tags": [
|
|
3761
|
+
{
|
|
3762
|
+
"text": "false",
|
|
3763
|
+
"name": "default"
|
|
3764
|
+
},
|
|
3765
|
+
{
|
|
3766
|
+
"text": "8.43.0",
|
|
3767
|
+
"name": "since"
|
|
3768
|
+
}
|
|
3769
|
+
],
|
|
3770
|
+
"docs": "Enable the shake gesture to show a channel selector menu for switching between update channels.\nWhen enabled AND `shakeMenu` is true, the shake gesture shows a channel selector\ninstead of the default debug menu (Go Home/Reload/Close).\n\nAfter selecting a channel, the app automatically checks for updates and downloads if available.\nOnly works if channels have `allow_self_set` enabled on the backend.\n\nOnly available for Android and iOS.",
|
|
3771
|
+
"complexTypes": [],
|
|
3772
|
+
"type": "boolean | undefined"
|
|
3661
3773
|
}
|
|
3662
3774
|
],
|
|
3663
3775
|
"docs": "CapacitorUpdater can be configured with these options:"
|