Skip to content

Commit ee5ae9c

Browse files
author
Lalit Sharma
committed
feat: update changelog for version 1.1.44, fix Android shared-link intake timing, and implement useShareIntentBridge
1 parent 7e6efb3 commit ee5ae9c

4 files changed

Lines changed: 164 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.44] — 2026-02-27
9+
10+
### Fixed
11+
- Fixed Android shared-link cold-start intake timing by replacing direct `useShareIntent` usage with a local bridge hook that attaches native listeners before requesting pending share-intent payloads.
12+
- Fixed potential dropped first-share behavior after launching from Google Maps share sheet by processing native intent refresh only after listener registration.
13+
14+
### Changed
15+
- Bumped `apps/mobile` version to `1.1.44`.
16+
817
## [1.1.43] — 2026-02-27
918

1019
### Fixed

apps/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eclipse-timer/mobile",
3-
"version": "1.1.43",
3+
"version": "1.1.44",
44
"private": true,
55
"main": "index.js",
66
"scripts": {

apps/mobile/src/navigation/RootNavigator.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
createNativeStackNavigator,
2525
type NativeStackScreenProps,
2626
} from "@react-navigation/native-stack";
27-
import { useShareIntent } from "expo-share-intent";
2827
import * as SplashScreen from "expo-splash-screen";
2928
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3029
import {
@@ -60,6 +59,7 @@ import {
6059
normalizeSharePayloadToIncomingLinks,
6160
subscribeToIncomingExternalLinks,
6261
} from "../services/shareIntake";
62+
import useShareIntentBridge from "../services/useShareIntentBridge";
6363
import { syncWearPreviewRouteState } from "../services/wearPreviewPublisher";
6464
import { startWearLiveSync } from "../services/wearSync";
6565
import { type FavoriteLocation, useAppState } from "../state/appState";
@@ -608,7 +608,7 @@ function LocationSettingsRoute({ onOpenMenu }: RouteWithMenuProps) {
608608
export default function RootNavigator() {
609609
const { state: appState, hasHydratedPreferences, actions } = useAppState();
610610
const { colors, resolvedTheme } = useAppTheme();
611-
const { hasShareIntent, shareIntent, resetShareIntent } = useShareIntent({
611+
const { hasShareIntent, shareIntent, resetShareIntent } = useShareIntentBridge({
612612
resetOnBackground: false,
613613
});
614614
const navigationRef = useNavigationContainerRef<RootStackParamList>();
@@ -931,6 +931,7 @@ export default function RootNavigator() {
931931
const shareIncomingLinks = normalizeSharePayloadToIncomingLinks({
932932
webUrl: shareIntent.webUrl,
933933
text: shareIntent.text,
934+
value: shareIntent.meta?.title,
934935
});
935936
if (!shareIncomingLinks.length) {
936937
resetShareIntent();
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { useLinkingURL } from "expo-linking";
2+
import {
3+
getScheme,
4+
getShareExtensionKey,
5+
parseShareIntent,
6+
type ShareIntent,
7+
ShareIntentModule,
8+
} from "expo-share-intent";
9+
import type {
10+
AndroidShareIntent,
11+
ShareIntentOptions,
12+
} from "expo-share-intent/build/ExpoShareIntentModule.types";
13+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
14+
import { AppState, Platform } from "react-native";
15+
16+
const SHARE_INTENT_DEFAULT_VALUE: ShareIntent = {
17+
files: null,
18+
text: null,
19+
webUrl: null,
20+
type: null,
21+
};
22+
23+
function hasShareIntentValue(intent: ShareIntent): boolean {
24+
return Boolean(
25+
intent.text || intent.webUrl || (Array.isArray(intent.files) && intent.files.length > 0),
26+
);
27+
}
28+
29+
export default function useShareIntentBridge(options: ShareIntentOptions = {}) {
30+
const url = useLinkingURL();
31+
const appState = useRef(AppState.currentState);
32+
const [shareIntent, setShareIntent] = useState<ShareIntent>(SHARE_INTENT_DEFAULT_VALUE);
33+
const [error, setError] = useState<string | null>(null);
34+
const [isReady, setIsReady] = useState(false);
35+
36+
const resolvedOptions = useMemo<ShareIntentOptions>(
37+
() => ({
38+
debug: options.debug ?? false,
39+
resetOnBackground: options.resetOnBackground ?? true,
40+
disabled: options.disabled ?? Platform.OS === "web",
41+
scheme: options.scheme,
42+
onResetShareIntent: options.onResetShareIntent,
43+
}),
44+
[
45+
options.debug,
46+
options.disabled,
47+
options.onResetShareIntent,
48+
options.resetOnBackground,
49+
options.scheme,
50+
],
51+
);
52+
53+
const resetShareIntent = useCallback(
54+
(clearNativeModule = true) => {
55+
if (resolvedOptions.disabled) return;
56+
setError(null);
57+
if (clearNativeModule && ShareIntentModule) {
58+
void ShareIntentModule.clearShareIntent(getShareExtensionKey(resolvedOptions));
59+
}
60+
if (hasShareIntentValue(shareIntent)) {
61+
setShareIntent(SHARE_INTENT_DEFAULT_VALUE);
62+
resolvedOptions.onResetShareIntent?.();
63+
}
64+
},
65+
[resolvedOptions, shareIntent],
66+
);
67+
68+
const refreshShareIntent = useCallback(() => {
69+
if (resolvedOptions.disabled || !ShareIntentModule) return;
70+
71+
if (Platform.OS === "android") {
72+
ShareIntentModule.getShareIntent("");
73+
return;
74+
}
75+
76+
const scheme = getScheme(resolvedOptions);
77+
if (typeof url === "string" && url.includes(`${scheme}://dataUrl=`)) {
78+
ShareIntentModule.getShareIntent(url);
79+
}
80+
}, [resolvedOptions, url]);
81+
82+
useEffect(() => {
83+
if (resolvedOptions.disabled) {
84+
setIsReady(true);
85+
return;
86+
}
87+
88+
if (!ShareIntentModule) {
89+
setIsReady(true);
90+
if (resolvedOptions.debug) {
91+
console.warn("expo-share-intent module unavailable");
92+
}
93+
return;
94+
}
95+
96+
const changeSubscription = ShareIntentModule.addListener("onChange", (event) => {
97+
try {
98+
const parsed = parseShareIntent(
99+
event.value as string | AndroidShareIntent,
100+
resolvedOptions,
101+
);
102+
setShareIntent(parsed);
103+
setError(null);
104+
} catch {
105+
setError("Cannot parse share intent value!");
106+
}
107+
});
108+
109+
const errorSubscription = ShareIntentModule.addListener("onError", (event) => {
110+
setError(event?.value ?? "Share intent error");
111+
});
112+
113+
// Important: refresh after listeners are attached so cold-start payloads are not dropped.
114+
refreshShareIntent();
115+
setIsReady(true);
116+
117+
return () => {
118+
changeSubscription.remove();
119+
errorSubscription.remove();
120+
};
121+
}, [refreshShareIntent, resolvedOptions]);
122+
123+
useEffect(() => {
124+
if (resolvedOptions.disabled) return;
125+
126+
const subscription = AppState.addEventListener("change", (nextAppState) => {
127+
if (nextAppState === "active") {
128+
refreshShareIntent();
129+
} else if (
130+
resolvedOptions.resetOnBackground !== false &&
131+
appState.current === "active" &&
132+
(nextAppState === "inactive" || nextAppState === "background")
133+
) {
134+
resetShareIntent();
135+
}
136+
appState.current = nextAppState;
137+
});
138+
139+
return () => {
140+
subscription.remove();
141+
};
142+
}, [refreshShareIntent, resetShareIntent, resolvedOptions]);
143+
144+
return {
145+
isReady,
146+
hasShareIntent: hasShareIntentValue(shareIntent),
147+
shareIntent,
148+
resetShareIntent,
149+
error,
150+
};
151+
}

0 commit comments

Comments
 (0)