Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions action-sheet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,20 @@ to select.

#### ShowActionsResult

| Prop | Type | Description | Since |
| ----------- | ------------------- | -------------------------------------------- | ----- |
| **`index`** | <code>number</code> | The index of the clicked option (Zero-based) | 1.0.0 |
| Prop | Type | Description | Since |
| -------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| **`index`** | <code>number</code> | The index of the clicked option (Zero-based), or -1 if the sheet was canceled. On iOS, if there is a button with <a href="#actionsheetbuttonstyle">ActionSheetButtonStyle.Cancel</a>, and user clicks outside the sheet, the index of the cancel option is returned | 1.0.0 |
| **`canceled`** | <code>boolean</code> | True if sheet was canceled by user; False otherwise On Web, requires having @ionic/pwa-elements version 3.4.0 or higher. | 8.1.0 |


#### ShowActionsOptions

| Prop | Type | Description | Since |
| ------------- | -------------------------------- | ------------------------------------------------------------------------ | ----- |
| **`title`** | <code>string</code> | The title of the Action Sheet. | 1.0.0 |
| **`message`** | <code>string</code> | A message to show under the title. This option is only supported on iOS. | 1.0.0 |
| **`options`** | <code>ActionSheetButton[]</code> | Options the user can choose from. | 1.0.0 |
| Prop | Type | Description | Since |
| ---------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
| **`title`** | <code>string</code> | The title of the Action Sheet. | 1.0.0 |
| **`message`** | <code>string</code> | A message to show under the title. This option is only supported on iOS. | 1.0.0 |
| **`options`** | <code>ActionSheetButton[]</code> | Options the user can choose from. | 1.0.0 |
| **`cancelable`** | <code>boolean</code> | If true, sheet is canceled when clicked outside; If false, it is not. By default, false. Not available on iOS, sheet is always cancelable by clicking outside of it. On Web, requires having @ionic/pwa-elements version 3.4.0 or higher. | 8.1.0 |


#### ActionSheetButton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public interface OnCancelListener {
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
this.cancelListener.onCancel();
if (this.cancelListener != null) {
this.cancelListener.onCancel();
}
}

private String title;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class ActionSheetPlugin extends Plugin {
@PluginMethod
public void showActions(final PluginCall call) {
String title = call.getString("title");
boolean cancelable = Boolean.TRUE.equals(call.getBoolean("cancelable", false));
JSArray options = call.getArray("options");
if (options == null) {
call.reject("Must supply options");
Expand All @@ -39,7 +40,10 @@ public void showActions(final PluginCall call) {
}
implementation.setTitle(title);
implementation.setOptions(actionOptions);
implementation.setCancelable(false);
implementation.setCancelable(cancelable);
if (cancelable) {
implementation.setOnCancelListener(() -> resolve(call, -1));
}
implementation.setOnSelectedListener((index) -> {
JSObject ret = new JSObject();
ret.put("index", index);
Expand All @@ -52,4 +56,12 @@ public void showActions(final PluginCall call) {
call.reject("JSON error processing an option for showActions", ex);
}
}

private void resolve(final PluginCall call, int selectedIndex) {
JSObject ret = new JSObject();
ret.put("index", selectedIndex);
ret.put("canceled", selectedIndex < 0);
call.resolve(ret);
implementation.dismiss();
}
}
72 changes: 66 additions & 6 deletions action-sheet/ios/Sources/ActionSheetPlugin/ActionSheetPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,103 @@ import Capacitor
* here: https://capacitorjs.com/docs/plugins/ios
*/
@objc(ActionSheetPlugin)
public class ActionSheetPlugin: CAPPlugin, CAPBridgedPlugin {
public class ActionSheetPlugin: CAPPlugin, CAPBridgedPlugin, UIAdaptivePresentationControllerDelegate {
public let identifier = "ActionSheetPlugin"
public let jsName = "ActionSheet"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "showActions", returnType: CAPPluginReturnPromise)
]
private let implementation = ActionSheet()
private var currentCall: CAPPluginCall?

@objc func showActions(_ call: CAPPluginCall) {
let title = call.options["title"] as? String
let message = call.options["message"] as? String

let options = call.getArray("options", JSObject.self) ?? []
var alertActions = [UIAlertAction]()
var hasCancellableButton = false
for (index, option) in options.enumerated() {
let style = option["style"] as? String ?? "DEFAULT"
let title = option["title"] as? String ?? ""
var buttonStyle: UIAlertAction.Style = .default
if style == "DESTRUCTIVE" {
buttonStyle = .destructive
} else if style == "CANCEL" {
hasCancellableButton = true
buttonStyle = .cancel
}
let action = UIAlertAction(title: title, style: buttonStyle, handler: { (_) in
call.resolve([
"index": index
])
let action = UIAlertAction(title: title, style: buttonStyle, handler: { [weak self] (_) in
if buttonStyle == .cancel {
call.actionSheetCanceled()
} else {
call.resolve([
"index": index,
"canceled": false
])
}
self?.currentCall = nil
})
alertActions.append(action)
}

DispatchQueue.main.async { [weak self] in
if let alertController = self?.implementation.buildActionSheet(title: title, message: message, actions: alertActions) {
self?.setCenteredPopover(alertController)
self?.bridge?.viewController?.present(alertController, animated: true, completion: nil)
self?.bridge?.viewController?.present(alertController, animated: true) {
if !hasCancellableButton {
self?.setupCancelationListerners(alertController, call)
}
}
}
}
}

private func setupCancelationListerners(_ alertController: UIAlertController, _ call: CAPPluginCall) {
if #available(iOS 26, *) {
self.currentCall = call
alertController.presentationController?.delegate = self
} else {
// For iOS versions below 26, setting the presentation controller delegate would result in a crash
// "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The presentation controller of an alert controller presenting as an alert must not have its delegate modified"
// Hence, the alternative by adding a gesture recognizer (which only works for iOS versions below 26)
let gestureRecognizer = TapGestureRecognizerWithClosure {
alertController.dismiss(animated: true, completion: nil)
call.actionSheetCanceled()
}
let backroundView = alertController.view.superview?.subviews[0]
backroundView?.addGestureRecognizer(gestureRecognizer)
}
}

// MARK: - UIAdaptivePresentationControllerDelegate

public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.currentCall?.actionSheetCanceled()
self.currentCall = nil
}
}

// MARK: - TapGestureRecognizerWithClosure
private final class TapGestureRecognizerWithClosure: UITapGestureRecognizer {
private let onTap: () -> Void

init(onTap: @escaping () -> Void) {
self.onTap = onTap
super.init(target: nil, action: nil)
self.addTarget(self, action: #selector(action))
}

@objc private func action() {
onTap()
}
}

private extension CAPPluginCall {
func actionSheetCanceled() {
resolve([
"index": -1,
"canceled": true
])
}
}
23 changes: 22 additions & 1 deletion action-sheet/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ export interface ShowActionsOptions {
* @since 1.0.0
*/
options: ActionSheetButton[];

/**
* If true, sheet is canceled when clicked outside; If false, it is not. By default, false.
*
* Not available on iOS, sheet is always cancelable by clicking outside of it.
*
* On Web, requires having @ionic/pwa-elements version 3.4.0 or higher.
*
* @since 8.1.0
*/
cancelable?: boolean;
}

export enum ActionSheetButtonStyle {
Expand Down Expand Up @@ -76,11 +87,21 @@ export interface ActionSheetButton {

export interface ShowActionsResult {
/**
* The index of the clicked option (Zero-based)
* The index of the clicked option (Zero-based), or -1 if the sheet was canceled.
*
* On iOS, if there is a button with ActionSheetButtonStyle.Cancel, and user clicks outside the sheet, the index of the cancel option is returned
*
* @since 1.0.0
*/
index: number;
/**
* True if sheet was canceled by user; False otherwise
*
* On Web, requires having @ionic/pwa-elements version 3.4.0 or higher.
*
* @since 8.1.0
*/
canceled: boolean;
}

export interface ActionSheetPlugin {
Expand Down
11 changes: 10 additions & 1 deletion action-sheet/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@

export class ActionSheetWeb extends WebPlugin implements ActionSheetPlugin {
async showActions(options: ShowActionsOptions): Promise<ShowActionsResult> {
return new Promise<ShowActionsResult>((resolve, _reject) => {

Check warning on line 7 in action-sheet/src/web.ts

View workflow job for this annotation

GitHub Actions / lint (action-sheet)

'_reject' is defined but never used
let actionSheet: any = document.querySelector('pwa-action-sheet');
if (!actionSheet) {
actionSheet = document.createElement('pwa-action-sheet');
document.body.appendChild(actionSheet);
}
actionSheet.header = options.title;
actionSheet.cancelable = false;
actionSheet.cancelable = options.cancelable;
actionSheet.options = options.options;
actionSheet.addEventListener('onSelection', async (e: any) => {
const selection = e.detail;
resolve({
index: selection,
canceled: false,
});
});
if (options.cancelable) {
actionSheet.addEventListener('onCanceled', async () => {
resolve({
index: -1,
canceled: true,
});
});
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,8 @@ private Uri getTempImage(Uri u, ByteArrayOutputStream bitmapOutputStream) {
try {
bis = new ByteArrayInputStream(bitmapOutputStream.toByteArray());
newUri = saveImage(u, bis);
} catch (IOException ex) {} finally {
} catch (IOException ex) {
} finally {
if (bis != null) {
try {
bis.close();
Expand Down
Loading