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.
- package/CapacitorExtendedDialog.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +190 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/albermonte/extendeddialog/DialogStyleOptions.java +99 -0
- package/android/src/main/java/com/albermonte/extendeddialog/ExtendedDialog.java +734 -0
- package/android/src/main/java/com/albermonte/extendeddialog/ExtendedDialogPlugin.java +207 -0
- package/android/src/main/java/com/albermonte/extendeddialog/FullScreenDialogFragment.java +589 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/values/themes.xml +34 -0
- package/dist/docs.json +384 -0
- package/dist/esm/definitions.d.ts +101 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +14 -0
- package/dist/esm/web.js +193 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +207 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +210 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/ExtendedDialogPlugin/ExtendedDialog.swift +497 -0
- package/ios/Sources/ExtendedDialogPlugin/ExtendedDialogPlugin.swift +177 -0
- package/ios/Sources/ExtendedDialogPlugin/FullScreenDialogViewController.swift +506 -0
- package/ios/Tests/ExtendedDialogPluginTests/ExtendedDialogPluginTests.swift +15 -0
- 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>
|