@capgo/capacitor-updater 8.48.0 → 8.49.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.
@@ -19,10 +19,13 @@ import android.widget.ListView;
19
19
  import android.widget.ProgressBar;
20
20
  import com.getcapacitor.Bridge;
21
21
  import com.getcapacitor.BridgeActivity;
22
+ import com.getcapacitor.JSArray;
23
+ import com.getcapacitor.JSObject;
22
24
  import java.util.ArrayList;
23
25
  import java.util.List;
24
26
  import java.util.Map;
25
27
  import org.json.JSONArray;
28
+ import org.json.JSONObject;
26
29
 
27
30
  public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetector.Listener {
28
31
 
@@ -36,11 +39,13 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
36
39
  private ThreeFingerPinchDetector pinchDetector;
37
40
  private boolean isShowing = false;
38
41
  private Logger logger;
42
+ private String gesture;
39
43
 
40
44
  public ShakeMenu(CapacitorUpdaterPlugin plugin, BridgeActivity activity, Logger logger, String gesture) {
41
45
  this.plugin = plugin;
42
46
  this.activity = activity;
43
47
  this.logger = logger;
48
+ this.gesture = gesture;
44
49
 
45
50
  if (CapacitorUpdaterPlugin.SHAKE_MENU_GESTURE_THREE_FINGER_PINCH.equals(gesture)) {
46
51
  this.pinchDetector = new ThreeFingerPinchDetector(this, logger);
@@ -52,6 +57,10 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
52
57
  }
53
58
  }
54
59
 
60
+ public boolean usesGesture(String gesture) {
61
+ return this.gesture != null && this.gesture.equals(gesture);
62
+ }
63
+
55
64
  public void stop() {
56
65
  if (shakeDetector != null) {
57
66
  shakeDetector.stop();
@@ -114,21 +123,43 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
114
123
  }
115
124
  String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
116
125
  String title = "Preview " + appName + " Menu";
117
- String message = "Reload the current preview or leave the test app.";
118
- String okButtonTitle = "Leave test app";
119
- String reloadButtonTitle = "Reload app";
120
- String cancelButtonTitle = "Close menu";
126
+ String message = "Reload, switch, or leave the current preview.";
127
+ List<String> actions = new ArrayList<>();
128
+ actions.add("Reload preview");
129
+ if (plugin.previewMenuPreviews().length() > 0) {
130
+ actions.add("Switch preview");
131
+ }
132
+ actions.add("Leave test app");
133
+ final boolean[] openingNestedSelector = { false };
134
+ final boolean[] previewActionRunning = { false };
121
135
 
122
136
  AlertDialog.Builder builder = new AlertDialog.Builder(activity);
123
137
  builder.setTitle(title);
124
138
  builder.setMessage(message);
125
-
126
- builder.setPositiveButton(okButtonTitle, null);
127
- builder.setNeutralButton(reloadButtonTitle, null);
139
+ builder.setItems(actions.toArray(new String[0]), (dialogInterface, which) -> {
140
+ AlertDialog dialog = (AlertDialog) dialogInterface;
141
+ String action = actions.get(which);
142
+ if ("Reload preview".equals(action)) {
143
+ previewActionRunning[0] = true;
144
+ logger.info("Reloading webview");
145
+ runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
146
+ plugin.reloadPreviewSessionFromShakeMenu()
147
+ );
148
+ } else if ("Switch preview".equals(action)) {
149
+ openingNestedSelector[0] = true;
150
+ dialog.dismiss();
151
+ showPreviewSelector();
152
+ } else {
153
+ previewActionRunning[0] = true;
154
+ runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
155
+ plugin.leavePreviewSessionFromShakeMenu()
156
+ );
157
+ }
158
+ });
128
159
 
129
160
  // Cancel button
130
161
  builder.setNegativeButton(
131
- cancelButtonTitle,
162
+ "Close menu",
132
163
  new DialogInterface.OnClickListener() {
133
164
  public void onClick(DialogInterface dialog, int id) {
134
165
  logger.info("Shake menu cancelled");
@@ -139,21 +170,12 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
139
170
  );
140
171
 
141
172
  AlertDialog dialog = builder.create();
142
- dialog.setOnDismissListener((dialogInterface) -> isShowing = false);
143
- dialog.show();
144
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((view) -> {
145
- setPreviewMenuButtonsEnabled(dialog, false);
146
- runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
147
- plugin.leavePreviewSessionFromShakeMenu()
148
- );
149
- });
150
- dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener((view) -> {
151
- setPreviewMenuButtonsEnabled(dialog, false);
152
- logger.info("Reloading webview");
153
- runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
154
- plugin.reloadPreviewSessionFromShakeMenu()
155
- );
173
+ dialog.setOnDismissListener((dialogInterface) -> {
174
+ if (!openingNestedSelector[0] && !previewActionRunning[0]) {
175
+ isShowing = false;
176
+ }
156
177
  });
178
+ dialog.show();
157
179
  } catch (Exception e) {
158
180
  logger.error("Error showing shake menu: " + e.getMessage());
159
181
  isShowing = false;
@@ -165,29 +187,40 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
165
187
  try {
166
188
  String appName = activity.getPackageManager().getApplicationLabel(activity.getApplicationInfo()).toString();
167
189
  String title = "Preview " + appName + " Menu";
168
- String message = "Reload or leave the current preview, or switch update channel.";
169
- String[] actions = { "Reload preview", "Leave test app", "Switch channel" };
170
- final boolean[] openingChannelSelector = { false };
190
+ String message = "Reload, switch, or leave the current preview.";
191
+ List<String> actions = new ArrayList<>();
192
+ actions.add("Reload preview");
193
+ if (plugin.previewMenuPreviews().length() > 0) {
194
+ actions.add("Switch preview");
195
+ }
196
+ actions.add("Leave test app");
197
+ actions.add("Switch channel");
198
+ final boolean[] openingNestedSelector = { false };
171
199
  final boolean[] previewActionRunning = { false };
172
200
 
173
201
  AlertDialog.Builder builder = new AlertDialog.Builder(activity);
174
202
  builder.setTitle(title);
175
203
  builder.setMessage(message);
176
- builder.setItems(actions, (dialogInterface, which) -> {
204
+ builder.setItems(actions.toArray(new String[0]), (dialogInterface, which) -> {
177
205
  AlertDialog dialog = (AlertDialog) dialogInterface;
178
- if (which == 0) {
206
+ String action = actions.get(which);
207
+ if ("Reload preview".equals(action)) {
179
208
  previewActionRunning[0] = true;
180
209
  logger.info("Reloading webview");
181
210
  runPreviewMenuAction(dialog, "Could not reload the test app.", "Error reloading test app: ", () ->
182
211
  plugin.reloadPreviewSessionFromShakeMenu()
183
212
  );
184
- } else if (which == 1) {
213
+ } else if ("Leave test app".equals(action)) {
185
214
  previewActionRunning[0] = true;
186
215
  runPreviewMenuAction(dialog, "Could not leave the test app.", "Error leaving test app: ", () ->
187
216
  plugin.leavePreviewSessionFromShakeMenu()
188
217
  );
218
+ } else if ("Switch preview".equals(action)) {
219
+ openingNestedSelector[0] = true;
220
+ dialog.dismiss();
221
+ showPreviewSelector();
189
222
  } else {
190
- openingChannelSelector[0] = true;
223
+ openingNestedSelector[0] = true;
191
224
  dialog.dismiss();
192
225
  showChannelSelector();
193
226
  }
@@ -200,7 +233,7 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
200
233
 
201
234
  AlertDialog dialog = builder.create();
202
235
  dialog.setOnDismissListener((dialogInterface) -> {
203
- if (!openingChannelSelector[0] && !previewActionRunning[0]) {
236
+ if (!openingNestedSelector[0] && !previewActionRunning[0]) {
204
237
  isShowing = false;
205
238
  }
206
239
  });
@@ -235,6 +268,146 @@ public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetect
235
268
  dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(enabled);
236
269
  }
237
270
 
271
+ private void showPreviewSelector() {
272
+ activity.runOnUiThread(() -> {
273
+ try {
274
+ JSArray previewsRaw = plugin.previewMenuPreviews();
275
+ List<JSObject> previews = new ArrayList<>();
276
+ for (int i = 0; i < previewsRaw.length(); i++) {
277
+ Object raw = previewsRaw.opt(i);
278
+ if (raw instanceof JSObject preview) {
279
+ previews.add(preview);
280
+ } else if (raw instanceof JSONObject json) {
281
+ previews.add(JSObject.fromJSONObject(json));
282
+ }
283
+ }
284
+
285
+ if (previews.isEmpty()) {
286
+ showError("No saved previews available on this device.");
287
+ return;
288
+ }
289
+
290
+ presentPreviewPicker(previews);
291
+ } catch (Exception e) {
292
+ logger.error("Error showing preview selector: " + e.getMessage());
293
+ isShowing = false;
294
+ }
295
+ });
296
+ }
297
+
298
+ private String previewLabel(JSObject preview) {
299
+ String name = preview.optString("name", "");
300
+ JSObject bundle = preview.getJSObject("bundle");
301
+ String version = bundle == null ? "" : bundle.optString("version", "");
302
+ String label = !name.isEmpty() ? name : (!version.isEmpty() ? version : preview.optString("id", "Preview"));
303
+ if (preview.optBoolean("isActive", false)) {
304
+ label += " (current)";
305
+ }
306
+ return label;
307
+ }
308
+
309
+ private void presentPreviewPicker(List<JSObject> previews) {
310
+ try {
311
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
312
+ builder.setTitle("Select Preview");
313
+
314
+ LinearLayout layout = new LinearLayout(activity);
315
+ layout.setOrientation(LinearLayout.VERTICAL);
316
+ int padding = dpToPx(16);
317
+ layout.setPadding(padding, padding, padding, padding);
318
+
319
+ EditText searchField = new EditText(activity);
320
+ searchField.setHint("Search previews...");
321
+ searchField.setSingleLine(true);
322
+ layout.addView(searchField);
323
+
324
+ final List<JSObject> displayedPreviews = new ArrayList<>();
325
+ displayedPreviews.addAll(previews.subList(0, Math.min(5, previews.size())));
326
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(
327
+ activity,
328
+ android.R.layout.simple_list_item_1,
329
+ previewLabels(displayedPreviews)
330
+ );
331
+
332
+ ListView listView = new ListView(activity);
333
+ listView.setAdapter(adapter);
334
+ LinearLayout.LayoutParams listParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dpToPx(250));
335
+ listView.setLayoutParams(listParams);
336
+ layout.addView(listView);
337
+
338
+ builder.setView(layout);
339
+ builder.setNegativeButton("Cancel", (dialog, which) -> {
340
+ dialog.dismiss();
341
+ isShowing = false;
342
+ });
343
+
344
+ AlertDialog dialog = builder.create();
345
+ dialog.setOnDismissListener((d) -> isShowing = false);
346
+
347
+ searchField.addTextChangedListener(
348
+ new TextWatcher() {
349
+ @Override
350
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
351
+
352
+ @Override
353
+ public void onTextChanged(CharSequence s, int start, int before, int count) {}
354
+
355
+ @Override
356
+ public void afterTextChanged(Editable s) {
357
+ String query = s.toString().toLowerCase();
358
+ displayedPreviews.clear();
359
+
360
+ for (JSObject preview : previews) {
361
+ if (previewLabel(preview).toLowerCase().contains(query)) {
362
+ displayedPreviews.add(preview);
363
+ if (displayedPreviews.size() >= 5) break;
364
+ }
365
+ }
366
+
367
+ adapter.clear();
368
+ adapter.addAll(previewLabels(displayedPreviews));
369
+ adapter.notifyDataSetChanged();
370
+ }
371
+ }
372
+ );
373
+
374
+ listView.setOnItemClickListener((parent, view, position, id) -> {
375
+ JSObject selectedPreview = displayedPreviews.get(position);
376
+ String previewId = selectedPreview.optString("id", "");
377
+ dialog.dismiss();
378
+ selectPreview(previewId);
379
+ });
380
+
381
+ dialog.show();
382
+ } catch (Exception e) {
383
+ logger.error("Error presenting preview picker: " + e.getMessage());
384
+ isShowing = false;
385
+ }
386
+ }
387
+
388
+ private List<String> previewLabels(List<JSObject> previews) {
389
+ List<String> labels = new ArrayList<>();
390
+ for (JSObject preview : previews) {
391
+ labels.add(previewLabel(preview));
392
+ }
393
+ return labels;
394
+ }
395
+
396
+ private void selectPreview(String previewId) {
397
+ new Thread(() -> {
398
+ try {
399
+ if (!plugin.setPreviewFromShakeMenu(previewId)) {
400
+ activity.runOnUiThread(() -> showError("Could not switch preview."));
401
+ }
402
+ } catch (Exception e) {
403
+ logger.error("Error switching preview: " + e.getMessage());
404
+ activity.runOnUiThread(() -> showError("Error switching preview: " + e.getMessage()));
405
+ } finally {
406
+ isShowing = false;
407
+ }
408
+ }).start();
409
+ }
410
+
238
411
  private void showConfiguredDefaultMenu() {
239
412
  activity.runOnUiThread(() -> {
240
413
  try {
@@ -19,7 +19,7 @@ public class ThreeFingerPinchDetector implements View.OnTouchListener {
19
19
  }
20
20
 
21
21
  private static final int REQUIRED_POINTER_COUNT = 3;
22
- private static final float MIN_SCALE_DELTA = 0.30f;
22
+ private static final float MIN_SCALE_DELTA = 0.12f;
23
23
  private static final long PINCH_TIMEOUT = 1000;
24
24
 
25
25
  private final Listener listener;