wallet-stack 1.0.0-alpha.131 → 1.0.0-alpha.133
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/locales/base/translation.json +11 -0
- package/package.json +2 -3
- package/src/analytics/Events.tsx +3 -10
- package/src/analytics/Properties.tsx +9 -25
- package/src/analytics/docs.ts +11 -8
- package/src/app/ErrorMessages.ts +1 -7
- package/src/identity/actions.ts +1 -97
- package/src/identity/contactMapping.test.ts +3 -28
- package/src/identity/contactMapping.ts +2 -88
- package/src/identity/reducer.ts +0 -77
- package/src/identity/saga.ts +2 -85
- package/src/identity/selectors.ts +0 -2
- package/src/images/Images.ts +3 -0
- package/src/images/assets/invite-modal.png +0 -0
- package/src/images/assets/invite-modal@1.5x.png +0 -0
- package/src/images/assets/invite-modal@2x.png +0 -0
- package/src/images/assets/invite-modal@3x.png +0 -0
- package/src/images/assets/invite-modal@4x.png +0 -0
- package/src/images/assets/minipay.png +0 -0
- package/src/images/assets/minipay@1.5x.png +0 -0
- package/src/images/assets/minipay@2x.png +0 -0
- package/src/images/assets/minipay@3x.png +0 -0
- package/src/images/assets/minipay@4x.png +0 -0
- package/src/images/assets/valora.png +0 -0
- package/src/images/assets/valora@1.5x.png +0 -0
- package/src/images/assets/valora@2x.png +0 -0
- package/src/images/assets/valora@3x.png +0 -0
- package/src/images/assets/valora@4x.png +0 -0
- package/src/index.d.ts +0 -1
- package/src/navigator/Navigator.tsx +10 -14
- package/src/navigator/Screens.tsx +2 -2
- package/src/navigator/types.tsx +5 -6
- package/src/qrcode/utils.test.tsx +4 -96
- package/src/qrcode/utils.ts +5 -114
- package/src/redux/migrations.test.ts +13 -0
- package/src/redux/migrations.ts +4 -0
- package/src/redux/store.test.ts +1 -2
- package/src/redux/store.ts +1 -1
- package/src/send/SelectRecipientAddress.test.tsx +146 -0
- package/src/send/SelectRecipientAddress.tsx +166 -0
- package/src/send/SendConfirmation.test.tsx +28 -0
- package/src/send/SendConfirmation.tsx +18 -1
- package/src/send/SendInvite.test.tsx +107 -0
- package/src/send/SendInvite.tsx +99 -0
- package/src/send/SendSelectRecipient.test.tsx +44 -223
- package/src/send/SendSelectRecipient.tsx +41 -149
- package/src/send/actions.ts +0 -26
- package/src/send/saga.ts +1 -6
- package/src/components/AccountNumberCard.tsx +0 -23
- package/src/components/ErrorMessageInline.tsx +0 -78
- package/src/components/SingleDigitInput.tsx +0 -53
- package/src/icons/HamburgerCard.tsx +0 -55
- package/src/identity/saga.test.ts +0 -103
- package/src/identity/secureSend.ts +0 -171
- package/src/send/ValidateRecipientAccount.test.tsx +0 -182
- package/src/send/ValidateRecipientAccount.tsx +0 -392
- package/src/send/ValidateRecipientIntro.test.tsx +0 -61
- package/src/send/ValidateRecipientIntro.tsx +0 -136
- package/src/send/__snapshots__/ValidateRecipientAccount.test.tsx.snap +0 -777
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
|
2
|
-
import React, { useState } from 'react'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
3
3
|
import { useTranslation } from 'react-i18next'
|
|
4
4
|
import { StyleSheet, Text, View } from 'react-native'
|
|
5
5
|
import { getFontScaleSync } from 'react-native-device-info'
|
|
6
6
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
|
7
|
-
import Share from 'react-native-share'
|
|
8
7
|
import { isAddressFormat } from 'src/account/utils'
|
|
9
8
|
import AppAnalytics from 'src/analytics/AppAnalytics'
|
|
10
9
|
import { SendEvents } from 'src/analytics/Events'
|
|
11
10
|
import { SendOrigin } from 'src/analytics/types'
|
|
12
11
|
import { getAppConfig } from 'src/appConfig'
|
|
13
12
|
import BackButton from 'src/components/BackButton'
|
|
14
|
-
import Button, { BtnSizes } from 'src/components/Button'
|
|
15
|
-
import InLineNotification, { NotificationVariant } from 'src/components/InLineNotification'
|
|
16
13
|
import KeyboardAwareScrollView from 'src/components/KeyboardAwareScrollView'
|
|
17
14
|
import CustomHeader from 'src/components/header/CustomHeader'
|
|
18
15
|
import CircledIcon from 'src/icons/CircledIcon'
|
|
19
16
|
import { importContacts } from 'src/identity/actions'
|
|
20
|
-
import {
|
|
21
|
-
import { AddressValidationType } from 'src/identity/reducer'
|
|
22
|
-
import { getAddressValidationType } from 'src/identity/secureSend'
|
|
23
|
-
import {
|
|
24
|
-
addressToVerifiedBySelector,
|
|
25
|
-
e164NumberToAddressSelector,
|
|
26
|
-
secureSendPhoneNumberMappingSelector,
|
|
27
|
-
} from 'src/identity/selectors'
|
|
17
|
+
import { addressToVerifiedBySelector, e164NumberToAddressSelector } from 'src/identity/selectors'
|
|
28
18
|
import { RecipientVerificationStatus } from 'src/identity/types'
|
|
29
19
|
import { noHeader } from 'src/navigator/Headers'
|
|
30
20
|
import { navigate } from 'src/navigator/NavigationService'
|
|
@@ -42,7 +32,6 @@ import colors from 'src/styles/colors'
|
|
|
42
32
|
import { typeScale } from 'src/styles/fonts'
|
|
43
33
|
import { Spacing } from 'src/styles/styles'
|
|
44
34
|
import variables from 'src/styles/variables'
|
|
45
|
-
import Logger from 'src/utils/Logger'
|
|
46
35
|
|
|
47
36
|
type Props = NativeStackScreenProps<StackParamList, Screens.SendSelectRecipient>
|
|
48
37
|
|
|
@@ -167,45 +156,6 @@ const getStartedStyles = StyleSheet.create({
|
|
|
167
156
|
},
|
|
168
157
|
})
|
|
169
158
|
|
|
170
|
-
function SendOrInviteButton({
|
|
171
|
-
recipient,
|
|
172
|
-
recipientVerificationStatus,
|
|
173
|
-
shareUrl,
|
|
174
|
-
onPress,
|
|
175
|
-
}: {
|
|
176
|
-
recipient: Recipient | null
|
|
177
|
-
recipientVerificationStatus: RecipientVerificationStatus
|
|
178
|
-
shareUrl: string | null
|
|
179
|
-
onPress: (shouldInviteRecipient: boolean) => void
|
|
180
|
-
}) {
|
|
181
|
-
const { t } = useTranslation()
|
|
182
|
-
|
|
183
|
-
const sendOrInviteButtonDisabled =
|
|
184
|
-
(!!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN) ||
|
|
185
|
-
// If the phone number is present and unverified and no share URL is found, disable the send/invite button
|
|
186
|
-
(recipient?.recipientType === RecipientType.PhoneNumber &&
|
|
187
|
-
recipientVerificationStatus === RecipientVerificationStatus.UNVERIFIED &&
|
|
188
|
-
!shareUrl)
|
|
189
|
-
|
|
190
|
-
const shouldInviteRecipient =
|
|
191
|
-
!sendOrInviteButtonDisabled &&
|
|
192
|
-
recipient?.recipientType === RecipientType.PhoneNumber &&
|
|
193
|
-
recipientVerificationStatus === RecipientVerificationStatus.UNVERIFIED
|
|
194
|
-
return (
|
|
195
|
-
<Button
|
|
196
|
-
testID="SendOrInviteButton"
|
|
197
|
-
style={styles.sendOrInviteButton}
|
|
198
|
-
onPress={() => onPress(shouldInviteRecipient)}
|
|
199
|
-
disabled={sendOrInviteButtonDisabled}
|
|
200
|
-
text={
|
|
201
|
-
shouldInviteRecipient
|
|
202
|
-
? t('sendSelectRecipient.buttons.invite')
|
|
203
|
-
: t('sendSelectRecipient.buttons.send')
|
|
204
|
-
}
|
|
205
|
-
size={BtnSizes.FULL}
|
|
206
|
-
/>
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
159
|
enum SelectRecipientView {
|
|
210
160
|
Recent = 'Recent',
|
|
211
161
|
Contacts = 'Contacts',
|
|
@@ -214,7 +164,6 @@ enum SelectRecipientView {
|
|
|
214
164
|
function SendSelectRecipient({ route }: Props) {
|
|
215
165
|
const { t } = useTranslation()
|
|
216
166
|
const dispatch = useDispatch()
|
|
217
|
-
const secureSendPhoneNumberMapping = useSelector(secureSendPhoneNumberMappingSelector)
|
|
218
167
|
const e164NumberToAddress = useSelector(e164NumberToAddressSelector)
|
|
219
168
|
const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
|
|
220
169
|
const shareUrl = getAppConfig().experimental?.inviteFriends?.shareUrl ?? null
|
|
@@ -222,19 +171,14 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
222
171
|
const forceTokenId = route.params?.forceTokenId
|
|
223
172
|
const defaultTokenIdOverride = route.params?.defaultTokenIdOverride
|
|
224
173
|
|
|
225
|
-
const [showSendOrInviteButton, setShowSendOrInviteButton] = useState(false)
|
|
226
|
-
|
|
227
174
|
const [showSearchResults, setShowSearchResults] = useState(false)
|
|
228
175
|
|
|
229
176
|
const [activeView, setActiveView] = useState(SelectRecipientView.Recent)
|
|
230
177
|
|
|
231
178
|
const onSearch = (searchQuery: string) => {
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// where the button appears but is bound to a recipient that is
|
|
235
|
-
// not present on the page.
|
|
179
|
+
// Clear any in-flight selection so a stale recipient can't auto-navigate
|
|
180
|
+
// once the user starts typing a different query.
|
|
236
181
|
unsetSelectedRecipient()
|
|
237
|
-
setShowSendOrInviteButton(false)
|
|
238
182
|
setShowSearchResults(!!searchQuery)
|
|
239
183
|
}
|
|
240
184
|
const { contactRecipients, recentRecipients } = useSendRecipients()
|
|
@@ -243,17 +187,31 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
243
187
|
const { recipientVerificationStatus, recipient, setSelectedRecipient, unsetSelectedRecipient } =
|
|
244
188
|
useFetchRecipientVerificationStatus()
|
|
245
189
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
recipient
|
|
250
|
-
|
|
251
|
-
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
// Auto-navigate once verification resolves. The picker stays mounted so the
|
|
192
|
+
// user's search text and selection are preserved when they come back.
|
|
193
|
+
if (!recipient || recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
252
196
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
197
|
+
const isUnverifiedPhone =
|
|
198
|
+
recipient.recipientType === RecipientType.PhoneNumber &&
|
|
199
|
+
recipientVerificationStatus === RecipientVerificationStatus.UNVERIFIED
|
|
200
|
+
|
|
201
|
+
if (isUnverifiedPhone) {
|
|
202
|
+
if (shareUrl) {
|
|
203
|
+
navigate(Screens.SendInvite, { recipient, shareUrl })
|
|
204
|
+
}
|
|
205
|
+
// Without shareUrl there's no invite flow and no send flow for an
|
|
206
|
+
// unverified phone — stay on the picker so the user can pick someone else.
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
AppAnalytics.track(SendEvents.send_select_recipient_send_press, {
|
|
211
|
+
recipientType: recipient.recipientType,
|
|
212
|
+
})
|
|
213
|
+
nextScreen(recipient)
|
|
214
|
+
}, [recipient, recipientVerificationStatus, shareUrl])
|
|
257
215
|
|
|
258
216
|
const onContactsPermissionGranted = () => {
|
|
259
217
|
dispatch(importContacts())
|
|
@@ -268,7 +226,6 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
268
226
|
AppAnalytics.track(SendEvents.send_select_recipient_recent_press, {
|
|
269
227
|
recipientType: recentRecipient.recipientType,
|
|
270
228
|
})
|
|
271
|
-
setSelectedRecipient(recentRecipient)
|
|
272
229
|
nextScreen(recentRecipient)
|
|
273
230
|
}
|
|
274
231
|
|
|
@@ -276,35 +233,28 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
276
233
|
// use the address from the recipient object
|
|
277
234
|
let address: string | null | undefined = selectedRecipient.address
|
|
278
235
|
|
|
279
|
-
// if not present there must be a phone number, route through
|
|
280
|
-
//
|
|
236
|
+
// if not present there must be a phone number, route through the address picker
|
|
237
|
+
// when multiple verified addresses exist, otherwise go directly to amount entry
|
|
281
238
|
if (!address && recipientHasNumber(selectedRecipient)) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
navigate(Screens.ValidateRecipientIntro, {
|
|
288
|
-
defaultTokenIdOverride,
|
|
239
|
+
const phoneAddresses = e164NumberToAddress[selectedRecipient.e164PhoneNumber] ?? []
|
|
240
|
+
const verifiedAddresses = phoneAddresses.filter((a) => !!addressToVerifiedBy[a])
|
|
241
|
+
|
|
242
|
+
if (verifiedAddresses.length > 1) {
|
|
243
|
+
navigate(Screens.SelectRecipientAddress, {
|
|
289
244
|
forceTokenId,
|
|
245
|
+
defaultTokenIdOverride,
|
|
290
246
|
recipient: selectedRecipient,
|
|
291
247
|
origin: SendOrigin.AppSendFlow,
|
|
292
248
|
})
|
|
293
249
|
return
|
|
294
250
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
e164NumberToAddress,
|
|
298
|
-
secureSendPhoneNumberMapping,
|
|
299
|
-
undefined
|
|
300
|
-
)
|
|
251
|
+
|
|
252
|
+
address = verifiedAddresses[0] ?? phoneAddresses[0]
|
|
301
253
|
}
|
|
302
254
|
|
|
303
255
|
if (!address) {
|
|
304
256
|
// this should never happen
|
|
305
|
-
throw new Error(
|
|
306
|
-
'No address found, this should never happen. Should have routed to invite or secure send.'
|
|
307
|
-
)
|
|
257
|
+
throw new Error('No address found, this should never happen. Should have routed to invite.')
|
|
308
258
|
}
|
|
309
259
|
|
|
310
260
|
navigate(Screens.SendEnterAmount, {
|
|
@@ -320,40 +270,6 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
320
270
|
})
|
|
321
271
|
}
|
|
322
272
|
|
|
323
|
-
const onPressSendOrInvite = async (shouldInviteRecipient: boolean) => {
|
|
324
|
-
if (!recipient) return
|
|
325
|
-
|
|
326
|
-
// Invites
|
|
327
|
-
if (shouldInviteRecipient) {
|
|
328
|
-
if (!shareUrl) {
|
|
329
|
-
Logger.warn('SendSelectRecipient', 'No share URL found for invite')
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
AppAnalytics.track(SendEvents.send_select_recipient_invite_press, {
|
|
334
|
-
recipientType: recipient.recipientType,
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
try {
|
|
338
|
-
await Share.open({
|
|
339
|
-
message: t('inviteWithSmsMessage.shareMessage', { shareUrl }),
|
|
340
|
-
url: shareUrl,
|
|
341
|
-
failOnCancel: false,
|
|
342
|
-
})
|
|
343
|
-
} catch (error) {
|
|
344
|
-
Logger.warn('SendSelectRecipient', 'Share sheet failed', error)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Sends
|
|
351
|
-
AppAnalytics.track(SendEvents.send_select_recipient_send_press, {
|
|
352
|
-
recipientType: recipient.recipientType,
|
|
353
|
-
})
|
|
354
|
-
nextScreen(recipient)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
273
|
const renderSearchResults = () => {
|
|
358
274
|
if (mergedRecipients.length) {
|
|
359
275
|
return (
|
|
@@ -362,7 +278,7 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
362
278
|
<RecipientPicker
|
|
363
279
|
testID={'SelectRecipient/AllRecipientsPicker'}
|
|
364
280
|
recipients={mergedRecipients}
|
|
365
|
-
onSelectRecipient={
|
|
281
|
+
onSelectRecipient={setSelectedRecipient}
|
|
366
282
|
selectedRecipient={recipient}
|
|
367
283
|
isSelectedRecipientLoading={
|
|
368
284
|
!!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
|
|
@@ -409,7 +325,7 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
409
325
|
<RecipientPicker
|
|
410
326
|
testID={'SelectRecipient/ContactRecipientPicker'}
|
|
411
327
|
recipients={contactRecipients}
|
|
412
|
-
onSelectRecipient={
|
|
328
|
+
onSelectRecipient={setSelectedRecipient}
|
|
413
329
|
selectedRecipient={recipient}
|
|
414
330
|
isSelectedRecipientLoading={
|
|
415
331
|
!!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
|
|
@@ -439,22 +355,6 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
439
355
|
</>
|
|
440
356
|
)}
|
|
441
357
|
</KeyboardAwareScrollView>
|
|
442
|
-
{showUnknownAddressInfo && (
|
|
443
|
-
<InLineNotification
|
|
444
|
-
variant={NotificationVariant.Info}
|
|
445
|
-
description={t('sendSelectRecipient.unknownAddressInfo')}
|
|
446
|
-
testID="UnknownAddressInfo"
|
|
447
|
-
style={styles.unknownAddressInfo}
|
|
448
|
-
/>
|
|
449
|
-
)}
|
|
450
|
-
{showSendOrInviteButton && (
|
|
451
|
-
<SendOrInviteButton
|
|
452
|
-
recipient={recipient}
|
|
453
|
-
recipientVerificationStatus={recipientVerificationStatus}
|
|
454
|
-
onPress={onPressSendOrInvite}
|
|
455
|
-
shareUrl={shareUrl}
|
|
456
|
-
/>
|
|
457
|
-
)}
|
|
458
358
|
</SafeAreaView>
|
|
459
359
|
)
|
|
460
360
|
}
|
|
@@ -496,14 +396,6 @@ const styles = StyleSheet.create({
|
|
|
496
396
|
padding: Spacing.Regular16,
|
|
497
397
|
textAlign: 'center',
|
|
498
398
|
},
|
|
499
|
-
unknownAddressInfo: {
|
|
500
|
-
margin: Spacing.Regular16,
|
|
501
|
-
marginBottom: variables.contentPadding,
|
|
502
|
-
},
|
|
503
|
-
sendOrInviteButton: {
|
|
504
|
-
margin: Spacing.Regular16,
|
|
505
|
-
marginTop: variables.contentPadding,
|
|
506
|
-
},
|
|
507
399
|
})
|
|
508
400
|
|
|
509
401
|
export default SendSelectRecipient
|
package/src/send/actions.ts
CHANGED
|
@@ -9,7 +9,6 @@ export type SVG = typeof Svg
|
|
|
9
9
|
|
|
10
10
|
export enum Actions {
|
|
11
11
|
BARCODE_DETECTED = 'SEND/BARCODE_DETECTED',
|
|
12
|
-
BARCODE_DETECTED_SECURE_SEND = 'SEND/BARCODE_DETECTED_SECURE_SEND',
|
|
13
12
|
QRCODE_SHARE = 'SEND/QRCODE_SHARE',
|
|
14
13
|
SEND_PAYMENT = 'SEND/SEND_PAYMENT',
|
|
15
14
|
SEND_PAYMENT_SUCCESS = 'SEND/SEND_PAYMENT_SUCCESS',
|
|
@@ -23,15 +22,6 @@ export interface HandleQRCodeDetectedAction {
|
|
|
23
22
|
defaultTokenIdOverride?: string
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
export interface HandleQRCodeDetectedSecureSendAction {
|
|
27
|
-
type: Actions.BARCODE_DETECTED_SECURE_SEND
|
|
28
|
-
qrCode: QrCode
|
|
29
|
-
requesterAddress?: string
|
|
30
|
-
recipient: Recipient
|
|
31
|
-
forceTokenId?: boolean
|
|
32
|
-
defaultTokenIdOverride?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
25
|
export interface ShareQRCodeAction {
|
|
36
26
|
type: Actions.QRCODE_SHARE
|
|
37
27
|
qrCodeSvg: SVG
|
|
@@ -64,7 +54,6 @@ export interface UpdateLastUsedCurrencyAction {
|
|
|
64
54
|
|
|
65
55
|
export type ActionTypes =
|
|
66
56
|
| HandleQRCodeDetectedAction
|
|
67
|
-
| HandleQRCodeDetectedSecureSendAction
|
|
68
57
|
| ShareQRCodeAction
|
|
69
58
|
| SendPaymentAction
|
|
70
59
|
| SendPaymentSuccessAction
|
|
@@ -83,21 +72,6 @@ export const handleQRCodeDetected = ({
|
|
|
83
72
|
defaultTokenIdOverride,
|
|
84
73
|
})
|
|
85
74
|
|
|
86
|
-
export const handleQRCodeDetectedSecureSend = (
|
|
87
|
-
qrCode: QrCode,
|
|
88
|
-
recipient: Recipient,
|
|
89
|
-
requesterAddress?: string,
|
|
90
|
-
forceTokenId?: boolean,
|
|
91
|
-
defaultTokenIdOverride?: string
|
|
92
|
-
): HandleQRCodeDetectedSecureSendAction => ({
|
|
93
|
-
type: Actions.BARCODE_DETECTED_SECURE_SEND,
|
|
94
|
-
qrCode,
|
|
95
|
-
requesterAddress,
|
|
96
|
-
recipient,
|
|
97
|
-
forceTokenId,
|
|
98
|
-
defaultTokenIdOverride,
|
|
99
|
-
})
|
|
100
|
-
|
|
101
75
|
export const shareQRCode = (qrCodeSvg: SVG): ShareQRCodeAction => ({
|
|
102
76
|
type: Actions.QRCODE_SHARE,
|
|
103
77
|
qrCodeSvg,
|
package/src/send/saga.ts
CHANGED
|
@@ -3,7 +3,7 @@ import AppAnalytics from 'src/analytics/AppAnalytics'
|
|
|
3
3
|
import { CeloExchangeEvents, SendEvents } from 'src/analytics/Events'
|
|
4
4
|
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
5
5
|
import { navigateBack, navigateInitialTab } from 'src/navigator/NavigationService'
|
|
6
|
-
import { handleQRCodeDefault,
|
|
6
|
+
import { handleQRCodeDefault, shareSVGImage } from 'src/qrcode/utils'
|
|
7
7
|
import {
|
|
8
8
|
Actions,
|
|
9
9
|
SendPaymentAction,
|
|
@@ -164,12 +164,7 @@ function* watchQrCodeDetections() {
|
|
|
164
164
|
yield* takeEvery(Actions.BARCODE_DETECTED, safely(handleQRCodeDefault))
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
function* watchQrCodeDetectionsSecureSend() {
|
|
168
|
-
yield* takeEvery(Actions.BARCODE_DETECTED_SECURE_SEND, safely(handleQRCodeSecureSend))
|
|
169
|
-
}
|
|
170
|
-
|
|
171
167
|
export function* sendSaga() {
|
|
172
|
-
yield* spawn(watchQrCodeDetectionsSecureSend)
|
|
173
168
|
yield* spawn(watchQrCodeDetections)
|
|
174
169
|
yield* spawn(watchQrCodeShare)
|
|
175
170
|
yield* spawn(watchSendPayment)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
-
import AccountNumber from 'src/components/AccountNumber'
|
|
4
|
-
import Card from 'src/components/Card'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
address: string
|
|
8
|
-
style?: ViewStyle
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function AccountNumberCard({ address, style }: Props) {
|
|
12
|
-
return (
|
|
13
|
-
<Card style={[styles.container, style]} rounded={true}>
|
|
14
|
-
<AccountNumber address={address} touchDisabled={true} />
|
|
15
|
-
</Card>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const styles = StyleSheet.create({
|
|
20
|
-
container: {
|
|
21
|
-
justifyContent: 'center',
|
|
22
|
-
},
|
|
23
|
-
})
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { WithTranslation } from 'react-i18next'
|
|
3
|
-
import { StyleSheet, Text, View } from 'react-native'
|
|
4
|
-
import { connect } from 'react-redux'
|
|
5
|
-
import { hideAlert } from 'src/alert/actions'
|
|
6
|
-
import { ErrorDisplayType } from 'src/alert/reducer'
|
|
7
|
-
import { ErrorMessages } from 'src/app/ErrorMessages'
|
|
8
|
-
import { withTranslation } from 'src/i18n'
|
|
9
|
-
import { RootState } from 'src/redux/reducers'
|
|
10
|
-
import colors from 'src/styles/colors'
|
|
11
|
-
import { typeScale } from 'src/styles/fonts'
|
|
12
|
-
|
|
13
|
-
const DISMISS_DEFAULT = 5
|
|
14
|
-
|
|
15
|
-
interface StateProps {
|
|
16
|
-
displayMethod: ErrorDisplayType | null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface OwnProps {
|
|
20
|
-
error?: ErrorMessages | null
|
|
21
|
-
dismissAfter?: number
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface DispatchProps {
|
|
25
|
-
hideAlert: typeof hideAlert
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const mapStateToProps = (state: RootState): StateProps => {
|
|
29
|
-
const displayMethod = state.alert ? state.alert.displayMethod : null
|
|
30
|
-
return {
|
|
31
|
-
displayMethod,
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const mapDispatchToProps = {
|
|
36
|
-
hideAlert,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type Props = DispatchProps & OwnProps & WithTranslation & StateProps
|
|
40
|
-
|
|
41
|
-
function ErrorMessageInline(props: Props) {
|
|
42
|
-
const { error, displayMethod, dismissAfter, t } = props
|
|
43
|
-
|
|
44
|
-
// Want to initiate/cleanup a timer for each new error
|
|
45
|
-
React.useEffect(() => {
|
|
46
|
-
const timer = window.setTimeout(props.hideAlert, (dismissAfter || DISMISS_DEFAULT) * 1000)
|
|
47
|
-
return () => window.clearTimeout(timer)
|
|
48
|
-
}, [error])
|
|
49
|
-
|
|
50
|
-
// Keep the space empty when there isn't an inline error
|
|
51
|
-
// displayMethod lives in redux store and we want to be able to use this component
|
|
52
|
-
// without populating it so much check if other types are already displayed
|
|
53
|
-
// rather than if INLINE is
|
|
54
|
-
if (!error || displayMethod === ErrorDisplayType.BANNER) {
|
|
55
|
-
return <View style={dismissAfter !== null && styles.errorContainer} />
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<View style={dismissAfter !== null && styles.errorContainer}>
|
|
60
|
-
<Text style={styles.errorMessage}>{t(error)} </Text>
|
|
61
|
-
</View>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const styles = StyleSheet.create({
|
|
66
|
-
errorContainer: {
|
|
67
|
-
height: 64,
|
|
68
|
-
},
|
|
69
|
-
errorMessage: {
|
|
70
|
-
...typeScale.bodySmall,
|
|
71
|
-
color: colors.errorPrimary,
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
|
|
76
|
-
(state: RootState) => mapStateToProps,
|
|
77
|
-
mapDispatchToProps
|
|
78
|
-
)(withTranslation<Props>()(ErrorMessageInline))
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { StyleSheet } from 'react-native'
|
|
3
|
-
import TextInput from 'src/components/TextInput'
|
|
4
|
-
import colors from 'src/styles/colors'
|
|
5
|
-
|
|
6
|
-
export interface SingleDigitInputProps {
|
|
7
|
-
inputValue: string
|
|
8
|
-
inputPlaceholder: string
|
|
9
|
-
onInputChange: (value: string) => void
|
|
10
|
-
testID?: string
|
|
11
|
-
forwardedRef: any
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Props = SingleDigitInputProps
|
|
15
|
-
|
|
16
|
-
// Multiline enabled as to handle unexpected cursor behavior
|
|
17
|
-
// https://github.com/facebook/react-native/issues/28794#issuecomment-877769852
|
|
18
|
-
|
|
19
|
-
export function SingleDigitInput({
|
|
20
|
-
inputValue,
|
|
21
|
-
inputPlaceholder,
|
|
22
|
-
onInputChange,
|
|
23
|
-
testID,
|
|
24
|
-
forwardedRef,
|
|
25
|
-
}: Props) {
|
|
26
|
-
return (
|
|
27
|
-
<TextInput
|
|
28
|
-
multiline={true}
|
|
29
|
-
textAlign={'center'}
|
|
30
|
-
value={inputValue}
|
|
31
|
-
placeholder={inputPlaceholder}
|
|
32
|
-
onChangeText={onInputChange}
|
|
33
|
-
maxLength={1}
|
|
34
|
-
showClearButton={false}
|
|
35
|
-
style={styles.codeInput}
|
|
36
|
-
testID={testID}
|
|
37
|
-
ref={forwardedRef}
|
|
38
|
-
/>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const styles = StyleSheet.create({
|
|
43
|
-
codeInput: {
|
|
44
|
-
borderColor: colors.borderPrimary,
|
|
45
|
-
borderRadius: 3,
|
|
46
|
-
borderWidth: 1,
|
|
47
|
-
flex: 0,
|
|
48
|
-
backgroundColor: colors.backgroundPrimary,
|
|
49
|
-
height: 50,
|
|
50
|
-
width: 50,
|
|
51
|
-
marginVertical: 5,
|
|
52
|
-
},
|
|
53
|
-
})
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import { StyleSheet, View } from 'react-native'
|
|
3
|
-
import { Line, Svg } from 'react-native-svg'
|
|
4
|
-
import { default as Colors, default as colors } from 'src/styles/colors'
|
|
5
|
-
import globalStyles from 'src/styles/styles'
|
|
6
|
-
|
|
7
|
-
function HamburgerCard() {
|
|
8
|
-
return (
|
|
9
|
-
<View style={styles.container}>
|
|
10
|
-
<Svg testID="Hamburger" width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
11
|
-
<Line
|
|
12
|
-
x1="7.25"
|
|
13
|
-
y1="9.75"
|
|
14
|
-
x2="24.75"
|
|
15
|
-
y2="9.75"
|
|
16
|
-
stroke={Colors.contentPrimary}
|
|
17
|
-
strokeWidth="2.5"
|
|
18
|
-
strokeLinecap="round"
|
|
19
|
-
/>
|
|
20
|
-
<Line
|
|
21
|
-
x1="7.25"
|
|
22
|
-
y1="15.75"
|
|
23
|
-
x2="24.75"
|
|
24
|
-
y2="15.75"
|
|
25
|
-
stroke={Colors.contentPrimary}
|
|
26
|
-
strokeWidth="2.5"
|
|
27
|
-
strokeLinecap="round"
|
|
28
|
-
/>
|
|
29
|
-
<Line
|
|
30
|
-
x1="7.25"
|
|
31
|
-
y1="21.75"
|
|
32
|
-
x2="24.75"
|
|
33
|
-
y2="21.75"
|
|
34
|
-
stroke={Colors.contentPrimary}
|
|
35
|
-
strokeWidth="2.5"
|
|
36
|
-
strokeLinecap="round"
|
|
37
|
-
/>
|
|
38
|
-
</Svg>
|
|
39
|
-
</View>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const styles = StyleSheet.create({
|
|
44
|
-
container: {
|
|
45
|
-
backgroundColor: colors.backgroundPrimary,
|
|
46
|
-
...globalStyles.softShadowLight,
|
|
47
|
-
alignItems: 'center',
|
|
48
|
-
justifyContent: 'center',
|
|
49
|
-
borderRadius: 4,
|
|
50
|
-
height: 32,
|
|
51
|
-
width: 32,
|
|
52
|
-
},
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
export default React.memo(HamburgerCard)
|