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,506 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
public enum DialogType {
|
|
4
|
+
case alert
|
|
5
|
+
case confirm
|
|
6
|
+
case prompt
|
|
7
|
+
case singleSelect
|
|
8
|
+
case multiSelect
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public class FullScreenDialogViewController: UIViewController {
|
|
12
|
+
|
|
13
|
+
// MARK: - Callbacks
|
|
14
|
+
|
|
15
|
+
var alertCallback: (() -> Void)?
|
|
16
|
+
var confirmCallback: ((Bool) -> Void)?
|
|
17
|
+
var promptCallback: ((String, Bool) -> Void)?
|
|
18
|
+
var singleSelectCallback: ((String?, Bool) -> Void)?
|
|
19
|
+
var multiSelectCallback: (([String], Bool) -> Void)?
|
|
20
|
+
|
|
21
|
+
// MARK: - Properties
|
|
22
|
+
|
|
23
|
+
private let dialogType: DialogType
|
|
24
|
+
private let dialogTitle: String?
|
|
25
|
+
private let message: String
|
|
26
|
+
private let okButtonTitle: String
|
|
27
|
+
private let cancelButtonTitle: String?
|
|
28
|
+
private let inputPlaceholder: String?
|
|
29
|
+
private let inputText: String?
|
|
30
|
+
private let options: [SelectOption]?
|
|
31
|
+
private var selectedValue: String?
|
|
32
|
+
private var selectedValues: Set<String>
|
|
33
|
+
private let focusInput: Bool
|
|
34
|
+
private let styleOptions: DialogStyleOptions?
|
|
35
|
+
|
|
36
|
+
private var inputTextField: UITextField?
|
|
37
|
+
private var tableView: UITableView?
|
|
38
|
+
private var dismissed = false
|
|
39
|
+
private var buttonStackBottomConstraint: NSLayoutConstraint?
|
|
40
|
+
|
|
41
|
+
// MARK: - UI Components
|
|
42
|
+
|
|
43
|
+
private lazy var headerView: UIView = {
|
|
44
|
+
let header = UIView()
|
|
45
|
+
header.translatesAutoresizingMaskIntoConstraints = false
|
|
46
|
+
return header
|
|
47
|
+
}()
|
|
48
|
+
|
|
49
|
+
private lazy var closeButtonView: UIButton = {
|
|
50
|
+
let button = UIButton(type: .system)
|
|
51
|
+
button.translatesAutoresizingMaskIntoConstraints = false
|
|
52
|
+
let config = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold)
|
|
53
|
+
button.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
|
|
54
|
+
button.tintColor = .label
|
|
55
|
+
button.backgroundColor = .systemBackground
|
|
56
|
+
button.layer.cornerRadius = 22
|
|
57
|
+
button.layer.cornerCurve = .continuous
|
|
58
|
+
button.layer.shadowColor = UIColor.black.cgColor
|
|
59
|
+
button.layer.shadowOpacity = 0.08
|
|
60
|
+
button.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
61
|
+
button.layer.shadowRadius = 4
|
|
62
|
+
button.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
|
63
|
+
return button
|
|
64
|
+
}()
|
|
65
|
+
|
|
66
|
+
private lazy var titleLabel: UILabel = {
|
|
67
|
+
let label = UILabel()
|
|
68
|
+
label.translatesAutoresizingMaskIntoConstraints = false
|
|
69
|
+
label.text = dialogTitle
|
|
70
|
+
let fontSize = styleOptions?.titleFontSize ?? 18
|
|
71
|
+
label.font = .systemFont(ofSize: fontSize, weight: .bold)
|
|
72
|
+
label.textAlignment = .center
|
|
73
|
+
if let titleColor = styleOptions?.titleColor {
|
|
74
|
+
label.textColor = titleColor
|
|
75
|
+
}
|
|
76
|
+
return label
|
|
77
|
+
}()
|
|
78
|
+
|
|
79
|
+
private lazy var messageLabel: UILabel = {
|
|
80
|
+
let label = UILabel()
|
|
81
|
+
label.translatesAutoresizingMaskIntoConstraints = false
|
|
82
|
+
label.text = message
|
|
83
|
+
label.numberOfLines = 0
|
|
84
|
+
let fontSize = styleOptions?.messageFontSize ?? 16
|
|
85
|
+
label.font = .systemFont(ofSize: fontSize)
|
|
86
|
+
label.textColor = styleOptions?.messageColor ?? .secondaryLabel
|
|
87
|
+
return label
|
|
88
|
+
}()
|
|
89
|
+
|
|
90
|
+
private lazy var buttonStack: UIStackView = {
|
|
91
|
+
let stack = UIStackView()
|
|
92
|
+
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
93
|
+
stack.axis = .horizontal
|
|
94
|
+
stack.spacing = 12
|
|
95
|
+
stack.distribution = .fillEqually
|
|
96
|
+
return stack
|
|
97
|
+
}()
|
|
98
|
+
|
|
99
|
+
private lazy var cancelButton: UIButton = {
|
|
100
|
+
let button = UIButton(type: .system)
|
|
101
|
+
button.setTitle(cancelButtonTitle ?? "Cancel", for: .normal)
|
|
102
|
+
let fontSize = styleOptions?.buttonFontSize ?? 17
|
|
103
|
+
button.titleLabel?.font = .systemFont(ofSize: fontSize, weight: .medium)
|
|
104
|
+
button.layer.cornerRadius = 12
|
|
105
|
+
button.layer.cornerCurve = .continuous
|
|
106
|
+
button.layer.borderWidth = 1
|
|
107
|
+
let cancelColor = styleOptions?.cancelButtonColor ?? .systemBlue
|
|
108
|
+
button.setTitleColor(cancelColor, for: .normal)
|
|
109
|
+
button.layer.borderColor = cancelColor.cgColor
|
|
110
|
+
button.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
|
111
|
+
return button
|
|
112
|
+
}()
|
|
113
|
+
|
|
114
|
+
private lazy var okButton: UIButton = {
|
|
115
|
+
let button = UIButton(type: .system)
|
|
116
|
+
button.setTitle(okButtonTitle, for: .normal)
|
|
117
|
+
button.setTitleColor(.white, for: .normal)
|
|
118
|
+
let fontSize = styleOptions?.buttonFontSize ?? 17
|
|
119
|
+
button.titleLabel?.font = .systemFont(ofSize: fontSize, weight: .semibold)
|
|
120
|
+
let buttonColor = styleOptions?.buttonColor ?? .systemBlue
|
|
121
|
+
button.backgroundColor = buttonColor
|
|
122
|
+
button.layer.cornerRadius = 12
|
|
123
|
+
button.layer.cornerCurve = .continuous
|
|
124
|
+
button.addTarget(self, action: #selector(okTapped), for: .touchUpInside)
|
|
125
|
+
return button
|
|
126
|
+
}()
|
|
127
|
+
|
|
128
|
+
private lazy var blurView: UIVisualEffectView = {
|
|
129
|
+
let view: UIVisualEffectView
|
|
130
|
+
if #available(iOS 26, *) {
|
|
131
|
+
let glassEffect = UIGlassEffect()
|
|
132
|
+
view = UIVisualEffectView(effect: glassEffect)
|
|
133
|
+
} else {
|
|
134
|
+
let blurEffect = UIBlurEffect(style: .systemThinMaterial)
|
|
135
|
+
view = UIVisualEffectView(effect: blurEffect)
|
|
136
|
+
}
|
|
137
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
|
138
|
+
return view
|
|
139
|
+
}()
|
|
140
|
+
|
|
141
|
+
// MARK: - Init
|
|
142
|
+
|
|
143
|
+
init(
|
|
144
|
+
dialogType: DialogType,
|
|
145
|
+
dialogTitle: String?,
|
|
146
|
+
message: String,
|
|
147
|
+
okButtonTitle: String,
|
|
148
|
+
cancelButtonTitle: String?,
|
|
149
|
+
inputPlaceholder: String?,
|
|
150
|
+
inputText: String?,
|
|
151
|
+
options: [SelectOption]?,
|
|
152
|
+
selectedValue: String?,
|
|
153
|
+
selectedValues: [String]?,
|
|
154
|
+
focusInput: Bool = false,
|
|
155
|
+
styleOptions: DialogStyleOptions? = nil
|
|
156
|
+
) {
|
|
157
|
+
self.dialogType = dialogType
|
|
158
|
+
self.dialogTitle = dialogTitle
|
|
159
|
+
self.message = message
|
|
160
|
+
self.okButtonTitle = okButtonTitle
|
|
161
|
+
self.cancelButtonTitle = cancelButtonTitle
|
|
162
|
+
self.inputPlaceholder = inputPlaceholder
|
|
163
|
+
self.inputText = inputText
|
|
164
|
+
self.options = options
|
|
165
|
+
self.selectedValue = selectedValue
|
|
166
|
+
self.selectedValues = Set(selectedValues ?? [])
|
|
167
|
+
self.focusInput = focusInput
|
|
168
|
+
self.styleOptions = styleOptions
|
|
169
|
+
super.init(nibName: nil, bundle: nil)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
required init?(coder: NSCoder) {
|
|
173
|
+
fatalError("init(coder:) has not been implemented")
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// MARK: - Lifecycle
|
|
177
|
+
|
|
178
|
+
public override func viewDidLoad() {
|
|
179
|
+
super.viewDidLoad()
|
|
180
|
+
setupUI()
|
|
181
|
+
applyLiquidGlassStyle()
|
|
182
|
+
setupKeyboardHandling()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
deinit {
|
|
186
|
+
NotificationCenter.default.removeObserver(self)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// MARK: - Keyboard Handling
|
|
190
|
+
|
|
191
|
+
private func setupKeyboardHandling() {
|
|
192
|
+
// Add tap gesture to dismiss keyboard
|
|
193
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
|
|
194
|
+
tapGesture.cancelsTouchesInView = false
|
|
195
|
+
view.addGestureRecognizer(tapGesture)
|
|
196
|
+
|
|
197
|
+
// Listen for keyboard notifications
|
|
198
|
+
NotificationCenter.default.addObserver(
|
|
199
|
+
self,
|
|
200
|
+
selector: #selector(keyboardWillShow(_:)),
|
|
201
|
+
name: UIResponder.keyboardWillShowNotification,
|
|
202
|
+
object: nil
|
|
203
|
+
)
|
|
204
|
+
NotificationCenter.default.addObserver(
|
|
205
|
+
self,
|
|
206
|
+
selector: #selector(keyboardWillHide(_:)),
|
|
207
|
+
name: UIResponder.keyboardWillHideNotification,
|
|
208
|
+
object: nil
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@objc private func dismissKeyboard() {
|
|
213
|
+
view.endEditing(true)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@objc private func keyboardWillShow(_ notification: Notification) {
|
|
217
|
+
guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
|
|
218
|
+
let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let keyboardHeight = keyboardFrame.height
|
|
223
|
+
let safeAreaBottom = view.safeAreaInsets.bottom
|
|
224
|
+
|
|
225
|
+
UIView.animate(withDuration: duration) {
|
|
226
|
+
self.buttonStackBottomConstraint?.constant = -(keyboardHeight - safeAreaBottom + 16)
|
|
227
|
+
self.view.layoutIfNeeded()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@objc private func keyboardWillHide(_ notification: Notification) {
|
|
232
|
+
guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
UIView.animate(withDuration: duration) {
|
|
237
|
+
self.buttonStackBottomConstraint?.constant = -24
|
|
238
|
+
self.view.layoutIfNeeded()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// MARK: - Setup
|
|
243
|
+
|
|
244
|
+
private func setupUI() {
|
|
245
|
+
// Apply custom background color or default light gray background like iOS Calendar
|
|
246
|
+
if let bgColor = styleOptions?.backgroundColor {
|
|
247
|
+
view.backgroundColor = bgColor
|
|
248
|
+
} else {
|
|
249
|
+
view.backgroundColor = .systemGroupedBackground
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Add blur background
|
|
253
|
+
view.insertSubview(blurView, at: 0)
|
|
254
|
+
NSLayoutConstraint.activate([
|
|
255
|
+
blurView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
256
|
+
blurView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
257
|
+
blurView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
258
|
+
blurView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
259
|
+
])
|
|
260
|
+
|
|
261
|
+
// Header with close button and title
|
|
262
|
+
view.addSubview(headerView)
|
|
263
|
+
headerView.addSubview(closeButtonView)
|
|
264
|
+
headerView.addSubview(titleLabel)
|
|
265
|
+
|
|
266
|
+
NSLayoutConstraint.activate([
|
|
267
|
+
headerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 12),
|
|
268
|
+
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
269
|
+
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
270
|
+
headerView.heightAnchor.constraint(equalToConstant: 64),
|
|
271
|
+
|
|
272
|
+
closeButtonView.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 16),
|
|
273
|
+
closeButtonView.centerYAnchor.constraint(equalTo: headerView.centerYAnchor),
|
|
274
|
+
closeButtonView.widthAnchor.constraint(equalToConstant: 44),
|
|
275
|
+
closeButtonView.heightAnchor.constraint(equalToConstant: 44),
|
|
276
|
+
|
|
277
|
+
titleLabel.centerXAnchor.constraint(equalTo: headerView.centerXAnchor),
|
|
278
|
+
titleLabel.centerYAnchor.constraint(equalTo: headerView.centerYAnchor)
|
|
279
|
+
])
|
|
280
|
+
|
|
281
|
+
// Content container
|
|
282
|
+
let contentView = UIView()
|
|
283
|
+
contentView.translatesAutoresizingMaskIntoConstraints = false
|
|
284
|
+
view.addSubview(contentView)
|
|
285
|
+
|
|
286
|
+
NSLayoutConstraint.activate([
|
|
287
|
+
contentView.topAnchor.constraint(equalTo: headerView.bottomAnchor, constant: 8),
|
|
288
|
+
contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
|
|
289
|
+
contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24)
|
|
290
|
+
])
|
|
291
|
+
|
|
292
|
+
// Message
|
|
293
|
+
contentView.addSubview(messageLabel)
|
|
294
|
+
NSLayoutConstraint.activate([
|
|
295
|
+
messageLabel.topAnchor.constraint(equalTo: contentView.topAnchor),
|
|
296
|
+
messageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
|
297
|
+
messageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
|
|
298
|
+
])
|
|
299
|
+
|
|
300
|
+
// Type-specific content
|
|
301
|
+
var lastView: UIView = messageLabel
|
|
302
|
+
|
|
303
|
+
switch dialogType {
|
|
304
|
+
case .prompt:
|
|
305
|
+
let textField = createTextField()
|
|
306
|
+
contentView.addSubview(textField)
|
|
307
|
+
NSLayoutConstraint.activate([
|
|
308
|
+
textField.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 24),
|
|
309
|
+
textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
|
310
|
+
textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
|
311
|
+
textField.heightAnchor.constraint(equalToConstant: 48)
|
|
312
|
+
])
|
|
313
|
+
inputTextField = textField
|
|
314
|
+
lastView = textField
|
|
315
|
+
|
|
316
|
+
case .singleSelect, .multiSelect:
|
|
317
|
+
let table = createTableView()
|
|
318
|
+
contentView.addSubview(table)
|
|
319
|
+
|
|
320
|
+
// Calculate preferred height based on number of options (52pt per row)
|
|
321
|
+
let rowCount = options?.count ?? 0
|
|
322
|
+
let preferredHeight = CGFloat(rowCount) * 52
|
|
323
|
+
let preferredHeightConstraint = table.heightAnchor.constraint(equalToConstant: min(preferredHeight, 300))
|
|
324
|
+
preferredHeightConstraint.priority = .defaultHigh
|
|
325
|
+
|
|
326
|
+
NSLayoutConstraint.activate([
|
|
327
|
+
table.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 16),
|
|
328
|
+
table.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
|
329
|
+
table.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
|
330
|
+
table.heightAnchor.constraint(lessThanOrEqualToConstant: 300),
|
|
331
|
+
preferredHeightConstraint
|
|
332
|
+
])
|
|
333
|
+
tableView = table
|
|
334
|
+
lastView = table
|
|
335
|
+
|
|
336
|
+
default:
|
|
337
|
+
break
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Buttons (add before contentView bottom constraint so we can reference buttonStack)
|
|
341
|
+
view.addSubview(buttonStack)
|
|
342
|
+
|
|
343
|
+
if dialogType != .alert && cancelButtonTitle != nil {
|
|
344
|
+
buttonStack.addArrangedSubview(cancelButton)
|
|
345
|
+
}
|
|
346
|
+
buttonStack.addArrangedSubview(okButton)
|
|
347
|
+
|
|
348
|
+
buttonStackBottomConstraint = buttonStack.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -24)
|
|
349
|
+
|
|
350
|
+
NSLayoutConstraint.activate([
|
|
351
|
+
buttonStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24),
|
|
352
|
+
buttonStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24),
|
|
353
|
+
buttonStackBottomConstraint!,
|
|
354
|
+
buttonStack.heightAnchor.constraint(equalToConstant: 50)
|
|
355
|
+
])
|
|
356
|
+
|
|
357
|
+
// Content bottom constraint - ensure content stays above buttons
|
|
358
|
+
NSLayoutConstraint.activate([
|
|
359
|
+
contentView.bottomAnchor.constraint(equalTo: lastView.bottomAnchor),
|
|
360
|
+
contentView.bottomAnchor.constraint(lessThanOrEqualTo: buttonStack.topAnchor, constant: -16)
|
|
361
|
+
])
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private func createTextField() -> UITextField {
|
|
365
|
+
let textField = UITextField()
|
|
366
|
+
textField.translatesAutoresizingMaskIntoConstraints = false
|
|
367
|
+
textField.placeholder = inputPlaceholder
|
|
368
|
+
textField.text = inputText
|
|
369
|
+
textField.font = .systemFont(ofSize: 16)
|
|
370
|
+
textField.borderStyle = .roundedRect
|
|
371
|
+
textField.backgroundColor = .secondarySystemGroupedBackground
|
|
372
|
+
textField.layer.cornerRadius = 8
|
|
373
|
+
|
|
374
|
+
if focusInput {
|
|
375
|
+
textField.becomeFirstResponder()
|
|
376
|
+
}
|
|
377
|
+
return textField
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private func createTableView() -> UITableView {
|
|
381
|
+
let table = UITableView(frame: .zero, style: .plain)
|
|
382
|
+
table.translatesAutoresizingMaskIntoConstraints = false
|
|
383
|
+
table.delegate = self
|
|
384
|
+
table.dataSource = self
|
|
385
|
+
table.register(UITableViewCell.self, forCellReuseIdentifier: "OptionCell")
|
|
386
|
+
table.layer.cornerRadius = 12
|
|
387
|
+
table.layer.cornerCurve = .continuous
|
|
388
|
+
table.clipsToBounds = true
|
|
389
|
+
table.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
|
|
390
|
+
table.backgroundColor = .secondarySystemGroupedBackground
|
|
391
|
+
|
|
392
|
+
return table
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private func applyLiquidGlassStyle() {
|
|
396
|
+
// Apply Liquid Glass styling adjustments for iOS 26+
|
|
397
|
+
// The main glass effect is applied via blurView using UIGlassEffect
|
|
398
|
+
if #available(iOS 26, *) {
|
|
399
|
+
// On iOS 26+, adjust close button to complement the glass background
|
|
400
|
+
closeButtonView.backgroundColor = UIColor.systemBackground.withAlphaComponent(0.3)
|
|
401
|
+
closeButtonView.layer.shadowOpacity = 0 // Glass handles visual depth
|
|
402
|
+
}
|
|
403
|
+
// Note: cornerCurve is set in button lazy vars for all iOS versions
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// MARK: - Actions
|
|
407
|
+
|
|
408
|
+
@objc private func cancelTapped() {
|
|
409
|
+
handleCancel()
|
|
410
|
+
dismiss(animated: true)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
@objc private func okTapped() {
|
|
414
|
+
handleConfirm()
|
|
415
|
+
dismiss(animated: true)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private func handleConfirm() {
|
|
419
|
+
guard !dismissed else { return }
|
|
420
|
+
dismissed = true
|
|
421
|
+
|
|
422
|
+
switch dialogType {
|
|
423
|
+
case .alert:
|
|
424
|
+
alertCallback?()
|
|
425
|
+
case .confirm:
|
|
426
|
+
confirmCallback?(true)
|
|
427
|
+
case .prompt:
|
|
428
|
+
let value = inputTextField?.text ?? ""
|
|
429
|
+
promptCallback?(value, false)
|
|
430
|
+
case .singleSelect:
|
|
431
|
+
singleSelectCallback?(selectedValue, false)
|
|
432
|
+
case .multiSelect:
|
|
433
|
+
multiSelectCallback?(Array(selectedValues), false)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private func handleCancel() {
|
|
438
|
+
guard !dismissed else { return }
|
|
439
|
+
dismissed = true
|
|
440
|
+
|
|
441
|
+
switch dialogType {
|
|
442
|
+
case .alert:
|
|
443
|
+
alertCallback?()
|
|
444
|
+
case .confirm:
|
|
445
|
+
confirmCallback?(false)
|
|
446
|
+
case .prompt:
|
|
447
|
+
promptCallback?("", true)
|
|
448
|
+
case .singleSelect:
|
|
449
|
+
singleSelectCallback?(nil, true)
|
|
450
|
+
case .multiSelect:
|
|
451
|
+
multiSelectCallback?([], true)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// MARK: - UITableViewDelegate & DataSource
|
|
457
|
+
|
|
458
|
+
extension FullScreenDialogViewController: UITableViewDelegate, UITableViewDataSource {
|
|
459
|
+
|
|
460
|
+
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
461
|
+
return options?.count ?? 0
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
465
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "OptionCell", for: indexPath)
|
|
466
|
+
guard let option = options?[indexPath.row] else { return cell }
|
|
467
|
+
|
|
468
|
+
cell.textLabel?.text = option.label
|
|
469
|
+
|
|
470
|
+
switch dialogType {
|
|
471
|
+
case .singleSelect:
|
|
472
|
+
cell.accessoryType = option.value == selectedValue ? .checkmark : .none
|
|
473
|
+
case .multiSelect:
|
|
474
|
+
cell.accessoryType = selectedValues.contains(option.value) ? .checkmark : .none
|
|
475
|
+
default:
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
cell.backgroundColor = .clear
|
|
480
|
+
return cell
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
484
|
+
tableView.deselectRow(at: indexPath, animated: true)
|
|
485
|
+
guard let option = options?[indexPath.row] else { return }
|
|
486
|
+
|
|
487
|
+
switch dialogType {
|
|
488
|
+
case .singleSelect:
|
|
489
|
+
selectedValue = option.value
|
|
490
|
+
tableView.reloadData()
|
|
491
|
+
case .multiSelect:
|
|
492
|
+
if selectedValues.contains(option.value) {
|
|
493
|
+
selectedValues.remove(option.value)
|
|
494
|
+
} else {
|
|
495
|
+
selectedValues.insert(option.value)
|
|
496
|
+
}
|
|
497
|
+
tableView.reloadRows(at: [indexPath], with: .automatic)
|
|
498
|
+
default:
|
|
499
|
+
break
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
|
504
|
+
return 52
|
|
505
|
+
}
|
|
506
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import ExtendedDialogPlugin
|
|
3
|
+
|
|
4
|
+
class ExtendedDialogTests: XCTestCase {
|
|
5
|
+
func testEcho() {
|
|
6
|
+
// This is an example of a functional test case for a plugin.
|
|
7
|
+
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
8
|
+
|
|
9
|
+
let implementation = ExtendedDialog()
|
|
10
|
+
let value = "Hello, World!"
|
|
11
|
+
let result = implementation.echo(value)
|
|
12
|
+
|
|
13
|
+
XCTAssertEqual(value, result)
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-extended-dialog",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Show native dialogs in Capacitor with Material 3 and Liquid Glass UI",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapacitorExtendedDialog.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Albermonte",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Albermonte/capacitor-extended-dialog.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Albermonte/capacitor-extended-dialog/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
34
|
+
"verify:ios": "xcodebuild -scheme CapacitorExtendedDialog -destination generic/platform=iOS",
|
|
35
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
36
|
+
"verify:web": "npm run build",
|
|
37
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
38
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
39
|
+
"eslint": "eslint . --ext ts",
|
|
40
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
41
|
+
"swiftlint": "node-swiftlint",
|
|
42
|
+
"docgen": "docgen --api ExtendedDialogPlugin --output-readme README.md --output-json dist/docs.json",
|
|
43
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
44
|
+
"clean": "rimraf ./dist",
|
|
45
|
+
"watch": "tsc --watch",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@capacitor/android": "^7.0.0",
|
|
50
|
+
"@capacitor/core": "^7.0.0",
|
|
51
|
+
"@capacitor/docgen": "^0.3.0",
|
|
52
|
+
"@capacitor/ios": "^7.0.0",
|
|
53
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
54
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
55
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
56
|
+
"eslint": "^8.57.0",
|
|
57
|
+
"prettier": "^3.4.2",
|
|
58
|
+
"prettier-plugin-java": "^2.6.6",
|
|
59
|
+
"rimraf": "^6.0.1",
|
|
60
|
+
"rollup": "^4.30.1",
|
|
61
|
+
"swiftlint": "^2.0.0",
|
|
62
|
+
"typescript": "~4.1.5"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@capacitor/core": ">=7.0.0"
|
|
66
|
+
},
|
|
67
|
+
"prettier": "@ionic/prettier-config",
|
|
68
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
69
|
+
"eslintConfig": {
|
|
70
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
71
|
+
},
|
|
72
|
+
"capacitor": {
|
|
73
|
+
"ios": {
|
|
74
|
+
"src": "ios"
|
|
75
|
+
},
|
|
76
|
+
"android": {
|
|
77
|
+
"src": "android"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|