capacitor-extended-dialog 0.0.1

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 (30) hide show
  1. package/CapacitorExtendedDialog.podspec +17 -0
  2. package/Package.swift +28 -0
  3. package/README.md +190 -0
  4. package/android/build.gradle +59 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/albermonte/extendeddialog/DialogStyleOptions.java +99 -0
  7. package/android/src/main/java/com/albermonte/extendeddialog/ExtendedDialog.java +734 -0
  8. package/android/src/main/java/com/albermonte/extendeddialog/ExtendedDialogPlugin.java +207 -0
  9. package/android/src/main/java/com/albermonte/extendeddialog/FullScreenDialogFragment.java +589 -0
  10. package/android/src/main/res/.gitkeep +0 -0
  11. package/android/src/main/res/values/themes.xml +34 -0
  12. package/dist/docs.json +384 -0
  13. package/dist/esm/definitions.d.ts +101 -0
  14. package/dist/esm/definitions.js +2 -0
  15. package/dist/esm/definitions.js.map +1 -0
  16. package/dist/esm/index.d.ts +4 -0
  17. package/dist/esm/index.js +7 -0
  18. package/dist/esm/index.js.map +1 -0
  19. package/dist/esm/web.d.ts +14 -0
  20. package/dist/esm/web.js +193 -0
  21. package/dist/esm/web.js.map +1 -0
  22. package/dist/plugin.cjs.js +207 -0
  23. package/dist/plugin.cjs.js.map +1 -0
  24. package/dist/plugin.js +210 -0
  25. package/dist/plugin.js.map +1 -0
  26. package/ios/Sources/ExtendedDialogPlugin/ExtendedDialog.swift +497 -0
  27. package/ios/Sources/ExtendedDialogPlugin/ExtendedDialogPlugin.swift +177 -0
  28. package/ios/Sources/ExtendedDialogPlugin/FullScreenDialogViewController.swift +506 -0
  29. package/ios/Tests/ExtendedDialogPluginTests/ExtendedDialogPluginTests.swift +15 -0
  30. package/package.json +80 -0
@@ -0,0 +1,734 @@
1
+ package com.albermonte.extendeddialog;
2
+
3
+ import android.app.Activity;
4
+ import android.content.Context;
5
+ import android.content.DialogInterface;
6
+ import android.content.res.ColorStateList;
7
+ import android.text.InputType;
8
+ import android.util.TypedValue;
9
+ import android.view.ContextThemeWrapper;
10
+ import android.view.View;
11
+ import android.view.WindowManager;
12
+ import android.widget.Button;
13
+ import android.widget.LinearLayout;
14
+ import android.widget.TextView;
15
+ import androidx.appcompat.app.AlertDialog;
16
+ import androidx.core.widget.TextViewCompat;
17
+ import androidx.fragment.app.FragmentActivity;
18
+ import com.google.android.material.color.MaterialColors;
19
+ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
20
+ import com.google.android.material.textfield.TextInputEditText;
21
+ import com.google.android.material.textfield.TextInputLayout;
22
+ import java.util.ArrayList;
23
+ import java.util.HashSet;
24
+ import java.util.List;
25
+ import java.util.Set;
26
+ import org.json.JSONArray;
27
+ import org.json.JSONException;
28
+ import org.json.JSONObject;
29
+
30
+ public class ExtendedDialog {
31
+
32
+ /**
33
+ * Wraps the given context with a Material3 theme overlay.
34
+ * This ensures MaterialAlertDialogBuilder works regardless of the app's base theme.
35
+ */
36
+ private Context getThemedContext(Context context) {
37
+ return new ContextThemeWrapper(context, com.google.android.material.R.style.Theme_Material3_DayNight_Dialog);
38
+ }
39
+
40
+ /**
41
+ * Applies style options to an AlertDialog after it's shown.
42
+ * When no custom styles are provided, M3 typography is applied by default.
43
+ */
44
+ private void applyDialogStyles(AlertDialog dialog, DialogStyleOptions styleOptions) {
45
+ // Apply M3 typography defaults first
46
+ applyM3Typography(dialog);
47
+
48
+ if (styleOptions == null || !styleOptions.hasStyles()) {
49
+ return;
50
+ }
51
+
52
+ // Style positive button
53
+ Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
54
+ if (positiveButton != null) {
55
+ if (styleOptions.getButtonColor() != null) {
56
+ positiveButton.setTextColor(ColorStateList.valueOf(styleOptions.getButtonColor()));
57
+ }
58
+ if (styleOptions.getButtonFontSize() != null) {
59
+ positiveButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getButtonFontSize());
60
+ }
61
+ }
62
+
63
+ // Style negative button
64
+ Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
65
+ if (negativeButton != null) {
66
+ if (styleOptions.getCancelButtonColor() != null) {
67
+ negativeButton.setTextColor(ColorStateList.valueOf(styleOptions.getCancelButtonColor()));
68
+ }
69
+ if (styleOptions.getButtonFontSize() != null) {
70
+ negativeButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getButtonFontSize());
71
+ }
72
+ }
73
+
74
+ // Style title text
75
+ // MaterialAlertDialogBuilder uses appcompat's alertTitle, not android's
76
+ TextView titleView = dialog.findViewById(androidx.appcompat.R.id.alertTitle);
77
+ if (titleView != null) {
78
+ if (styleOptions.getTitleColor() != null) {
79
+ titleView.setTextColor(styleOptions.getTitleColor());
80
+ }
81
+ if (styleOptions.getTitleFontSize() != null) {
82
+ titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getTitleFontSize());
83
+ }
84
+ }
85
+
86
+ // Style message text
87
+ TextView messageView = dialog.findViewById(android.R.id.message);
88
+ if (messageView != null) {
89
+ if (styleOptions.getMessageFontSize() != null) {
90
+ messageView.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getMessageFontSize());
91
+ }
92
+ if (styleOptions.getMessageColor() != null) {
93
+ messageView.setTextColor(styleOptions.getMessageColor());
94
+ }
95
+ }
96
+
97
+ // Style background
98
+ if (styleOptions.getBackgroundColor() != null && dialog.getWindow() != null) {
99
+ dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(styleOptions.getBackgroundColor()));
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Applies Material 3 typography and colors to dialog title, message, and buttons.
105
+ * M3 dialog specs: Title uses onSurface (#1D1B20), Message uses onSurfaceVariant (#49454F),
106
+ * Buttons use colorPrimary (#6750A4 baseline)
107
+ */
108
+ private void applyM3Typography(AlertDialog dialog) {
109
+ Context ctx = dialog.getContext();
110
+
111
+ // Apply M3 HeadlineSmall to title with onSurface color
112
+ // MaterialAlertDialogBuilder uses appcompat's alertTitle, not android's
113
+ TextView titleView = dialog.findViewById(androidx.appcompat.R.id.alertTitle);
114
+ if (titleView != null) {
115
+ TextViewCompat.setTextAppearance(titleView,
116
+ com.google.android.material.R.style.TextAppearance_Material3_HeadlineSmall);
117
+ // M3 dialog headline color: onSurface (#1D1B20)
118
+ TypedValue onSurface = new TypedValue();
119
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorOnSurface, onSurface, true)) {
120
+ titleView.setTextColor(onSurface.data);
121
+ }
122
+ }
123
+
124
+ // Apply M3 BodyMedium to message with onSurfaceVariant color
125
+ TextView messageView = dialog.findViewById(android.R.id.message);
126
+ if (messageView != null) {
127
+ TextViewCompat.setTextAppearance(messageView,
128
+ com.google.android.material.R.style.TextAppearance_Material3_BodyMedium);
129
+ // M3 dialog supporting text color: onSurfaceVariant (#49454F)
130
+ TypedValue onSurfaceVariant = new TypedValue();
131
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorOnSurfaceVariant, onSurfaceVariant, true)) {
132
+ messageView.setTextColor(onSurfaceVariant.data);
133
+ }
134
+ }
135
+
136
+ // Apply M3 colorPrimary to dialog buttons
137
+ // This ensures consistent button colors across all dialogs regardless of app theme
138
+ TypedValue primaryColor = new TypedValue();
139
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorPrimary, primaryColor, true)) {
140
+ Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
141
+ if (positiveButton != null) {
142
+ positiveButton.setTextColor(ColorStateList.valueOf(primaryColor.data));
143
+ }
144
+ Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
145
+ if (negativeButton != null) {
146
+ negativeButton.setTextColor(ColorStateList.valueOf(primaryColor.data));
147
+ }
148
+ Button neutralButton = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
149
+ if (neutralButton != null) {
150
+ neutralButton.setTextColor(ColorStateList.valueOf(primaryColor.data));
151
+ }
152
+ }
153
+ }
154
+
155
+ public interface AlertCallback {
156
+ void onDismiss();
157
+ }
158
+
159
+ public interface ConfirmCallback {
160
+ void onResult(boolean value);
161
+ }
162
+
163
+ public interface PromptCallback {
164
+ void onResult(String value, boolean cancelled);
165
+ }
166
+
167
+ public interface SingleSelectCallback {
168
+ void onResult(String value, boolean cancelled);
169
+ }
170
+
171
+ public interface MultiSelectCallback {
172
+ void onResult(String[] values, boolean cancelled);
173
+ }
174
+
175
+ public void showAlert(
176
+ Activity activity,
177
+ String title,
178
+ String message,
179
+ String buttonTitle,
180
+ boolean fullscreen,
181
+ DialogStyleOptions styleOptions,
182
+ AlertCallback callback
183
+ ) {
184
+ if (fullscreen && activity instanceof FragmentActivity) {
185
+ showFullScreenAlert((FragmentActivity) activity, title, message, buttonTitle, styleOptions, callback);
186
+ } else {
187
+ showBasicAlert(activity, title, message, buttonTitle, styleOptions, callback);
188
+ }
189
+ }
190
+
191
+ private void showBasicAlert(
192
+ Activity activity,
193
+ String title,
194
+ String message,
195
+ String buttonTitle,
196
+ DialogStyleOptions styleOptions,
197
+ AlertCallback callback
198
+ ) {
199
+ activity.runOnUiThread(() -> {
200
+ Context themedContext = getThemedContext(activity);
201
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(themedContext,
202
+ R.style.ThemeOverlay_ExtendedDialog_MaterialAlertDialog);
203
+
204
+ if (title != null && !title.isEmpty()) {
205
+ builder.setTitle(title);
206
+ }
207
+ builder.setMessage(message);
208
+ builder.setPositiveButton(buttonTitle != null ? buttonTitle : "OK", (dialog, which) -> callback.onDismiss());
209
+ builder.setOnCancelListener((dialog) -> callback.onDismiss());
210
+
211
+ AlertDialog dialog = builder.create();
212
+ dialog.show();
213
+ applyDialogStyles(dialog, styleOptions);
214
+ });
215
+ }
216
+
217
+ private void showFullScreenAlert(
218
+ FragmentActivity activity,
219
+ String title,
220
+ String message,
221
+ String buttonTitle,
222
+ DialogStyleOptions styleOptions,
223
+ AlertCallback callback
224
+ ) {
225
+ activity.runOnUiThread(() -> {
226
+ FullScreenDialogFragment fragment = FullScreenDialogFragment.newInstance(
227
+ FullScreenDialogFragment.DialogType.ALERT,
228
+ title,
229
+ message,
230
+ buttonTitle != null ? buttonTitle : "OK",
231
+ null,
232
+ null,
233
+ null,
234
+ null,
235
+ false,
236
+ styleOptions
237
+ );
238
+ fragment.setAlertCallback(callback);
239
+ fragment.show(activity.getSupportFragmentManager(), "fullscreen_alert");
240
+ });
241
+ }
242
+
243
+ public void showConfirm(
244
+ Activity activity,
245
+ String title,
246
+ String message,
247
+ String okButtonTitle,
248
+ String cancelButtonTitle,
249
+ boolean fullscreen,
250
+ DialogStyleOptions styleOptions,
251
+ ConfirmCallback callback
252
+ ) {
253
+ if (fullscreen && activity instanceof FragmentActivity) {
254
+ showFullScreenConfirm((FragmentActivity) activity, title, message, okButtonTitle, cancelButtonTitle, styleOptions, callback);
255
+ } else {
256
+ showBasicConfirm(activity, title, message, okButtonTitle, cancelButtonTitle, styleOptions, callback);
257
+ }
258
+ }
259
+
260
+ private void showBasicConfirm(
261
+ Activity activity,
262
+ String title,
263
+ String message,
264
+ String okButtonTitle,
265
+ String cancelButtonTitle,
266
+ DialogStyleOptions styleOptions,
267
+ ConfirmCallback callback
268
+ ) {
269
+ activity.runOnUiThread(() -> {
270
+ Context themedContext = getThemedContext(activity);
271
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(themedContext,
272
+ R.style.ThemeOverlay_ExtendedDialog_MaterialAlertDialog);
273
+
274
+ if (title != null && !title.isEmpty()) {
275
+ builder.setTitle(title);
276
+ }
277
+ builder.setMessage(message);
278
+ builder.setPositiveButton(okButtonTitle != null ? okButtonTitle : "OK", (dialog, which) -> callback.onResult(true));
279
+ builder.setNegativeButton(cancelButtonTitle != null ? cancelButtonTitle : "Cancel", (dialog, which) ->
280
+ callback.onResult(false)
281
+ );
282
+ builder.setOnCancelListener((dialog) -> callback.onResult(false));
283
+
284
+ AlertDialog dialog = builder.create();
285
+ dialog.show();
286
+ applyDialogStyles(dialog, styleOptions);
287
+ });
288
+ }
289
+
290
+ private void showFullScreenConfirm(
291
+ FragmentActivity activity,
292
+ String title,
293
+ String message,
294
+ String okButtonTitle,
295
+ String cancelButtonTitle,
296
+ DialogStyleOptions styleOptions,
297
+ ConfirmCallback callback
298
+ ) {
299
+ activity.runOnUiThread(() -> {
300
+ FullScreenDialogFragment fragment = FullScreenDialogFragment.newInstance(
301
+ FullScreenDialogFragment.DialogType.CONFIRM,
302
+ title,
303
+ message,
304
+ okButtonTitle != null ? okButtonTitle : "OK",
305
+ cancelButtonTitle != null ? cancelButtonTitle : "Cancel",
306
+ null,
307
+ null,
308
+ null,
309
+ false,
310
+ styleOptions
311
+ );
312
+ fragment.setConfirmCallback(callback);
313
+ fragment.show(activity.getSupportFragmentManager(), "fullscreen_confirm");
314
+ });
315
+ }
316
+
317
+ public void showPrompt(
318
+ Activity activity,
319
+ String title,
320
+ String message,
321
+ String okButtonTitle,
322
+ String cancelButtonTitle,
323
+ String inputPlaceholder,
324
+ String inputText,
325
+ boolean fullscreen,
326
+ boolean focusInput,
327
+ DialogStyleOptions styleOptions,
328
+ PromptCallback callback
329
+ ) {
330
+ if (fullscreen && activity instanceof FragmentActivity) {
331
+ showFullScreenPrompt(
332
+ (FragmentActivity) activity,
333
+ title,
334
+ message,
335
+ okButtonTitle,
336
+ cancelButtonTitle,
337
+ inputPlaceholder,
338
+ inputText,
339
+ focusInput,
340
+ styleOptions,
341
+ callback
342
+ );
343
+ } else {
344
+ showBasicPrompt(
345
+ activity,
346
+ title,
347
+ message,
348
+ okButtonTitle,
349
+ cancelButtonTitle,
350
+ inputPlaceholder,
351
+ inputText,
352
+ focusInput,
353
+ styleOptions,
354
+ callback
355
+ );
356
+ }
357
+ }
358
+
359
+ private void showBasicPrompt(
360
+ Activity activity,
361
+ String title,
362
+ String message,
363
+ String okButtonTitle,
364
+ String cancelButtonTitle,
365
+ String inputPlaceholder,
366
+ String inputText,
367
+ boolean focusInput,
368
+ DialogStyleOptions styleOptions,
369
+ PromptCallback callback
370
+ ) {
371
+ activity.runOnUiThread(() -> {
372
+ Context themedContext = getThemedContext(activity);
373
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(themedContext,
374
+ R.style.ThemeOverlay_ExtendedDialog_MaterialAlertDialog);
375
+
376
+ if (title != null && !title.isEmpty()) {
377
+ builder.setTitle(title);
378
+ }
379
+ builder.setMessage(message);
380
+
381
+ // Use Material 3 TextInputLayout with outlined style
382
+ TextInputLayout textInputLayout = new TextInputLayout(themedContext, null,
383
+ com.google.android.material.R.attr.textInputOutlinedStyle);
384
+ textInputLayout.setBoxBackgroundMode(TextInputLayout.BOX_BACKGROUND_OUTLINE);
385
+
386
+ final TextInputEditText input = new TextInputEditText(textInputLayout.getContext());
387
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
388
+ if (inputPlaceholder != null) {
389
+ textInputLayout.setHint(inputPlaceholder);
390
+ }
391
+ if (inputText != null) {
392
+ input.setText(inputText);
393
+ input.setSelection(inputText.length());
394
+ }
395
+ textInputLayout.addView(input);
396
+
397
+ LinearLayout container = new LinearLayout(themedContext);
398
+ container.setOrientation(LinearLayout.VERTICAL);
399
+ int padding = (int) (20 * activity.getResources().getDisplayMetrics().density);
400
+ container.setPadding(padding, padding / 2, padding, 0);
401
+ container.addView(textInputLayout);
402
+ builder.setView(container);
403
+
404
+ builder.setPositiveButton(okButtonTitle != null ? okButtonTitle : "OK", (dialog, which) ->
405
+ callback.onResult(input.getText().toString(), false)
406
+ );
407
+ builder.setNegativeButton(cancelButtonTitle != null ? cancelButtonTitle : "Cancel", (dialog, which) ->
408
+ callback.onResult("", true)
409
+ );
410
+ builder.setOnCancelListener((dialog) -> callback.onResult("", true));
411
+
412
+ AlertDialog dialog = builder.create();
413
+ if (focusInput) {
414
+ dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
415
+ input.requestFocus();
416
+ } else {
417
+ dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
418
+ }
419
+ dialog.show();
420
+ applyDialogStyles(dialog, styleOptions);
421
+ });
422
+ }
423
+
424
+ private void showFullScreenPrompt(
425
+ FragmentActivity activity,
426
+ String title,
427
+ String message,
428
+ String okButtonTitle,
429
+ String cancelButtonTitle,
430
+ String inputPlaceholder,
431
+ String inputText,
432
+ boolean focusInput,
433
+ DialogStyleOptions styleOptions,
434
+ PromptCallback callback
435
+ ) {
436
+ activity.runOnUiThread(() -> {
437
+ FullScreenDialogFragment fragment = FullScreenDialogFragment.newInstance(
438
+ FullScreenDialogFragment.DialogType.PROMPT,
439
+ title,
440
+ message,
441
+ okButtonTitle != null ? okButtonTitle : "OK",
442
+ cancelButtonTitle != null ? cancelButtonTitle : "Cancel",
443
+ inputPlaceholder,
444
+ inputText,
445
+ null,
446
+ focusInput,
447
+ styleOptions
448
+ );
449
+ fragment.setPromptCallback(callback);
450
+ fragment.show(activity.getSupportFragmentManager(), "fullscreen_prompt");
451
+ });
452
+ }
453
+
454
+ public void showSingleSelect(
455
+ Activity activity,
456
+ String title,
457
+ String message,
458
+ JSONArray options,
459
+ String selectedValue,
460
+ String okButtonTitle,
461
+ String cancelButtonTitle,
462
+ boolean fullscreen,
463
+ DialogStyleOptions styleOptions,
464
+ SingleSelectCallback callback
465
+ ) {
466
+ if (fullscreen && activity instanceof FragmentActivity) {
467
+ showFullScreenSingleSelect(
468
+ (FragmentActivity) activity,
469
+ title,
470
+ message,
471
+ options,
472
+ selectedValue,
473
+ okButtonTitle,
474
+ cancelButtonTitle,
475
+ styleOptions,
476
+ callback
477
+ );
478
+ } else {
479
+ showBasicSingleSelect(
480
+ activity,
481
+ title,
482
+ message,
483
+ options,
484
+ selectedValue,
485
+ okButtonTitle,
486
+ cancelButtonTitle,
487
+ styleOptions,
488
+ callback
489
+ );
490
+ }
491
+ }
492
+
493
+ private void showBasicSingleSelect(
494
+ Activity activity,
495
+ String title,
496
+ String message,
497
+ JSONArray options,
498
+ String selectedValue,
499
+ String okButtonTitle,
500
+ String cancelButtonTitle,
501
+ DialogStyleOptions styleOptions,
502
+ SingleSelectCallback callback
503
+ ) {
504
+ activity.runOnUiThread(() -> {
505
+ try {
506
+ List<String> labels = new ArrayList<>();
507
+ List<String> values = new ArrayList<>();
508
+ int checkedItem = -1;
509
+
510
+ for (int i = 0; i < options.length(); i++) {
511
+ JSONObject option = options.getJSONObject(i);
512
+ String label = option.getString("label");
513
+ String value = option.getString("value");
514
+ labels.add(label);
515
+ values.add(value);
516
+ if (selectedValue != null && selectedValue.equals(value)) {
517
+ checkedItem = i;
518
+ }
519
+ }
520
+
521
+ final int[] selectedIndex = { checkedItem };
522
+
523
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getThemedContext(activity),
524
+ R.style.ThemeOverlay_ExtendedDialog_MaterialAlertDialog);
525
+
526
+ if (title != null && !title.isEmpty()) {
527
+ builder.setTitle(title);
528
+ }
529
+
530
+ builder.setSingleChoiceItems(labels.toArray(new String[0]), checkedItem, (dialog, which) -> {
531
+ selectedIndex[0] = which;
532
+ // M3 guideline: Enable confirming action when a choice is made
533
+ ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
534
+ });
535
+
536
+ builder.setPositiveButton(okButtonTitle != null ? okButtonTitle : "OK", (dialog, which) -> {
537
+ if (selectedIndex[0] >= 0 && selectedIndex[0] < values.size()) {
538
+ callback.onResult(values.get(selectedIndex[0]), false);
539
+ } else {
540
+ callback.onResult(null, false);
541
+ }
542
+ });
543
+ builder.setNegativeButton(cancelButtonTitle != null ? cancelButtonTitle : "Cancel", (dialog, which) ->
544
+ callback.onResult(null, true)
545
+ );
546
+ builder.setOnCancelListener((dialog) -> callback.onResult(null, true));
547
+
548
+ AlertDialog dialog = builder.create();
549
+ dialog.show();
550
+ applyDialogStyles(dialog, styleOptions);
551
+
552
+ // M3 guideline: Disable confirming action until a choice is made
553
+ if (checkedItem < 0) {
554
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
555
+ }
556
+ } catch (JSONException e) {
557
+ callback.onResult(null, true);
558
+ }
559
+ });
560
+ }
561
+
562
+ private void showFullScreenSingleSelect(
563
+ FragmentActivity activity,
564
+ String title,
565
+ String message,
566
+ JSONArray options,
567
+ String selectedValue,
568
+ String okButtonTitle,
569
+ String cancelButtonTitle,
570
+ DialogStyleOptions styleOptions,
571
+ SingleSelectCallback callback
572
+ ) {
573
+ activity.runOnUiThread(() -> {
574
+ FullScreenDialogFragment fragment = FullScreenDialogFragment.newInstance(
575
+ FullScreenDialogFragment.DialogType.SINGLE_SELECT,
576
+ title,
577
+ message,
578
+ okButtonTitle != null ? okButtonTitle : "OK",
579
+ cancelButtonTitle != null ? cancelButtonTitle : "Cancel",
580
+ null,
581
+ selectedValue,
582
+ options.toString(),
583
+ false,
584
+ styleOptions
585
+ );
586
+ fragment.setSingleSelectCallback(callback);
587
+ fragment.show(activity.getSupportFragmentManager(), "fullscreen_single_select");
588
+ });
589
+ }
590
+
591
+ public void showMultiSelect(
592
+ Activity activity,
593
+ String title,
594
+ String message,
595
+ JSONArray options,
596
+ JSONArray selectedValues,
597
+ String okButtonTitle,
598
+ String cancelButtonTitle,
599
+ boolean fullscreen,
600
+ DialogStyleOptions styleOptions,
601
+ MultiSelectCallback callback
602
+ ) {
603
+ if (fullscreen && activity instanceof FragmentActivity) {
604
+ showFullScreenMultiSelect(
605
+ (FragmentActivity) activity,
606
+ title,
607
+ message,
608
+ options,
609
+ selectedValues,
610
+ okButtonTitle,
611
+ cancelButtonTitle,
612
+ styleOptions,
613
+ callback
614
+ );
615
+ } else {
616
+ showBasicMultiSelect(
617
+ activity,
618
+ title,
619
+ message,
620
+ options,
621
+ selectedValues,
622
+ okButtonTitle,
623
+ cancelButtonTitle,
624
+ styleOptions,
625
+ callback
626
+ );
627
+ }
628
+ }
629
+
630
+ private void showBasicMultiSelect(
631
+ Activity activity,
632
+ String title,
633
+ String message,
634
+ JSONArray options,
635
+ JSONArray selectedValues,
636
+ String okButtonTitle,
637
+ String cancelButtonTitle,
638
+ DialogStyleOptions styleOptions,
639
+ MultiSelectCallback callback
640
+ ) {
641
+ activity.runOnUiThread(() -> {
642
+ try {
643
+ List<String> labels = new ArrayList<>();
644
+ List<String> values = new ArrayList<>();
645
+ Set<String> selectedSet = new HashSet<>();
646
+
647
+ if (selectedValues != null) {
648
+ for (int i = 0; i < selectedValues.length(); i++) {
649
+ selectedSet.add(selectedValues.getString(i));
650
+ }
651
+ }
652
+
653
+ for (int i = 0; i < options.length(); i++) {
654
+ JSONObject option = options.getJSONObject(i);
655
+ labels.add(option.getString("label"));
656
+ values.add(option.getString("value"));
657
+ }
658
+
659
+ boolean[] checkedItems = new boolean[values.size()];
660
+ for (int i = 0; i < values.size(); i++) {
661
+ checkedItems[i] = selectedSet.contains(values.get(i));
662
+ }
663
+
664
+ final Set<String> resultSet = new HashSet<>(selectedSet);
665
+
666
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getThemedContext(activity),
667
+ R.style.ThemeOverlay_ExtendedDialog_MaterialAlertDialog);
668
+
669
+ if (title != null && !title.isEmpty()) {
670
+ builder.setTitle(title);
671
+ }
672
+
673
+ builder.setMultiChoiceItems(labels.toArray(new String[0]), checkedItems, (dialog, which, isChecked) -> {
674
+ if (isChecked) {
675
+ resultSet.add(values.get(which));
676
+ } else {
677
+ resultSet.remove(values.get(which));
678
+ }
679
+ // M3 guideline: Enable confirming action when a choice is made
680
+ ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!resultSet.isEmpty());
681
+ });
682
+
683
+ builder.setPositiveButton(okButtonTitle != null ? okButtonTitle : "OK", (dialog, which) ->
684
+ callback.onResult(resultSet.toArray(new String[0]), false)
685
+ );
686
+ builder.setNegativeButton(cancelButtonTitle != null ? cancelButtonTitle : "Cancel", (dialog, which) ->
687
+ callback.onResult(new String[0], true)
688
+ );
689
+ builder.setOnCancelListener((dialog) -> callback.onResult(new String[0], true));
690
+
691
+ AlertDialog dialog = builder.create();
692
+ dialog.show();
693
+ applyDialogStyles(dialog, styleOptions);
694
+
695
+ // M3 guideline: Disable confirming action until a choice is made
696
+ if (resultSet.isEmpty()) {
697
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
698
+ }
699
+ } catch (JSONException e) {
700
+ callback.onResult(new String[0], true);
701
+ }
702
+ });
703
+ }
704
+
705
+ private void showFullScreenMultiSelect(
706
+ FragmentActivity activity,
707
+ String title,
708
+ String message,
709
+ JSONArray options,
710
+ JSONArray selectedValues,
711
+ String okButtonTitle,
712
+ String cancelButtonTitle,
713
+ DialogStyleOptions styleOptions,
714
+ MultiSelectCallback callback
715
+ ) {
716
+ activity.runOnUiThread(() -> {
717
+ String selectedValuesStr = selectedValues != null ? selectedValues.toString() : null;
718
+ FullScreenDialogFragment fragment = FullScreenDialogFragment.newInstance(
719
+ FullScreenDialogFragment.DialogType.MULTI_SELECT,
720
+ title,
721
+ message,
722
+ okButtonTitle != null ? okButtonTitle : "OK",
723
+ cancelButtonTitle != null ? cancelButtonTitle : "Cancel",
724
+ null,
725
+ selectedValuesStr,
726
+ options.toString(),
727
+ false,
728
+ styleOptions
729
+ );
730
+ fragment.setMultiSelectCallback(callback);
731
+ fragment.show(activity.getSupportFragmentManager(), "fullscreen_multi_select");
732
+ });
733
+ }
734
+ }