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,589 @@
1
+ package com.albermonte.extendeddialog;
2
+
3
+ import android.app.Dialog;
4
+ import android.content.Context;
5
+ import android.content.DialogInterface;
6
+ import android.content.res.ColorStateList;
7
+ import android.graphics.Color;
8
+ import android.os.Bundle;
9
+ import android.text.InputType;
10
+ import android.util.TypedValue;
11
+ import android.view.ContextThemeWrapper;
12
+ import android.view.LayoutInflater;
13
+ import android.view.View;
14
+ import android.view.ViewGroup;
15
+ import android.view.Window;
16
+ import android.view.WindowManager;
17
+ import android.widget.CheckBox;
18
+ import android.widget.LinearLayout;
19
+ import android.widget.RadioButton;
20
+ import android.widget.RadioGroup;
21
+ import android.widget.ScrollView;
22
+ import android.widget.TextView;
23
+ import androidx.annotation.NonNull;
24
+ import androidx.annotation.Nullable;
25
+ import androidx.core.graphics.Insets;
26
+ import androidx.core.view.ViewCompat;
27
+ import androidx.core.view.WindowInsetsCompat;
28
+ import androidx.core.widget.TextViewCompat;
29
+ import androidx.fragment.app.DialogFragment;
30
+ import com.google.android.material.appbar.MaterialToolbar;
31
+ import com.google.android.material.button.MaterialButton;
32
+ import com.google.android.material.textfield.TextInputEditText;
33
+ import com.google.android.material.textfield.TextInputLayout;
34
+ import java.util.ArrayList;
35
+ import java.util.HashSet;
36
+ import java.util.List;
37
+ import java.util.Set;
38
+ import org.json.JSONArray;
39
+ import org.json.JSONException;
40
+ import org.json.JSONObject;
41
+
42
+ public class FullScreenDialogFragment extends DialogFragment {
43
+
44
+ public enum DialogType {
45
+ ALERT,
46
+ CONFIRM,
47
+ PROMPT,
48
+ SINGLE_SELECT,
49
+ MULTI_SELECT
50
+ }
51
+
52
+ private static final String ARG_TYPE = "type";
53
+ private static final String ARG_TITLE = "title";
54
+ private static final String ARG_MESSAGE = "message";
55
+ private static final String ARG_OK_BUTTON = "okButton";
56
+ private static final String ARG_CANCEL_BUTTON = "cancelButton";
57
+ private static final String ARG_INPUT_PLACEHOLDER = "inputPlaceholder";
58
+ private static final String ARG_INPUT_TEXT = "inputText";
59
+ private static final String ARG_OPTIONS = "options";
60
+ private static final String ARG_FOCUS_INPUT = "focusInput";
61
+
62
+ private ExtendedDialog.AlertCallback alertCallback;
63
+ private ExtendedDialog.ConfirmCallback confirmCallback;
64
+ private ExtendedDialog.PromptCallback promptCallback;
65
+ private ExtendedDialog.SingleSelectCallback singleSelectCallback;
66
+ private ExtendedDialog.MultiSelectCallback multiSelectCallback;
67
+
68
+ private TextInputEditText inputField;
69
+ private MaterialButton okBtn;
70
+ private String selectedValue;
71
+ private Set<String> selectedValues = new HashSet<>();
72
+ private boolean dismissed = false;
73
+ private Context themedContext;
74
+
75
+ /**
76
+ * Returns a context wrapped with Material3 theme.
77
+ * This ensures Material components work regardless of the app's base theme.
78
+ */
79
+ private Context getThemedContext() {
80
+ if (themedContext == null) {
81
+ themedContext = new ContextThemeWrapper(requireContext(), com.google.android.material.R.style.Theme_Material3_DayNight_Dialog);
82
+ }
83
+ return themedContext;
84
+ }
85
+
86
+ public static FullScreenDialogFragment newInstance(
87
+ DialogType type,
88
+ String title,
89
+ String message,
90
+ String okButton,
91
+ String cancelButton,
92
+ String inputPlaceholder,
93
+ String inputText,
94
+ String optionsJson,
95
+ boolean focusInput,
96
+ DialogStyleOptions styleOptions
97
+ ) {
98
+ FullScreenDialogFragment fragment = new FullScreenDialogFragment();
99
+ Bundle args = new Bundle();
100
+ args.putString(ARG_TYPE, type.name());
101
+ args.putString(ARG_TITLE, title);
102
+ args.putString(ARG_MESSAGE, message);
103
+ args.putString(ARG_OK_BUTTON, okButton);
104
+ args.putString(ARG_CANCEL_BUTTON, cancelButton);
105
+ args.putString(ARG_INPUT_PLACEHOLDER, inputPlaceholder);
106
+ args.putString(ARG_INPUT_TEXT, inputText);
107
+ args.putString(ARG_OPTIONS, optionsJson);
108
+ args.putBoolean(ARG_FOCUS_INPUT, focusInput);
109
+ if (styleOptions != null) {
110
+ styleOptions.writeToBundle(args);
111
+ }
112
+ fragment.setArguments(args);
113
+ return fragment;
114
+ }
115
+
116
+ public void setAlertCallback(ExtendedDialog.AlertCallback callback) {
117
+ this.alertCallback = callback;
118
+ }
119
+
120
+ public void setConfirmCallback(ExtendedDialog.ConfirmCallback callback) {
121
+ this.confirmCallback = callback;
122
+ }
123
+
124
+ public void setPromptCallback(ExtendedDialog.PromptCallback callback) {
125
+ this.promptCallback = callback;
126
+ }
127
+
128
+ public void setSingleSelectCallback(ExtendedDialog.SingleSelectCallback callback) {
129
+ this.singleSelectCallback = callback;
130
+ }
131
+
132
+ public void setMultiSelectCallback(ExtendedDialog.MultiSelectCallback callback) {
133
+ this.multiSelectCallback = callback;
134
+ }
135
+
136
+ @Override
137
+ public void onCreate(@Nullable Bundle savedInstanceState) {
138
+ super.onCreate(savedInstanceState);
139
+ setStyle(DialogFragment.STYLE_NORMAL, R.style.Theme_ExtendedDialog_FullScreen);
140
+ }
141
+
142
+ @NonNull
143
+ @Override
144
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
145
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
146
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
147
+ return dialog;
148
+ }
149
+
150
+ @Override
151
+ public void onStart() {
152
+ super.onStart();
153
+ Dialog dialog = getDialog();
154
+ if (dialog != null && dialog.getWindow() != null) {
155
+ Window window = dialog.getWindow();
156
+ // Set window to fill entire screen
157
+ window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
158
+ window.setWindowAnimations(android.R.style.Animation_Dialog);
159
+ // Enable edge-to-edge display
160
+ window.setDecorFitsSystemWindows(false);
161
+ window.setStatusBarColor(Color.TRANSPARENT);
162
+ window.setNavigationBarColor(Color.TRANSPARENT);
163
+ // Remove any background dim that might cause gaps
164
+ window.setBackgroundDrawableResource(android.R.color.transparent);
165
+ }
166
+ }
167
+
168
+ @Nullable
169
+ @Override
170
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
171
+ Bundle args = getArguments();
172
+ if (args == null) {
173
+ dismiss();
174
+ return null;
175
+ }
176
+
177
+ DialogType type = DialogType.valueOf(args.getString(ARG_TYPE, DialogType.ALERT.name()));
178
+ String title = args.getString(ARG_TITLE, "");
179
+ String message = args.getString(ARG_MESSAGE, "");
180
+ String okButton = args.getString(ARG_OK_BUTTON, "OK");
181
+ String cancelButton = args.getString(ARG_CANCEL_BUTTON, "Cancel");
182
+ String inputPlaceholder = args.getString(ARG_INPUT_PLACEHOLDER);
183
+ String inputText = args.getString(ARG_INPUT_TEXT);
184
+ String optionsJson = args.getString(ARG_OPTIONS);
185
+ boolean focusInput = args.getBoolean(ARG_FOCUS_INPUT, false);
186
+ DialogStyleOptions styleOptions = DialogStyleOptions.readFromBundle(args);
187
+
188
+ // Create root layout
189
+ Context ctx = getThemedContext();
190
+ LinearLayout root = new LinearLayout(ctx);
191
+ root.setOrientation(LinearLayout.VERTICAL);
192
+ if (styleOptions.getBackgroundColor() != null) {
193
+ root.setBackgroundColor(styleOptions.getBackgroundColor());
194
+ } else {
195
+ // Use Material 3 surface color or fallback to white
196
+ TypedValue surfaceColor = new TypedValue();
197
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorSurface, surfaceColor, true)) {
198
+ root.setBackgroundColor(surfaceColor.data);
199
+ } else {
200
+ root.setBackgroundColor(getResources().getColor(android.R.color.white, null));
201
+ }
202
+ }
203
+
204
+ // Handle window insets for edge-to-edge display
205
+ ViewCompat.setOnApplyWindowInsetsListener(root, (v, windowInsets) -> {
206
+ Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
207
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
208
+ return WindowInsetsCompat.CONSUMED;
209
+ });
210
+
211
+ // Create toolbar
212
+ MaterialToolbar toolbar = new MaterialToolbar(ctx);
213
+ toolbar.setTitle(title != null && !title.isEmpty() ? title : "");
214
+ if (styleOptions.getTitleColor() != null) {
215
+ toolbar.setTitleTextColor(styleOptions.getTitleColor());
216
+ }
217
+ if (styleOptions.getTitleFontSize() != null) {
218
+ toolbar.setTitleTextAppearance(ctx, com.google.android.material.R.style.TextAppearance_Material3_TitleLarge);
219
+ }
220
+ toolbar.setNavigationIcon(com.google.android.material.R.drawable.ic_m3_chip_close);
221
+
222
+ // Tint navigation icon for proper visibility in dark/light themes
223
+ TypedValue colorOnSurface = new TypedValue();
224
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorOnSurface, colorOnSurface, true)) {
225
+ toolbar.setNavigationIconTint(colorOnSurface.data);
226
+ }
227
+
228
+ toolbar.setNavigationOnClickListener((v) -> {
229
+ handleCancel();
230
+ dismiss();
231
+ });
232
+
233
+ root.addView(toolbar);
234
+
235
+ // Create scroll view for content
236
+ ScrollView scrollView = new ScrollView(ctx);
237
+ scrollView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
238
+
239
+ LinearLayout contentLayout = new LinearLayout(ctx);
240
+ contentLayout.setOrientation(LinearLayout.VERTICAL);
241
+ float density = getResources().getDisplayMetrics().density;
242
+ int horizontalPadding = (int) (32 * density);
243
+ int verticalPadding = (int) (24 * density);
244
+ contentLayout.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
245
+
246
+ // Add message
247
+ if (message != null && !message.isEmpty()) {
248
+ TextView messageView = new TextView(ctx);
249
+ messageView.setText(message);
250
+ if (styleOptions.getMessageFontSize() != null) {
251
+ messageView.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getMessageFontSize());
252
+ } else {
253
+ // M3 dialog supporting text: 14sp, weight 400, tracking 0.25sp
254
+ TextViewCompat.setTextAppearance(messageView,
255
+ com.google.android.material.R.style.TextAppearance_Material3_BodyMedium);
256
+ }
257
+ if (styleOptions.getMessageColor() != null) {
258
+ messageView.setTextColor(styleOptions.getMessageColor());
259
+ } else {
260
+ // M3 dialog supporting text color: onSurfaceVariant (#49454F)
261
+ TypedValue onSurfaceVariant = new TypedValue();
262
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorOnSurfaceVariant, onSurfaceVariant, true)) {
263
+ messageView.setTextColor(onSurfaceVariant.data);
264
+ }
265
+ }
266
+ LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams(
267
+ ViewGroup.LayoutParams.MATCH_PARENT,
268
+ ViewGroup.LayoutParams.WRAP_CONTENT
269
+ );
270
+ messageParams.bottomMargin = (int) (32 * density);
271
+ messageView.setLayoutParams(messageParams);
272
+ contentLayout.addView(messageView);
273
+ }
274
+
275
+ // Add type-specific content
276
+ switch (type) {
277
+ case PROMPT:
278
+ addPromptContent(contentLayout, inputPlaceholder, inputText, focusInput);
279
+ break;
280
+ case SINGLE_SELECT:
281
+ addSingleSelectContent(contentLayout, optionsJson, inputText);
282
+ break;
283
+ case MULTI_SELECT:
284
+ addMultiSelectContent(contentLayout, optionsJson, inputText);
285
+ break;
286
+ }
287
+
288
+ scrollView.addView(contentLayout);
289
+ root.addView(scrollView);
290
+
291
+ // Create button container
292
+ LinearLayout buttonContainer = new LinearLayout(ctx);
293
+ buttonContainer.setOrientation(LinearLayout.HORIZONTAL);
294
+ buttonContainer.setPadding(horizontalPadding, verticalPadding / 2, horizontalPadding, verticalPadding);
295
+ buttonContainer.setGravity(android.view.Gravity.END);
296
+
297
+ // Get M3 primary color for text buttons
298
+ TypedValue primaryColor = new TypedValue();
299
+ ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorPrimary, primaryColor, true);
300
+ int primaryColorValue = primaryColor.data;
301
+
302
+ if (type != DialogType.ALERT) {
303
+ // Cancel button - M3 text button style (never disabled per M3 guidelines)
304
+ MaterialButton cancelBtn = new MaterialButton(ctx, null,
305
+ com.google.android.material.R.attr.borderlessButtonStyle);
306
+ cancelBtn.setText(cancelButton);
307
+ cancelBtn.setOnClickListener((v) -> {
308
+ handleCancel();
309
+ dismiss();
310
+ });
311
+
312
+ // Apply cancel button color - use custom or M3 primary
313
+ if (styleOptions.getCancelButtonColor() != null) {
314
+ cancelBtn.setTextColor(ColorStateList.valueOf(styleOptions.getCancelButtonColor()));
315
+ } else {
316
+ cancelBtn.setTextColor(ColorStateList.valueOf(primaryColorValue));
317
+ }
318
+ if (styleOptions.getButtonFontSize() != null) {
319
+ cancelBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getButtonFontSize());
320
+ }
321
+
322
+ LinearLayout.LayoutParams cancelParams = new LinearLayout.LayoutParams(
323
+ ViewGroup.LayoutParams.WRAP_CONTENT,
324
+ ViewGroup.LayoutParams.WRAP_CONTENT
325
+ );
326
+ cancelParams.rightMargin = (int) (8 * getResources().getDisplayMetrics().density);
327
+ cancelBtn.setLayoutParams(cancelParams);
328
+ buttonContainer.addView(cancelBtn);
329
+ }
330
+
331
+ // OK button - M3 text button style (same as Cancel per M3 guidelines)
332
+ okBtn = new MaterialButton(ctx, null,
333
+ com.google.android.material.R.attr.borderlessButtonStyle);
334
+ okBtn.setText(okButton);
335
+ okBtn.setOnClickListener((v) -> {
336
+ handleConfirm(type);
337
+ dismiss();
338
+ });
339
+
340
+ // Apply OK button color - use custom or M3 primary
341
+ if (styleOptions.getButtonColor() != null) {
342
+ okBtn.setTextColor(ColorStateList.valueOf(styleOptions.getButtonColor()));
343
+ } else {
344
+ okBtn.setTextColor(ColorStateList.valueOf(primaryColorValue));
345
+ }
346
+ if (styleOptions.getButtonFontSize() != null) {
347
+ okBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getButtonFontSize());
348
+ }
349
+
350
+ // M3 guideline: Disable confirming action until a choice is made for selection dialogs
351
+ if (type == DialogType.SINGLE_SELECT && selectedValue == null) {
352
+ okBtn.setEnabled(false);
353
+ } else if (type == DialogType.MULTI_SELECT && selectedValues.isEmpty()) {
354
+ okBtn.setEnabled(false);
355
+ }
356
+
357
+ buttonContainer.addView(okBtn);
358
+
359
+ root.addView(buttonContainer);
360
+
361
+ return root;
362
+ }
363
+
364
+ private void addPromptContent(LinearLayout container, String placeholder, String text, boolean focusInput) {
365
+ Context ctx = getThemedContext();
366
+
367
+ // Use Material 3 TextInputLayout with outlined style
368
+ TextInputLayout textInputLayout = new TextInputLayout(ctx, null,
369
+ com.google.android.material.R.attr.textInputOutlinedStyle);
370
+ textInputLayout.setBoxBackgroundMode(TextInputLayout.BOX_BACKGROUND_OUTLINE);
371
+ textInputLayout.setLayoutParams(new LinearLayout.LayoutParams(
372
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
373
+
374
+ inputField = new TextInputEditText(textInputLayout.getContext());
375
+ inputField.setInputType(InputType.TYPE_CLASS_TEXT);
376
+ if (placeholder != null) {
377
+ textInputLayout.setHint(placeholder);
378
+ }
379
+ if (text != null) {
380
+ inputField.setText(text);
381
+ inputField.setSelection(text.length());
382
+ }
383
+ textInputLayout.addView(inputField);
384
+ container.addView(textInputLayout);
385
+
386
+ if (focusInput) {
387
+ inputField.requestFocus();
388
+ // Show keyboard when dialog is shown
389
+ inputField.postDelayed(() -> {
390
+ if (getContext() != null) {
391
+ android.view.inputmethod.InputMethodManager imm =
392
+ (android.view.inputmethod.InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
393
+ if (imm != null) {
394
+ imm.showSoftInput(inputField, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
395
+ }
396
+ }
397
+ }, 200);
398
+ }
399
+ }
400
+
401
+ private void addSingleSelectContent(LinearLayout container, String optionsJson, String selectedValueArg) {
402
+ if (optionsJson == null) return;
403
+
404
+ selectedValue = selectedValueArg;
405
+ Context ctx = getThemedContext();
406
+ float density = getResources().getDisplayMetrics().density;
407
+ int itemPadding = (int) (20 * density);
408
+
409
+ try {
410
+ JSONArray options = new JSONArray(optionsJson);
411
+ RadioGroup radioGroup = new RadioGroup(ctx);
412
+ radioGroup.setPadding(0, itemPadding / 2, 0, 0);
413
+
414
+ for (int i = 0; i < options.length(); i++) {
415
+ JSONObject option = options.getJSONObject(i);
416
+ String label = option.getString("label");
417
+ String value = option.getString("value");
418
+
419
+ RadioButton radioButton = new RadioButton(ctx);
420
+ radioButton.setText(label);
421
+ TextViewCompat.setTextAppearance(radioButton,
422
+ com.google.android.material.R.style.TextAppearance_Material3_BodyLarge);
423
+ // M3 list item text color: onSurface (#1D1B20)
424
+ TypedValue onSurface = new TypedValue();
425
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorOnSurface, onSurface, true)) {
426
+ radioButton.setTextColor(onSurface.data);
427
+ }
428
+ radioButton.setId(View.generateViewId());
429
+ radioButton.setPadding((int) (8 * density), itemPadding, 0, itemPadding);
430
+
431
+ if (value.equals(selectedValue)) {
432
+ radioButton.setChecked(true);
433
+ }
434
+
435
+ final String finalValue = value;
436
+ radioButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
437
+ if (isChecked) {
438
+ selectedValue = finalValue;
439
+ // M3 guideline: Enable confirming action when a choice is made
440
+ if (okBtn != null) {
441
+ okBtn.setEnabled(true);
442
+ }
443
+ }
444
+ });
445
+
446
+ radioGroup.addView(radioButton);
447
+ }
448
+
449
+ container.addView(radioGroup);
450
+ } catch (JSONException e) {
451
+ // Handle error
452
+ }
453
+ }
454
+
455
+ private void addMultiSelectContent(LinearLayout container, String optionsJson, String selectedValuesJson) {
456
+ if (optionsJson == null) return;
457
+
458
+ // Parse selected values
459
+ if (selectedValuesJson != null) {
460
+ try {
461
+ JSONArray selected = new JSONArray(selectedValuesJson);
462
+ for (int i = 0; i < selected.length(); i++) {
463
+ selectedValues.add(selected.getString(i));
464
+ }
465
+ } catch (JSONException e) {
466
+ // Ignore parsing errors
467
+ }
468
+ }
469
+
470
+ Context ctx = getThemedContext();
471
+ float density = getResources().getDisplayMetrics().density;
472
+ int itemPadding = (int) (20 * density);
473
+
474
+ try {
475
+ JSONArray options = new JSONArray(optionsJson);
476
+
477
+ for (int i = 0; i < options.length(); i++) {
478
+ JSONObject option = options.getJSONObject(i);
479
+ String label = option.getString("label");
480
+ String value = option.getString("value");
481
+
482
+ CheckBox checkBox = new CheckBox(ctx);
483
+ checkBox.setText(label);
484
+ TextViewCompat.setTextAppearance(checkBox,
485
+ com.google.android.material.R.style.TextAppearance_Material3_BodyLarge);
486
+ // M3 list item text color: onSurface (#1D1B20)
487
+ TypedValue onSurface = new TypedValue();
488
+ if (ctx.getTheme().resolveAttribute(com.google.android.material.R.attr.colorOnSurface, onSurface, true)) {
489
+ checkBox.setTextColor(onSurface.data);
490
+ }
491
+ checkBox.setPadding((int) (8 * density), itemPadding, 0, itemPadding);
492
+ checkBox.setChecked(selectedValues.contains(value));
493
+
494
+ final String finalValue = value;
495
+ checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
496
+ if (isChecked) {
497
+ selectedValues.add(finalValue);
498
+ } else {
499
+ selectedValues.remove(finalValue);
500
+ }
501
+ // M3 guideline: Enable confirming action when a choice is made
502
+ if (okBtn != null) {
503
+ okBtn.setEnabled(!selectedValues.isEmpty());
504
+ }
505
+ });
506
+
507
+ container.addView(checkBox);
508
+ }
509
+ } catch (JSONException e) {
510
+ // Handle error
511
+ }
512
+ }
513
+
514
+ private void handleConfirm(DialogType type) {
515
+ dismissed = true;
516
+ switch (type) {
517
+ case ALERT:
518
+ if (alertCallback != null) {
519
+ alertCallback.onDismiss();
520
+ }
521
+ break;
522
+ case CONFIRM:
523
+ if (confirmCallback != null) {
524
+ confirmCallback.onResult(true);
525
+ }
526
+ break;
527
+ case PROMPT:
528
+ if (promptCallback != null) {
529
+ String value = inputField != null ? inputField.getText().toString() : "";
530
+ promptCallback.onResult(value, false);
531
+ }
532
+ break;
533
+ case SINGLE_SELECT:
534
+ if (singleSelectCallback != null) {
535
+ singleSelectCallback.onResult(selectedValue, false);
536
+ }
537
+ break;
538
+ case MULTI_SELECT:
539
+ if (multiSelectCallback != null) {
540
+ multiSelectCallback.onResult(selectedValues.toArray(new String[0]), false);
541
+ }
542
+ break;
543
+ }
544
+ }
545
+
546
+ private void handleCancel() {
547
+ if (dismissed) return;
548
+ dismissed = true;
549
+
550
+ Bundle args = getArguments();
551
+ if (args == null) return;
552
+
553
+ DialogType type = DialogType.valueOf(args.getString(ARG_TYPE, DialogType.ALERT.name()));
554
+
555
+ switch (type) {
556
+ case ALERT:
557
+ if (alertCallback != null) {
558
+ alertCallback.onDismiss();
559
+ }
560
+ break;
561
+ case CONFIRM:
562
+ if (confirmCallback != null) {
563
+ confirmCallback.onResult(false);
564
+ }
565
+ break;
566
+ case PROMPT:
567
+ if (promptCallback != null) {
568
+ promptCallback.onResult("", true);
569
+ }
570
+ break;
571
+ case SINGLE_SELECT:
572
+ if (singleSelectCallback != null) {
573
+ singleSelectCallback.onResult(null, true);
574
+ }
575
+ break;
576
+ case MULTI_SELECT:
577
+ if (multiSelectCallback != null) {
578
+ multiSelectCallback.onResult(new String[0], true);
579
+ }
580
+ break;
581
+ }
582
+ }
583
+
584
+ @Override
585
+ public void onCancel(@NonNull DialogInterface dialog) {
586
+ super.onCancel(dialog);
587
+ handleCancel();
588
+ }
589
+ }
File without changes
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <!-- Material 3 Full Screen Dialog Theme -->
4
+ <style name="Theme.ExtendedDialog.FullScreen" parent="Theme.Material3.DayNight.NoActionBar">
5
+ <item name="android:windowIsFloating">false</item>
6
+ <item name="android:windowBackground">@android:color/transparent</item>
7
+ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
8
+ <item name="android:statusBarColor">@android:color/transparent</item>
9
+ <item name="android:navigationBarColor">@android:color/transparent</item>
10
+ <item name="android:windowNoTitle">true</item>
11
+ <item name="android:windowTranslucentStatus">false</item>
12
+ <item name="android:windowTranslucentNavigation">false</item>
13
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
14
+ </style>
15
+
16
+ <!-- Material 3 Dialog Theme Overlay with proper button styles -->
17
+ <style name="ThemeOverlay.ExtendedDialog.MaterialAlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
18
+ <item name="buttonBarPositiveButtonStyle">@style/Widget.Material3.Button.TextButton.Dialog</item>
19
+ <item name="buttonBarNegativeButtonStyle">@style/Widget.Material3.Button.TextButton.Dialog</item>
20
+ <item name="buttonBarNeutralButtonStyle">@style/Widget.Material3.Button.TextButton.Dialog</item>
21
+ <item name="materialAlertDialogBodyTextStyle">@style/MaterialAlertDialog.Material3.Body.Text</item>
22
+ </style>
23
+
24
+ <!-- Material 3 Shape Appearance for dialogs -->
25
+ <style name="ShapeAppearance.ExtendedDialog.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
26
+ <item name="cornerFamily">rounded</item>
27
+ <item name="cornerSize">28dp</item>
28
+ </style>
29
+
30
+ <!-- Material 3 Alert Dialog style with custom shape -->
31
+ <style name="MaterialAlertDialog.ExtendedDialog" parent="MaterialAlertDialog.Material3">
32
+ <item name="shapeAppearance">@style/ShapeAppearance.ExtendedDialog.MediumComponent</item>
33
+ </style>
34
+ </resources>