capacitor-extended-dialog 0.0.2 → 0.0.4

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.
@@ -0,0 +1,430 @@
1
+ package com.albermonte.extendeddialog;
2
+
3
+ import android.content.DialogInterface;
4
+ import android.content.Context;
5
+ import android.content.res.ColorStateList;
6
+ import android.graphics.Bitmap;
7
+ import android.graphics.BitmapFactory;
8
+ import android.os.Bundle;
9
+ import android.util.Base64;
10
+ import android.util.TypedValue;
11
+ import android.view.ContextThemeWrapper;
12
+ import android.view.Gravity;
13
+ import android.view.LayoutInflater;
14
+ import android.view.View;
15
+ import android.view.ViewGroup;
16
+ import android.widget.FrameLayout;
17
+ import android.widget.ImageView;
18
+ import android.widget.LinearLayout;
19
+ import android.widget.ScrollView;
20
+ import android.widget.TextView;
21
+ import android.app.Dialog;
22
+ import androidx.annotation.NonNull;
23
+ import androidx.annotation.Nullable;
24
+ import androidx.core.widget.TextViewCompat;
25
+ import com.google.android.material.bottomsheet.BottomSheetBehavior;
26
+ import com.google.android.material.bottomsheet.BottomSheetDialog;
27
+ import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
28
+ import com.google.android.material.button.MaterialButton;
29
+ import com.google.android.material.color.MaterialColors;
30
+ import com.google.android.material.shape.MaterialShapeDrawable;
31
+ import com.google.android.material.shape.ShapeAppearanceModel;
32
+ import java.io.InputStream;
33
+ import java.net.URL;
34
+ import java.util.concurrent.Executor;
35
+ import java.util.concurrent.Executors;
36
+ import org.json.JSONArray;
37
+ import org.json.JSONException;
38
+ import org.json.JSONObject;
39
+
40
+ public class SheetBottomDialogFragment extends BottomSheetDialogFragment {
41
+
42
+ private static final String ARG_TITLE = "title";
43
+ private static final String ARG_HEADER_LOGO = "headerLogo";
44
+ private static final String ARG_ROWS = "rows";
45
+ private static final String ARG_CONFIRM_BUTTON = "confirmButton";
46
+ private static final String ARG_CANCEL_BUTTON = "cancelButton";
47
+ private static final String ARG_FULLSCREEN = "fullscreen";
48
+
49
+ private ExtendedDialog.SheetCallback sheetCallback;
50
+ private boolean dismissed = false;
51
+ private Context themedContext;
52
+
53
+ public static SheetBottomDialogFragment newInstance(
54
+ String title,
55
+ String headerLogo,
56
+ String rowsJson,
57
+ String confirmButton,
58
+ String cancelButton,
59
+ boolean fullscreen,
60
+ DialogStyleOptions styleOptions
61
+ ) {
62
+ SheetBottomDialogFragment fragment = new SheetBottomDialogFragment();
63
+ Bundle args = new Bundle();
64
+ args.putString(ARG_TITLE, title);
65
+ args.putString(ARG_HEADER_LOGO, headerLogo);
66
+ args.putString(ARG_ROWS, rowsJson);
67
+ args.putString(ARG_CONFIRM_BUTTON, confirmButton);
68
+ args.putString(ARG_CANCEL_BUTTON, cancelButton);
69
+ args.putBoolean(ARG_FULLSCREEN, fullscreen);
70
+ if (styleOptions != null) {
71
+ styleOptions.writeToBundle(args);
72
+ }
73
+ fragment.setArguments(args);
74
+ return fragment;
75
+ }
76
+
77
+ public void setSheetCallback(ExtendedDialog.SheetCallback callback) {
78
+ this.sheetCallback = callback;
79
+ }
80
+
81
+ @NonNull
82
+ @Override
83
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
84
+ Context m3Context = getThemedContext();
85
+ return new BottomSheetDialog(m3Context, R.style.ThemeOverlay_ExtendedDialog_BottomSheetDialog);
86
+ }
87
+
88
+ private Context getThemedContext() {
89
+ if (themedContext == null) {
90
+ themedContext = new ContextThemeWrapper(requireContext(), com.google.android.material.R.style.Theme_Material3_DayNight_Dialog);
91
+ }
92
+ return themedContext;
93
+ }
94
+
95
+ @Nullable
96
+ @Override
97
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
98
+ Bundle args = getArguments();
99
+ if (args == null) {
100
+ dismiss();
101
+ return null;
102
+ }
103
+
104
+ String title = args.getString(ARG_TITLE, "");
105
+ String headerLogo = args.getString(ARG_HEADER_LOGO);
106
+ String rowsJson = args.getString(ARG_ROWS);
107
+ String confirmButton = args.getString(ARG_CONFIRM_BUTTON, "Confirm");
108
+ String cancelButton = args.getString(ARG_CANCEL_BUTTON, "Cancel");
109
+ boolean fullscreen = args.getBoolean(ARG_FULLSCREEN, false);
110
+ DialogStyleOptions styleOptions = DialogStyleOptions.readFromBundle(args);
111
+
112
+ Context ctx = getThemedContext();
113
+ float density = getResources().getDisplayMetrics().density;
114
+
115
+ // Root layout
116
+ LinearLayout root = new LinearLayout(ctx);
117
+ root.setOrientation(LinearLayout.VERTICAL);
118
+
119
+ // Apply background color
120
+ if (styleOptions.getBackgroundColor() != null) {
121
+ // Use MaterialShapeDrawable to preserve rounded corners
122
+ ShapeAppearanceModel shapeModel = ShapeAppearanceModel.builder()
123
+ .setTopLeftCornerSize(28 * density)
124
+ .setTopRightCornerSize(28 * density)
125
+ .setBottomLeftCornerSize(0)
126
+ .setBottomRightCornerSize(0)
127
+ .build();
128
+ MaterialShapeDrawable shapeDrawable = new MaterialShapeDrawable(shapeModel);
129
+ shapeDrawable.setFillColor(ColorStateList.valueOf(styleOptions.getBackgroundColor()));
130
+ root.setBackground(shapeDrawable);
131
+ }
132
+
133
+ // Drag handle (only shown in basic/non-fullscreen mode)
134
+ if (!fullscreen) {
135
+ View dragHandle = new View(ctx);
136
+ int handleWidth = (int) (32 * density);
137
+ int handleHeight = (int) (4 * density);
138
+ LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(handleWidth, handleHeight);
139
+ handleParams.gravity = Gravity.CENTER_HORIZONTAL;
140
+ handleParams.topMargin = (int) (16 * density);
141
+ handleParams.bottomMargin = (int) (8 * density);
142
+ dragHandle.setLayoutParams(handleParams);
143
+ int outlineVariantColor = MaterialColors.getColor(ctx, com.google.android.material.R.attr.colorOutlineVariant, 0xFFCAC4D0);
144
+ android.graphics.drawable.GradientDrawable handleDrawable = new android.graphics.drawable.GradientDrawable();
145
+ handleDrawable.setShape(android.graphics.drawable.GradientDrawable.RECTANGLE);
146
+ handleDrawable.setCornerRadius(handleHeight / 2f);
147
+ handleDrawable.setColor(outlineVariantColor);
148
+ dragHandle.setBackground(handleDrawable);
149
+ root.addView(dragHandle);
150
+ }
151
+
152
+ // ScrollView for content
153
+ ScrollView scrollView = new ScrollView(ctx);
154
+ scrollView.setLayoutParams(new LinearLayout.LayoutParams(
155
+ ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
156
+
157
+ LinearLayout contentLayout = new LinearLayout(ctx);
158
+ contentLayout.setOrientation(LinearLayout.VERTICAL);
159
+ int horizontalPadding = (int) (24 * density);
160
+ int verticalPadding = (int) (16 * density);
161
+ contentLayout.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
162
+
163
+ // Header logo
164
+ if (headerLogo != null && !headerLogo.isEmpty()) {
165
+ ImageView logoView = new ImageView(ctx);
166
+ int logoSize = fullscreen ? (int) (64 * density) : (int) (48 * density);
167
+ LinearLayout.LayoutParams logoParams = new LinearLayout.LayoutParams(logoSize, logoSize);
168
+ logoParams.gravity = Gravity.CENTER_HORIZONTAL;
169
+ logoParams.bottomMargin = (int) (12 * density);
170
+ logoView.setLayoutParams(logoParams);
171
+ contentLayout.addView(logoView);
172
+ loadImageAsync(logoView, headerLogo);
173
+ }
174
+
175
+ // Title
176
+ if (title != null && !title.isEmpty()) {
177
+ TextView titleView = new TextView(ctx);
178
+ titleView.setText(title);
179
+ TextViewCompat.setTextAppearance(titleView,
180
+ com.google.android.material.R.style.TextAppearance_Material3_HeadlineSmall);
181
+ if (styleOptions.getTitleColor() != null) {
182
+ titleView.setTextColor(styleOptions.getTitleColor());
183
+ } else {
184
+ int onSurfaceColor = MaterialColors.getColor(ctx, android.R.attr.textColorPrimary, 0xFF1D1B20);
185
+ titleView.setTextColor(onSurfaceColor);
186
+ }
187
+ if (styleOptions.getTitleFontSize() != null) {
188
+ titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getTitleFontSize());
189
+ }
190
+ titleView.setGravity(Gravity.CENTER);
191
+ LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
192
+ LinearLayout.LayoutParams.MATCH_PARENT,
193
+ LinearLayout.LayoutParams.WRAP_CONTENT
194
+ );
195
+ titleParams.bottomMargin = (int) (20 * density);
196
+ titleView.setLayoutParams(titleParams);
197
+ contentLayout.addView(titleView);
198
+ }
199
+
200
+ // Rows
201
+ if (rowsJson != null) {
202
+ try {
203
+ JSONArray rows = new JSONArray(rowsJson);
204
+ for (int i = 0; i < rows.length(); i++) {
205
+ JSONObject row = rows.getJSONObject(i);
206
+ LinearLayout rowLayout = createSheetRow(ctx, row, styleOptions, density, i < rows.length() - 1);
207
+ contentLayout.addView(rowLayout);
208
+ }
209
+ } catch (JSONException e) {
210
+ // Handle error
211
+ }
212
+ }
213
+
214
+ scrollView.addView(contentLayout);
215
+ root.addView(scrollView);
216
+
217
+ // Button container pinned at bottom
218
+ LinearLayout buttonContainer = new LinearLayout(ctx);
219
+ buttonContainer.setOrientation(LinearLayout.VERTICAL);
220
+ buttonContainer.setPadding(horizontalPadding, (int) (12 * density), horizontalPadding, (int) (4 * density));
221
+
222
+ int primaryColor = MaterialColors.getColor(ctx, android.R.attr.colorPrimary, 0xFF6750A4);
223
+
224
+ // M3 Expressive L-size button: 48dp height, 24dp horizontal padding
225
+ int buttonHeight = (int) (48 * density);
226
+ int buttonHorizontalPadding = (int) (24 * density);
227
+
228
+ // Confirm button (filled style with purple background, white text)
229
+ MaterialButton confirmBtn = new MaterialButton(ctx);
230
+ confirmBtn.setText(confirmButton);
231
+ confirmBtn.setOnClickListener(v -> {
232
+ handleConfirm();
233
+ dismiss();
234
+ });
235
+ if (styleOptions.getButtonColor() != null) {
236
+ confirmBtn.setBackgroundTintList(ColorStateList.valueOf(styleOptions.getButtonColor()));
237
+ } else {
238
+ confirmBtn.setBackgroundTintList(ColorStateList.valueOf(primaryColor));
239
+ }
240
+ confirmBtn.setTextColor(ColorStateList.valueOf(0xFFFFFFFF));
241
+ confirmBtn.setCornerRadius((int) (24 * density));
242
+ confirmBtn.setMinHeight(buttonHeight);
243
+ confirmBtn.setPadding(buttonHorizontalPadding, 0, buttonHorizontalPadding, 0);
244
+ if (styleOptions.getButtonFontSize() != null) {
245
+ confirmBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getButtonFontSize());
246
+ }
247
+ LinearLayout.LayoutParams confirmParams = new LinearLayout.LayoutParams(
248
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
249
+ confirmBtn.setLayoutParams(confirmParams);
250
+ buttonContainer.addView(confirmBtn);
251
+
252
+ // Cancel button
253
+ MaterialButton cancelBtn = new MaterialButton(ctx, null,
254
+ com.google.android.material.R.attr.borderlessButtonStyle);
255
+ cancelBtn.setText(cancelButton);
256
+ cancelBtn.setOnClickListener(v -> {
257
+ handleCancel();
258
+ dismiss();
259
+ });
260
+ if (styleOptions.getCancelButtonColor() != null) {
261
+ cancelBtn.setTextColor(ColorStateList.valueOf(styleOptions.getCancelButtonColor()));
262
+ } else {
263
+ cancelBtn.setTextColor(ColorStateList.valueOf(primaryColor));
264
+ }
265
+ cancelBtn.setMinHeight(buttonHeight);
266
+ cancelBtn.setPadding(buttonHorizontalPadding, 0, buttonHorizontalPadding, 0);
267
+ if (styleOptions.getButtonFontSize() != null) {
268
+ cancelBtn.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getButtonFontSize());
269
+ }
270
+ LinearLayout.LayoutParams cancelParams = new LinearLayout.LayoutParams(
271
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
272
+ cancelBtn.setLayoutParams(cancelParams);
273
+ buttonContainer.addView(cancelBtn);
274
+
275
+ root.addView(buttonContainer);
276
+
277
+ return root;
278
+ }
279
+
280
+ @Override
281
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
282
+ super.onViewCreated(view, savedInstanceState);
283
+
284
+ Bundle args = getArguments();
285
+ boolean fullscreen = args != null && args.getBoolean(ARG_FULLSCREEN, false);
286
+
287
+ // Get the bottom sheet view from the dialog
288
+ BottomSheetDialog dialog = (BottomSheetDialog) getDialog();
289
+ if (dialog == null) return;
290
+
291
+ dialog.setOnShowListener(dialogInterface -> {
292
+ BottomSheetDialog d = (BottomSheetDialog) dialogInterface;
293
+ FrameLayout bottomSheet = d.findViewById(com.google.android.material.R.id.design_bottom_sheet);
294
+ if (bottomSheet == null) return;
295
+
296
+ BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
297
+
298
+ if (fullscreen) {
299
+ // Make the bottom sheet fill the entire screen
300
+ ViewGroup.LayoutParams layoutParams = bottomSheet.getLayoutParams();
301
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
302
+ bottomSheet.setLayoutParams(layoutParams);
303
+ behavior.setFitToContents(false);
304
+ behavior.setSkipCollapsed(true);
305
+ behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
306
+ // Prevent dragging down in fullscreen mode
307
+ behavior.setDraggable(false);
308
+ } else {
309
+ behavior.setFitToContents(true);
310
+ behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
311
+ behavior.setSkipCollapsed(true);
312
+ }
313
+ });
314
+ }
315
+
316
+ private LinearLayout createSheetRow(Context context, JSONObject row, DialogStyleOptions styleOptions, float density, boolean showDivider) throws JSONException {
317
+ LinearLayout rowLayout = new LinearLayout(context);
318
+ rowLayout.setOrientation(LinearLayout.HORIZONTAL);
319
+ rowLayout.setGravity(Gravity.CENTER_VERTICAL);
320
+ LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
321
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
322
+ rowLayout.setLayoutParams(rowParams);
323
+ rowLayout.setPadding(0, (int) (8 * density), 0, (int) (8 * density));
324
+
325
+ // Row logo
326
+ if (row.has("logo") && !row.getString("logo").isEmpty()) {
327
+ ImageView rowLogoView = new ImageView(context);
328
+ int logoSize = (int) (24 * density);
329
+ LinearLayout.LayoutParams logoParams = new LinearLayout.LayoutParams(logoSize, logoSize);
330
+ logoParams.rightMargin = (int) (12 * density);
331
+ rowLogoView.setLayoutParams(logoParams);
332
+ rowLayout.addView(rowLogoView);
333
+ loadImageAsync(rowLogoView, row.getString("logo"));
334
+ }
335
+
336
+ // Row title
337
+ TextView rowTitle = new TextView(context);
338
+ rowTitle.setText(row.getString("title"));
339
+ TextViewCompat.setTextAppearance(rowTitle,
340
+ com.google.android.material.R.style.TextAppearance_Material3_TitleMedium);
341
+ if (styleOptions.getMessageColor() != null) {
342
+ rowTitle.setTextColor(styleOptions.getMessageColor());
343
+ } else {
344
+ int onSurfaceColor = MaterialColors.getColor(context, android.R.attr.textColorPrimary, 0xFF1D1B20);
345
+ rowTitle.setTextColor(onSurfaceColor);
346
+ }
347
+ if (styleOptions.getMessageFontSize() != null) {
348
+ rowTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getMessageFontSize());
349
+ }
350
+ LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
351
+ rowTitle.setLayoutParams(titleParams);
352
+ rowLayout.addView(rowTitle);
353
+
354
+ // Row value
355
+ if (row.has("value") && !row.getString("value").isEmpty()) {
356
+ TextView rowValue = new TextView(context);
357
+ rowValue.setText(row.getString("value"));
358
+ TextViewCompat.setTextAppearance(rowValue,
359
+ com.google.android.material.R.style.TextAppearance_Material3_BodyLarge);
360
+ int onSurfaceVariantColor = MaterialColors.getColor(context, android.R.attr.textColorSecondary, 0xFF49454F);
361
+ rowValue.setTextColor(onSurfaceVariantColor);
362
+ rowValue.setGravity(Gravity.END);
363
+ if (styleOptions.getMessageFontSize() != null) {
364
+ rowValue.setTextSize(TypedValue.COMPLEX_UNIT_SP, styleOptions.getMessageFontSize());
365
+ }
366
+ rowLayout.addView(rowValue);
367
+ }
368
+
369
+ // Wrap in container with optional divider
370
+ LinearLayout container = new LinearLayout(context);
371
+ container.setOrientation(LinearLayout.VERTICAL);
372
+ container.addView(rowLayout);
373
+
374
+ if (showDivider) {
375
+ View divider = new View(context);
376
+ divider.setBackgroundColor(MaterialColors.getColor(context, android.R.attr.listDivider, 0xFFE0E0E0));
377
+ LinearLayout.LayoutParams dividerParams = new LinearLayout.LayoutParams(
378
+ LinearLayout.LayoutParams.MATCH_PARENT, (int) (1 * density));
379
+ divider.setLayoutParams(dividerParams);
380
+ container.addView(divider);
381
+ }
382
+
383
+ return container;
384
+ }
385
+
386
+ private void loadImageAsync(ImageView imageView, String imageUrl) {
387
+ Executor executor = Executors.newSingleThreadExecutor();
388
+ executor.execute(() -> {
389
+ try {
390
+ Bitmap bitmap;
391
+ if (imageUrl.startsWith("data:")) {
392
+ String base64Data = imageUrl.substring(imageUrl.indexOf(",") + 1);
393
+ byte[] decodedBytes = Base64.decode(base64Data, Base64.DEFAULT);
394
+ bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length);
395
+ } else {
396
+ InputStream inputStream = new URL(imageUrl).openStream();
397
+ bitmap = BitmapFactory.decodeStream(inputStream);
398
+ inputStream.close();
399
+ }
400
+ if (bitmap != null) {
401
+ imageView.post(() -> imageView.setImageBitmap(bitmap));
402
+ }
403
+ } catch (Exception e) {
404
+ // Silently fail - image won't be displayed
405
+ }
406
+ });
407
+ }
408
+
409
+ private void handleConfirm() {
410
+ if (dismissed) return;
411
+ dismissed = true;
412
+ if (sheetCallback != null) {
413
+ sheetCallback.onResult(true);
414
+ }
415
+ }
416
+
417
+ private void handleCancel() {
418
+ if (dismissed) return;
419
+ dismissed = true;
420
+ if (sheetCallback != null) {
421
+ sheetCallback.onResult(false);
422
+ }
423
+ }
424
+
425
+ @Override
426
+ public void onCancel(@NonNull DialogInterface dialog) {
427
+ super.onCancel(dialog);
428
+ handleCancel();
429
+ }
430
+ }
@@ -31,4 +31,18 @@
31
31
  <style name="MaterialAlertDialog.ExtendedDialog" parent="MaterialAlertDialog.Material3">
32
32
  <item name="shapeAppearance">@style/ShapeAppearance.ExtendedDialog.MediumComponent</item>
33
33
  </style>
34
+
35
+ <!-- Material 3 Bottom Sheet Dialog Theme -->
36
+ <style name="ThemeOverlay.ExtendedDialog.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
37
+ <item name="bottomSheetStyle">@style/Widget.ExtendedDialog.BottomSheet.Modal</item>
38
+ </style>
39
+
40
+ <style name="Widget.ExtendedDialog.BottomSheet.Modal" parent="Widget.Material3.BottomSheet.Modal">
41
+ <item name="shapeAppearanceOverlay">@style/ShapeAppearance.ExtendedDialog.BottomSheet</item>
42
+ </style>
43
+
44
+ <style name="ShapeAppearance.ExtendedDialog.BottomSheet" parent="">
45
+ <item name="cornerSizeBottomLeft">0dp</item>
46
+ <item name="cornerSizeBottomRight">0dp</item>
47
+ </style>
34
48
  </resources>