Skip to content

Commit d2ab13b

Browse files
refactor(mobile): share imports run outside the new-task screen (#25793)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f3b058c commit d2ab13b

6 files changed

Lines changed: 972 additions & 321 deletions

File tree

‎apps/mobile/src/App.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { CloudAuthProvider } from "./features/cloud/CloudAuthProvider";
2626
import { prepareNativeShowcaseCapture } from "./features/showcase/nativeShowcaseScene";
2727
import {
2828
presentPendingIncomingShare,
29+
pruneIncomingShareImports,
2930
startIncomingShareInbox,
3031
} from "./features/sharing/IncomingShareProvider";
3132
import {
@@ -133,6 +134,7 @@ function handleNavigationStateChange(state: NavigationState | undefined): void {
133134
setAgentNotificationPathname(workspaceLocationFromState(state).pathname);
134135
recordAppShortcutNavigationState(state);
135136
pruneNewTaskBranchCheckouts(state);
137+
pruneIncomingShareImports(state);
136138
}
137139

138140
function handleNavigationReady(): void {

‎apps/mobile/src/features/sharing/IncomingShareProvider.tsx‎

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Dialog from "../../components/ui/Dialog";
22
import Constants from "expo-constants";
33
import * as Crypto from "expo-crypto";
4-
import { PROVIDER_SEND_TURN_MAX_FILE_BYTES } from "@t3tools/contracts";
4+
import { EnvironmentId, PROVIDER_SEND_TURN_MAX_FILE_BYTES } from "@t3tools/contracts";
55
import {
66
clearSharedPayloads,
77
getResolvedSharedPayloadsAsync,
@@ -11,10 +11,12 @@ import {
1111
} from "expo-sharing";
1212
import { useSyncExternalStore } from "react";
1313
import { Platform } from "react-native";
14+
import { CommonActions } from "@react-navigation/native";
1415

1516
import {
1617
buildIncomingShareDraft,
1718
isShareFileUriUnderOwnedRoots,
19+
selectIncomingShareAttachmentsForServer,
1820
type IncomingShareDestination,
1921
type IncomingShareDraft,
2022
} from "./incoming-share-model";
@@ -26,6 +28,16 @@ import { coalescedRun } from "../../lib/coalescedRun";
2628
import { rootNavigationRef } from "../../lib/rootNavigation";
2729
import { selectWorkspaceDetail } from "../workspace/workspace-detail-selection";
2830
import { isDraftThreadRoute } from "../threads/thread-route-state";
31+
import { makeIncomingShareImports } from "../threads/incomingShareImport";
32+
import { liveNewTaskFlowKeys } from "../threads/new-task-flow-state";
33+
import { appAtomRegistry } from "../../state/atom-registry";
34+
import { serverEnvironment } from "../../state/server";
35+
import {
36+
getComposerDraftSnapshot,
37+
mergeComposerDraftContent,
38+
scheduleUnusedComposerAttachmentCleanup,
39+
undoComposerDraftMerge,
40+
} from "../../state/use-composer-drafts";
2941
import {
3042
EMPTY_INCOMING_SHARE_PRESENTATION_STATE,
3143
transitionIncomingSharePresentation,
@@ -282,6 +294,67 @@ async function releaseShareReservation(
282294
publish(await incomingShareInbox.releaseReservation(shareId, expectedDestination));
283295
}
284296

297+
/** Moves shares into new-task drafts; see makeIncomingShareImports. */
298+
export const incomingShareImports = makeIncomingShareImports({
299+
registry: appAtomRegistry,
300+
getShare: (shareId) => snapshot.drafts.find((draft) => draft.id === shareId) ?? null,
301+
reserve: reserveShare,
302+
release: releaseShareReservation,
303+
consume: consumeShare,
304+
readDraft: getComposerDraftSnapshot,
305+
undoMerge: undoComposerDraftMerge,
306+
mergeIntoDraft: mergeComposerDraftContent,
307+
selectAttachments: (share, environmentId) =>
308+
selectIncomingShareAttachmentsForServer({
309+
attachments: share.attachments,
310+
serverConfig: appAtomRegistry.get(
311+
serverEnvironment.configValueAtom(EnvironmentId.make(environmentId)),
312+
),
313+
}),
314+
releaseUnusedAttachments: scheduleUnusedComposerAttachmentCleanup,
315+
onWarnings: (warnings) => {
316+
Dialog.alert("Some shared content was skipped", warnings.join("\n"));
317+
},
318+
onImportFailure: (target, error) => {
319+
Dialog.alert(
320+
"Could not import shared content",
321+
error instanceof Error ? error.message : "The shared content could not be saved.",
322+
[
323+
{
324+
text: "Cancel import",
325+
style: "cancel",
326+
onPress: () => void incomingShareImports.cancel(target),
327+
},
328+
{ text: "Retry", onPress: () => incomingShareImports.retry(target) },
329+
],
330+
{ cancelable: false },
331+
);
332+
},
333+
onCancelFailure: (target, error) => {
334+
Dialog.alert(
335+
"Could not cancel import",
336+
error instanceof Error ? error.message : "The shared content could not be restored safely.",
337+
[
338+
{ text: "Retry import", onPress: () => incomingShareImports.retry(target) },
339+
{ text: "Retry cancel", onPress: () => void incomingShareImports.cancel(target) },
340+
],
341+
{ cancelable: false },
342+
);
343+
},
344+
leave: (target) => {
345+
rootNavigationRef.dispatch({
346+
...CommonActions.goBack(),
347+
source: target.routeKey,
348+
target: rootNavigationRef.getRootState()?.key,
349+
});
350+
},
351+
});
352+
353+
/** Abandons the share imports of draft routes that left the stack. */
354+
export function pruneIncomingShareImports(state: Parameters<typeof liveNewTaskFlowKeys>[0]) {
355+
void incomingShareImports.prune(liveNewTaskFlowKeys(state));
356+
}
357+
285358
export function useIncomingShare() {
286359
const { drafts, isLoading } = useSyncExternalStore(
287360
subscribeIncomingShares,
@@ -291,8 +364,6 @@ export function useIncomingShare() {
291364
pendingShare: drafts[0] ?? null,
292365
isLoading,
293366
getShare: (shareId: string) => drafts.find((draft) => draft.id === shareId) ?? null,
294-
reserveShare,
295367
releaseShareReservation,
296-
consumeShare,
297368
};
298369
}

0 commit comments

Comments
 (0)