Skip to content

Commit d41a21a

Browse files
authored
Merge branch 'master' into cf/palette-theming
2 parents 3427a2f + b1ff6f3 commit d41a21a

File tree

9 files changed

+139
-105
lines changed

9 files changed

+139
-105
lines changed

packages/fcl-react-native/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @onflow/fcl-react-native
22

3+
## 1.22.0
4+
5+
### Minor Changes
6+
7+
- [#2774](https://github.com/onflow/fcl-js/pull/2774) [`d8cbe12f20ca9c047567155b40642e3dbea66c89`](https://github.com/onflow/fcl-js/commit/d8cbe12f20ca9c047567155b40642e3dbea66c89) Thanks [@mfbz](https://github.com/mfbz)! - Improved wc redirect flexibility and updated connect modal to be normal centered modal for better layout support.
8+
39
## 1.21.0
410

511
### Minor Changes

packages/fcl-react-native/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ npm install --save @onflow/fcl @onflow/types
3838
For a detailed guide explaining how to use `@onflow/fcl` to interact with Flow please see the [Flow App Quick Start](https://developers.flow.com/tutorials/flow-app-quickstart)
3939

4040
Having trouble with something? Reach out to us on [Discord](https://discord.gg/k6cZ7QC), we are more than happy to help.
41+
42+
## WalletConnect Deeplinks
43+
44+
This package uses `wc-redirect` as the deeplink path for WalletConnect redirects (e.g., `myapp://wc-redirect`). When a wallet approves a connection or transaction, it redirects back to your app using this path.
45+
46+
If you're using Expo Router, you may want to intercept this path to prevent unwanted navigation. See `@onflow/react-native-sdk` README for details.

packages/fcl-react-native/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@onflow/fcl-react-native",
3-
"version": "1.21.0",
3+
"version": "1.22.0",
44
"description": "React Native JavaScript/TypeScript library for building mobile applications on the Flow blockchain.",
55
"license": "Apache-2.0",
66
"author": "Flow Foundation",

packages/fcl-react-native/src/utils/react-native/ConnectModal.js

Lines changed: 73 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {Image} from "expo-image"
2-
import {createElement, useEffect, useRef, useState} from "react"
2+
import {createElement, useEffect, useState} from "react"
33
import {
4-
Animated,
54
Modal,
5+
Pressable,
66
SafeAreaView,
77
ScrollView,
88
StyleSheet,
@@ -92,40 +92,15 @@ export const ConnectModal = ({
9292
}) => {
9393
const {services, isLoading} = useServiceDiscovery({fcl})
9494

95-
// Animation values
96-
const backdropOpacity = useRef(new Animated.Value(0)).current
97-
const slideAnim = useRef(new Animated.Value(300)).current
98-
9995
// Double-click protection
10096
const [isAuthenticating, setIsAuthenticating] = useState(false)
10197

102-
// Animate backdrop and content when modal visibility changes
98+
// Reset authentication state when modal opens
10399
useEffect(() => {
104100
if (visible) {
105-
// Fade in backdrop instantly (fast)
106-
Animated.timing(backdropOpacity, {
107-
toValue: 1,
108-
duration: 200,
109-
useNativeDriver: true,
110-
}).start()
111-
112-
// Slide up content with spring animation
113-
Animated.spring(slideAnim, {
114-
toValue: 0,
115-
tension: 65,
116-
friction: 10,
117-
useNativeDriver: true,
118-
}).start()
119-
120-
// Reset authentication state when modal opens
121-
setIsAuthenticating(false)
122-
} else {
123-
// Reset animations when modal closes
124-
backdropOpacity.setValue(0)
125-
slideAnim.setValue(300)
126101
setIsAuthenticating(false)
127102
}
128-
}, [visible, backdropOpacity, slideAnim])
103+
}, [visible])
129104

130105
const handleServiceSelect = service => {
131106
// Prevent double-click: ignore if already authenticating
@@ -141,76 +116,70 @@ export const ConnectModal = ({
141116
{
142117
visible,
143118
transparent: true,
144-
animationType: "none",
119+
animationType: "fade",
145120
onRequestClose: onClose,
146121
},
147122
createElement(
148-
Animated.View,
149-
{style: [styles.backdrop, {opacity: backdropOpacity}]},
150-
createElement(TouchableOpacity, {
151-
style: styles.backdropTouchable,
152-
activeOpacity: 1,
153-
onPress: onClose,
154-
}),
123+
Pressable,
124+
{style: styles.backdrop, onPress: onClose},
155125
createElement(
156-
SafeAreaView,
157-
{style: styles.safeArea},
126+
Pressable,
127+
{style: styles.modalContainer, onPress: e => e.stopPropagation()},
158128
createElement(
159-
Animated.View,
160-
{
161-
style: [
162-
styles.modalContent,
163-
{transform: [{translateY: slideAnim}]},
164-
],
165-
},
166-
// Header
129+
SafeAreaView,
130+
{style: styles.safeArea},
167131
createElement(
168132
View,
169-
{style: styles.header},
170-
createElement(Text, {style: styles.title}, title),
133+
{style: styles.modalContent},
134+
// Header
171135
createElement(
172-
TouchableOpacity,
173-
{onPress: onClose, style: styles.closeButton},
174-
createElement(Text, {style: styles.closeButtonText}, "✕")
175-
)
176-
),
177-
// Content
178-
createElement(
179-
Wrapper,
180-
null,
181-
isLoading &&
182-
(Loading
183-
? createElement(Loading)
184-
: createElement(
185-
View,
186-
{style: styles.loadingContainer},
187-
createElement(
188-
Text,
189-
{style: styles.loadingText},
190-
"Loading wallets..."
191-
)
192-
)),
193-
!isLoading &&
194-
services.length === 0 &&
195-
(Empty
196-
? createElement(Empty)
197-
: createElement(
198-
View,
199-
{style: styles.emptyContainer},
200-
createElement(
201-
Text,
202-
{style: styles.emptyText},
203-
"No wallets found"
204-
)
205-
)),
206-
!isLoading &&
207-
services.map((service, index) => {
208-
return createElement(ServiceCard, {
209-
key: service?.provider?.address ?? service?.uid ?? index,
210-
service,
211-
onPress: () => handleServiceSelect(service),
136+
View,
137+
{style: styles.header},
138+
createElement(Text, {style: styles.title}, title),
139+
createElement(
140+
TouchableOpacity,
141+
{onPress: onClose, style: styles.closeButton},
142+
createElement(Text, {style: styles.closeButtonText}, "✕")
143+
)
144+
),
145+
// Content
146+
createElement(
147+
Wrapper,
148+
null,
149+
isLoading &&
150+
(Loading
151+
? createElement(Loading)
152+
: createElement(
153+
View,
154+
{style: styles.loadingContainer},
155+
createElement(
156+
Text,
157+
{style: styles.loadingText},
158+
"Loading wallets..."
159+
)
160+
)),
161+
!isLoading &&
162+
services.length === 0 &&
163+
(Empty
164+
? createElement(Empty)
165+
: createElement(
166+
View,
167+
{style: styles.emptyContainer},
168+
createElement(
169+
Text,
170+
{style: styles.emptyText},
171+
"No wallets found"
172+
)
173+
)),
174+
!isLoading &&
175+
services.map((service, index) => {
176+
return createElement(ServiceCard, {
177+
key: service?.provider?.address ?? service?.uid ?? index,
178+
service,
179+
onPress: () => handleServiceSelect(service),
180+
})
212181
})
213-
})
182+
)
214183
)
215184
)
216185
)
@@ -222,23 +191,27 @@ const styles = StyleSheet.create({
222191
backdrop: {
223192
flex: 1,
224193
backgroundColor: "rgba(0, 0, 0, 0.5)",
225-
justifyContent: "flex-end",
194+
justifyContent: "center",
195+
alignItems: "center",
196+
padding: 16,
226197
},
227-
backdropTouchable: {
228-
position: "absolute",
229-
top: 0,
230-
left: 0,
231-
right: 0,
232-
bottom: 0,
198+
modalContainer: {
199+
width: "100%",
200+
maxWidth: 400,
201+
maxHeight: "80%",
233202
},
234203
safeArea: {
235-
maxHeight: "80%",
204+
width: "100%",
236205
},
237206
modalContent: {
238207
backgroundColor: "#ffffff",
239-
borderTopLeftRadius: 20,
240-
borderTopRightRadius: 20,
208+
borderRadius: 20,
241209
overflow: "hidden",
210+
shadowColor: "#000000",
211+
shadowOffset: {width: 0, height: 4},
212+
shadowOpacity: 0.15,
213+
shadowRadius: 12,
214+
elevation: 8,
242215
},
243216
header: {
244217
flexDirection: "row",

packages/fcl-react-native/src/walletconnect/client.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ const initClient = async ({
6868
await initializeWalletConnect()
6969

7070
// Auto-detect redirect URI using expo-linking (always available as dependency)
71-
const redirect = Linking.createURL("")
71+
// We use a unique path that apps can intercept
72+
// This allows apps to handle the redirect however they want (e.g., stay on current screen)
73+
const redirect = Linking.createURL("wc-redirect")
7274

7375
// Build metadata
7476
const clientMetadata = metadata || {

packages/react-native-sdk/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# @onflow/react-native-sdk
22

3+
## 0.3.0
4+
5+
### Minor Changes
6+
7+
- [#2774](https://github.com/onflow/fcl-js/pull/2774) [`d8cbe12f20ca9c047567155b40642e3dbea66c89`](https://github.com/onflow/fcl-js/commit/d8cbe12f20ca9c047567155b40642e3dbea66c89) Thanks [@mfbz](https://github.com/mfbz)! - Improved wc redirect flexibility and updated connect modal to be normal centered modal for better layout support.
8+
9+
### Patch Changes
10+
11+
- Updated dependencies [[`d8cbe12f20ca9c047567155b40642e3dbea66c89`](https://github.com/onflow/fcl-js/commit/d8cbe12f20ca9c047567155b40642e3dbea66c89)]:
12+
- @onflow/fcl-react-native@1.22.0
13+
314
## 0.2.0
415

516
### Minor Changes

packages/react-native-sdk/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ Here's a look at some of the hooks available. For a full list, see the [official
5656
- `<Connect />` - Wallet connection button with built-in profile modal
5757
- `<Profile />` - Displays connected wallet information with disconnect option
5858

59+
## 🔗 WalletConnect Deeplinks
60+
61+
This SDK uses `wc-redirect` as the deeplink path for WalletConnect redirects (e.g., `myapp://wc-redirect`). When a wallet approves a connection or transaction, it redirects back to your app using this path.
62+
63+
To prevent navigation flashes, you can intercept this path using Expo Router's `+native-intent.tsx`. See [Expo Router Native Intent documentation](https://docs.expo.dev/router/advanced/native-intent/) for details.
64+
5965
## 📚 Full Documentation
6066

6167
Looking for full API docs, examples, and usage tips?

packages/react-native-sdk/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@onflow/react-native-sdk",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "React Native library for interacting with the Flow blockchain",
55
"license": "Apache-2.0",
66
"author": "Flow Foundation",
@@ -37,7 +37,7 @@
3737
"react-native-svg": "^15.8.0"
3838
},
3939
"peerDependencies": {
40-
"@onflow/fcl-react-native": ">=1.21.0",
40+
"@onflow/fcl-react-native": ">=1.22.0",
4141
"react": "^18.0.0 || ^19.0.0",
4242
"react-native": ">=0.70.0",
4343
"viem": "^2.29.2"

packages/react-native-sdk/src/provider/FlowProvider.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ const defaultQueryOptions: DefaultOptions = {
3030
},
3131
}
3232

33+
// Singleton to preserve flowClient across remounts (e.g., deeplink navigation)
34+
// This prevents auth state from being lost when expo-router causes remounts
35+
let cachedFlowClient: ReturnType<typeof createFlowClient> | null = null
36+
let cachedConfigKey: string | null = null
37+
38+
function getConfigKey(
39+
cfg: FlowConfig,
40+
flowJson?: Record<string, unknown>
41+
): string {
42+
// Create a stable key from config to detect if config actually changed
43+
return JSON.stringify({
44+
accessNodeUrl: cfg.accessNodeUrl,
45+
flowNetwork: cfg.flowNetwork,
46+
walletconnectProjectId: cfg.walletconnectProjectId,
47+
})
48+
}
49+
3350
export function FlowProvider({
3451
config: initialConfig = {},
3552
queryClient: _queryClient,
@@ -43,7 +60,14 @@ export function FlowProvider({
4360

4461
const flowClient = useMemo(() => {
4562
if (_flowClient) return _flowClient
46-
return createFlowClient({
63+
64+
// Check if we can reuse cached client (same config)
65+
const configKey = getConfigKey(initialConfig, flowJson)
66+
if (cachedFlowClient && cachedConfigKey === configKey) {
67+
return cachedFlowClient
68+
}
69+
70+
const client = createFlowClient({
4771
accessNodeUrl: initialConfig.accessNodeUrl!,
4872
discoveryWallet: initialConfig.discoveryWallet,
4973
discoveryWalletMethod: initialConfig.discoveryWalletMethod,
@@ -62,6 +86,12 @@ export function FlowProvider({
6286
appDetailUrl: initialConfig.appDetailUrl,
6387
serviceOpenIdScopes: initialConfig.serviceOpenIdScopes,
6488
})
89+
90+
// Cache for reuse across remounts
91+
cachedFlowClient = client
92+
cachedConfigKey = configKey
93+
94+
return client
6595
}, [_flowClient, initialConfig, flowJson])
6696

6797
// Set discovery.authn.endpoint in global FCL config for ServiceDiscovery

0 commit comments

Comments
 (0)