fix(ios): prevent share overlay from reappearing after dismissal

Add in-progress flag with iOS-specific delayed clearing to prevent the native share sheet from immediately reappearing after dismissal. On iOS, dismissing the share sheet fires window focus events that can re-trigger the share method. The 500ms delay prevents this re-trigger while remaining imperceptible to users.
This commit is contained in:
Johannes Millan 2026-01-20 16:45:43 +01:00
parent 286e04834e
commit 5a9f52ee62

View file

@ -8,6 +8,7 @@ import {
} from '@angular/core';
import { WorkContextType } from '../../features/work-context/work-context.model';
import { T } from 'src/app/t.const';
import { IS_IOS } from '../../util/is-ios';
import { TODAY_TAG } from '../../features/tag/tag.const';
import { DialogConfirmComponent } from '../../ui/dialog-confirm/dialog-confirm.component';
import { MatDialog } from '@angular/material/dialog';
@ -57,6 +58,7 @@ export class WorkContextMenuComponent implements OnInit {
isForProject: boolean = true;
base: string = 'project';
shareSupport: ShareSupport = 'none';
private _isShareInProgress = false;
// TODO: Skipped for migration because:
// Accessor inputs cannot be migrated as they are too complex.
@ -133,53 +135,73 @@ export class WorkContextMenuComponent implements OnInit {
protected readonly INBOX_PROJECT = INBOX_PROJECT;
async shareTasksAsMarkdown(): Promise<void> {
const { status, markdown, contextTitle } =
await this._markdownService.getMarkdownForContext(
this.contextId,
this.isForProject,
);
if (status === 'empty' || !markdown) {
this._snackService.open(T.GLOBAL_SNACK.NO_TASKS_TO_COPY);
// Guard against concurrent share operations
if (this._isShareInProgress) {
return;
}
const shareResult = await this._shareService.shareText({
title: contextTitle ?? 'Super Productivity',
text: markdown,
});
this._isShareInProgress = true;
if (shareResult === 'shared') {
if (this.shareSupport === 'none') {
const support = await this._shareService.getShareSupport();
this._setShareSupport(support);
try {
const { status, markdown, contextTitle } =
await this._markdownService.getMarkdownForContext(
this.contextId,
this.isForProject,
);
if (status === 'empty' || !markdown) {
this._snackService.open(T.GLOBAL_SNACK.NO_TASKS_TO_COPY);
return;
}
return;
}
if (shareResult === 'cancelled') {
return;
}
const shareResult = await this._shareService.shareText({
title: contextTitle ?? 'Super Productivity',
text: markdown,
});
const didCopy = await this._markdownService.copyMarkdownText(markdown);
if (didCopy) {
if (shareResult === 'unavailable') {
this._snackService.open(T.GLOBAL_SNACK.SHARE_UNAVAILABLE_FALLBACK);
this._setShareSupport('none');
} else if (shareResult === 'failed') {
this._snackService.open(T.GLOBAL_SNACK.SHARE_FAILED_FALLBACK);
this._setShareSupport('none');
if (shareResult === 'shared') {
if (this.shareSupport === 'none') {
const support = await this._shareService.getShareSupport();
this._setShareSupport(support);
}
return;
}
if (shareResult === 'cancelled') {
return;
}
const didCopy = await this._markdownService.copyMarkdownText(markdown);
if (didCopy) {
if (shareResult === 'unavailable') {
this._snackService.open(T.GLOBAL_SNACK.SHARE_UNAVAILABLE_FALLBACK);
this._setShareSupport('none');
} else if (shareResult === 'failed') {
this._snackService.open(T.GLOBAL_SNACK.SHARE_FAILED_FALLBACK);
this._setShareSupport('none');
} else {
this._snackService.open(T.GLOBAL_SNACK.COPY_TO_CLIPPBOARD);
}
return;
}
this._snackService.open({
msg: T.GLOBAL_SNACK.SHARE_FAILED,
type: 'ERROR',
});
this._setShareSupport('none');
} finally {
// iOS-specific: Delay clearing flag to prevent re-trigger from focus events
// On iOS, dismissing the native share sheet fires window focus events
// that can cause the method to be called again
if (IS_IOS) {
setTimeout(() => {
this._isShareInProgress = false;
}, 500);
} else {
this._snackService.open(T.GLOBAL_SNACK.COPY_TO_CLIPPBOARD);
this._isShareInProgress = false;
}
return;
}
this._snackService.open({
msg: T.GLOBAL_SNACK.SHARE_FAILED,
type: 'ERROR',
});
this._setShareSupport('none');
}
async unplanAllTodayTasks(): Promise<void> {