diff --git a/CHANGELOG.md b/CHANGELOG.md index f6623a49cd4..d376e60aa6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.7] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.7) + +### Added + +- points v0 +- prompt app reviews +- bug fixes + ## [1.9.6] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.6) ### Added diff --git a/android/app/build.gradle b/android/app/build.gradle index f8de8ea381a..098f20cbfc0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -152,8 +152,8 @@ android { applicationId "me.rainbow" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 191 - versionName "1.9.7" + versionCode 192 + versionName "1.9.8" missingDimensionStrategy 'react-native-camera', 'general' renderscriptTargetApi 23 renderscriptSupportModeEnabled true diff --git a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java index 70a6275e742..f83ddc4de23 100644 --- a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java +++ b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java @@ -1,6 +1,8 @@ package me.rainbow.NativeModules.RNReview; +import android.app.Activity; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.google.android.play.core.review.ReviewInfo; @@ -9,23 +11,44 @@ import com.google.android.play.core.tasks.Task; public class RNReviewModule extends ReactContextBaseJavaModule { + private final ReactApplicationContext reactContext; + + public RNReviewModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + @Override public String getName() { return "RNReview"; } @ReactMethod + public void show(final Promise promise) { - ReviewManager manager = ReviewManagerFactory.create(getReactApplicationContext()); + final Activity activity = reactContext.getCurrentActivity(); + if (activity == null) { + promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist"); + return; + } + + ReviewManager manager = ReviewManagerFactory.create(reactContext); Task request = manager.requestReviewFlow(); request.addOnCompleteListener(task -> { if (task.isSuccessful()) { // We can get the ReviewInfo object ReviewInfo reviewInfo = task.getResult(); + Task flow = manager.launchReviewFlow(activity, reviewInfo); + flow.addOnCompleteListener(task1 -> { + if (task1.isSuccessful()) { + promise.resolve(null); + } else { + promise.reject("E_REVIEW_FLOW_FAILED", "Review flow failed"); + } + }); } else { - // There was some problem, continue regardless of the result. + promise.reject("E_REQUEST_REVIEW_FAILED", "Request review flow failed"); } }); } - } diff --git a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java index ff93254fab9..7317def905b 100644 --- a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java +++ b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java @@ -12,7 +12,7 @@ public class RNReviewPackage implements ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new RNReviewModule()); + return Arrays.asList(new RNReviewModule(reactContext)); } @Override diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 997b352c6e1..c8cf7251057 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -1517,7 +1517,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.7; + MARKETING_VERSION = 1.9.8; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -1580,7 +1580,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.7; + MARKETING_VERSION = 1.9.8; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -1688,7 +1688,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.7; + MARKETING_VERSION = 1.9.8; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -1797,7 +1797,7 @@ "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.7; + MARKETING_VERSION = 1.9.8; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( diff --git a/package.json b/package.json index 8caeeadadc0..e24cdf287e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Rainbow", - "version": "1.9.7-1", + "version": "1.9.8-1", "private": true, "scripts": { "setup": "yarn install && yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", diff --git a/patches/react-native+0.72.0.patch b/patches/react-native+0.72.0.patch index 95c9d705ce1..84789ee633a 100644 --- a/patches/react-native+0.72.0.patch +++ b/patches/react-native+0.72.0.patch @@ -10,6 +10,22 @@ index 26961d7..ee5943d 100644 } else if (!global.nativeExtensions) { const bridgeConfig = global.__fbBatchedBridgeConfig; invariant( +diff --git a/node_modules/react-native/React/Views/RCTView.m b/node_modules/react-native/React/Views/RCTView.m +index 619509f..a9477e5 100644 +--- a/node_modules/react-native/React/Views/RCTView.m ++++ b/node_modules/react-native/React/Views/RCTView.m +@@ -831,6 +831,11 @@ static CGFloat RCTDefaultIfNegativeTo(CGFloat defaultValue, CGFloat x) + layer.backgroundColor = backgroundColor; + layer.contents = nil; + layer.needsDisplayOnBoundsChange = NO; ++ if (@available(iOS 13.0, *)) { ++ if (layer.cornerRadius < MIN(self.bounds.size.height, self.bounds.size.width) / 2) { ++ layer.cornerCurve = kCACornerCurveContinuous; ++ } ++ } + layer.mask = nil; + return; + } diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/rncore/ComponentDescriptors.h b/node_modules/react-native/ReactCommon/react/renderer/components/rncore/ComponentDescriptors.h new file mode 100644 index 0000000..37b7d8d diff --git a/patches/react-native-pager-view+5.4.24.patch b/patches/react-native-pager-view+5.4.24.patch index d2489e125e2..749591965af 100644 --- a/patches/react-native-pager-view+5.4.24.patch +++ b/patches/react-native-pager-view+5.4.24.patch @@ -10,3 +10,16 @@ index 81070b2..2f1d2b9 100644 vp.adapter = ViewPagerAdapter() //https://github.com/callstack/react-native-viewpager/issues/183 vp.isSaveEnabled = false +diff --git a/node_modules/react-native-pager-view/ios/ReactNativePageView.m b/node_modules/react-native-pager-view/ios/ReactNativePageView.m +index 9f8ed5b..9a1f8c9 100644 +--- a/node_modules/react-native-pager-view/ios/ReactNativePageView.m ++++ b/node_modules/react-native-pager-view/ios/ReactNativePageView.m +@@ -107,7 +107,7 @@ + if([subview isKindOfClass:UIScrollView.class]){ + ((UIScrollView *)subview).delegate = self; + ((UIScrollView *)subview).keyboardDismissMode = _dismissKeyboard; +- ((UIScrollView *)subview).delaysContentTouches = YES; ++ ((UIScrollView *)subview).delaysContentTouches = NO; + self.scrollView = (UIScrollView *)subview; + } + } diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index db787f4e3da..8a4b3f47dc7 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -15,6 +15,7 @@ import RecyclerActivityList from './RecyclerActivityList'; import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { useSectionListScrollToTopContext } from '@/navigation/SectionListScrollToTopContext'; +import { safeAreaInsetValues } from '@/utils'; const sx = StyleSheet.create({ sectionHeader: { @@ -162,6 +163,9 @@ const ActivityList = ({ keyExtractor={keyExtractor} removeClippedSubviews renderSectionHeader={renderSectionHeaderWithTheme} + scrollIndicatorInsets={{ + bottom: safeAreaInsetValues.bottom + 14, + }} sections={sections} /> ); diff --git a/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx b/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx index 0969e186038..c57e0724ff6 100644 --- a/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx +++ b/src/components/animations/ButtonPressAnimation/ButtonPressAnimation.android.tsx @@ -294,7 +294,7 @@ export default function ButtonPressAnimation({ wrapperStyle, hapticType = 'selection', enableHapticFeedback = true, - disallowInterruption = true, + disallowInterruption = false, }: Props) { const normalizedTransformOrigin = useMemo( () => normalizeTransformOrigin(transformOrigin), diff --git a/src/components/asset-list/RecyclerAssetList2/index.tsx b/src/components/asset-list/RecyclerAssetList2/index.tsx index edf5f908c46..07eb44cc75e 100644 --- a/src/components/asset-list/RecyclerAssetList2/index.tsx +++ b/src/components/asset-list/RecyclerAssetList2/index.tsx @@ -70,7 +70,10 @@ function RecyclerAssetList({ briefSectionsData={briefSectionsData} disablePullDownToRefresh={!!disablePullDownToRefresh} extendedState={extendedState} - scrollIndicatorInsets={{ bottom: 40, top: 132 }} + scrollIndicatorInsets={{ + bottom: insets.bottom + 14, + top: 132, + }} type={type} /> @@ -177,13 +180,12 @@ function NavbarOverlay({ actionTitle: lang.t('button.my_qr_code'), icon: { iconType: 'SYSTEM', iconValue: 'qrcode' }, }, - mostRecentWalletConnectors.length > 0 || activeWCV2Sessions.length > 0 - ? { - actionKey: 'connectedApps', - actionTitle: lang.t('wallet.connected_apps'), - icon: { iconType: 'SYSTEM', iconValue: 'app.badge.checkmark' }, - } - : null, + + { + actionKey: 'connectedApps', + actionTitle: lang.t('wallet.connected_apps'), + icon: { iconType: 'SYSTEM', iconValue: 'app.badge.checkmark' }, + }, ].filter(Boolean), ...(ios ? { menuTitle: '' } : {}), }), diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx index b05b9daf9ca..a5af90c724b 100644 --- a/src/components/change-wallet/AddressRow.tsx +++ b/src/components/change-wallet/AddressRow.tsx @@ -13,7 +13,6 @@ import { Icon } from '../icons'; import { Centered, Column, ColumnWithMargins, Row } from '../layout'; import { Text, TruncatedText } from '../text'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import ContextMenuAndroid from '@/components/native-context-menu/contextMenu.android'; import useExperimentalFlag, { NOTIFICATIONS } from '@/config/experimentalHooks'; import { removeFirstEmojiFromString, @@ -25,6 +24,7 @@ import { abbreviations, deviceUtils, profileUtils } from '@/utils'; import { EditWalletContextMenuActions } from '@/screens/ChangeWalletSheet'; import { toChecksumAddress } from '@/handlers/web3'; import { IS_IOS, IS_ANDROID } from '@/env'; +import { ContextMenu } from '../context-menu'; const maxAccountLabelWidth = deviceUtils.dimensions.width - 88; const NOOP = () => undefined; @@ -219,6 +219,25 @@ export default function AddressRow({ menuTitle: walletName, }; + const handleSelectActionMenuItem = useCallback( + (buttonIndex: number) => { + switch (buttonIndex) { + case 0: + contextMenuActions?.edit(walletId, address); + break; + case 1: + contextMenuActions?.notifications(walletName, address); + break; + case 2: + contextMenuActions?.remove(walletId, address); + break; + default: + break; + } + }, + [contextMenuActions, walletName, walletId, address] + ); + const handleSelectMenuItem = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly ({ nativeEvent: { actionKey } }) => { @@ -316,14 +335,15 @@ export default function AddressRow({ ) : ( - // @ts-expect-error js component - item.actionTitle)} isAnchoredToRight - onPressMenuItem={handleSelectMenuItem} + onPressActionSheet={handleSelectActionMenuItem} > - - + + + + ))} diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index 4bff48b0017..79ff68abd30 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ /* eslint-disable no-undef */ import AnimateNumber from '@bankify/react-native-animate-number'; import lang from 'i18n-js'; @@ -34,6 +35,8 @@ import styled from '@/styled-thing'; import { fonts, fontWithWidth, margin, padding } from '@/styles'; import { ethereumUtils, gasUtils } from '@/utils'; import { getNetworkObj } from '@/networks'; +import { IS_ANDROID } from '@/env'; +import { ContextMenu } from '../context-menu'; const { GAS_EMOJIS, @@ -249,6 +252,7 @@ const GasSpeedButton = ({ shouldOpenCustomGasSheet.focusTo, flashbotTransaction, speeds, + fallbackColor, ]); const openCustomOptions = useCallback( @@ -377,6 +381,25 @@ const GasSpeedButton = ({ [handlePressSpeedOption] ); + const handlePressActionSheet = useCallback( + buttonIndex => { + switch (buttonIndex) { + case 0: + handlePressSpeedOption(NORMAL); + break; + case 1: + handlePressSpeedOption(FAST); + break; + case 2: + handlePressSpeedOption(URGENT); + break; + case 3: + handlePressSpeedOption(CUSTOM); + } + }, + [handlePressSpeedOption] + ); + const nativeFeeCurrency = useMemo(() => { switch (currentNetwork) { case networkTypes.polygon: @@ -399,6 +422,8 @@ const GasSpeedButton = ({ const menuConfig = useMemo(() => { const menuOptions = speedOptions.map(gasOption => { + if (IS_ANDROID) return gasOption; + const totalGwei = add( gasFeeParamsBySpeed[gasOption]?.maxBaseFee?.gwei, gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei @@ -441,6 +466,7 @@ const GasSpeedButton = ({ gasFeeParamsBySpeed, selectedGasFeeOption, speedOptions, + isL2, ]); const gasOptionsAvailable = useMemo(() => speedOptions.length > 1, [ @@ -477,6 +503,24 @@ const GasSpeedButton = ({ /> ); if (!gasOptionsAvailable || gasIsNotReady) return pager; + + if (IS_ANDROID) { + return ( + + {pager} + + ); + } + return ( diff --git a/src/components/positions/PositionsCard.tsx b/src/components/positions/PositionsCard.tsx index 5efdd314261..67ef48e329f 100644 --- a/src/components/positions/PositionsCard.tsx +++ b/src/components/positions/PositionsCard.tsx @@ -72,7 +72,7 @@ export const PositionCard = ({ position }: PositionCardProps) => { }, [navigate, position]); const depositTokens: CoinStackToken[] = useMemo(() => { - let tokens: CoinStackToken[] = []; + const tokens: CoinStackToken[] = []; position.deposits.forEach((deposit: RainbowDeposit) => { deposit.underlying.forEach(({ asset }) => { tokens.push({ diff --git a/src/languages/en_US.json b/src/languages/en_US.json index 7ee4c0ac412..ae3078e3768 100644 --- a/src/languages/en_US.json +++ b/src/languages/en_US.json @@ -2243,6 +2243,7 @@ "available_networks": "Available Networks", "change_network": "Change Network", "connected_apps": "Connected apps", + "no_connected_apps": "No Connected apps yet 😢", "disconnect": "Disconnect", "go_back_to_your_browser": "Go back to your browser", "paste_uri": { diff --git a/src/screens/ConnectedDappsSheet.js b/src/screens/ConnectedDappsSheet.js index 2e35cb71bec..41631b16856 100644 --- a/src/screens/ConnectedDappsSheet.js +++ b/src/screens/ConnectedDappsSheet.js @@ -1,5 +1,5 @@ import lang from 'i18n-js'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Divider from '@/components/Divider'; import { Row } from '@/components/layout'; @@ -12,12 +12,15 @@ import { useWalletConnectConnections } from '@/hooks'; import { useNavigation } from '@/navigation'; import styled from '@/styled-thing'; import { useWalletConnectV2Sessions } from '@/walletConnect/hooks/useWalletConnectV2Sessions'; +import { useFocusEffect } from '@react-navigation/native'; +import { Text, Box } from '@/design-system'; const MAX_VISIBLE_DAPPS = 7; const ScrollableItems = styled.ScrollView({ height: ({ length }) => - WalletConnectListItemHeight * Math.min(length, MAX_VISIBLE_DAPPS) + 20, + WalletConnectListItemHeight * Math.min(length, MAX_VISIBLE_DAPPS) + + (length === 0 ? 60 : 20), }); const SheetTitleWithPadding = styled(SheetTitle)({ @@ -31,13 +34,22 @@ export default function ConnectedDappsSheet() { const insets = useSafeAreaInsets(); const { colors } = useTheme(); const { sessions, reload } = useWalletConnectV2Sessions(); + const [focused, setFocused] = useState(false); const numOfRows = mostRecentWalletConnectors.length + sessions.length; + useFocusEffect(() => { + setTimeout(() => { + setFocused(true); + }, 2000); + }); + useEffect(() => { - if (numOfRows === 0) { + if (numOfRows === 0 && focused) { goBack(); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [goBack, numOfRows]); return ( @@ -75,6 +87,19 @@ export default function ConnectedDappsSheet() { /> ) )} + {numOfRows === 0 && ( + + + {lang.t('walletconnect.no_connected_apps')} + + + )} diff --git a/src/screens/ENSIntroSheet.tsx b/src/screens/ENSIntroSheet.tsx index 317ad38804f..e6252c7d81e 100644 --- a/src/screens/ENSIntroSheet.tsx +++ b/src/screens/ENSIntroSheet.tsx @@ -3,7 +3,7 @@ import { useRoute } from '@react-navigation/native'; import { IS_TESTING } from 'react-native-dotenv'; import lang from 'i18n-js'; import React, { useCallback, useMemo } from 'react'; -import { InteractionManager } from 'react-native'; +import { InteractionManager, View } from 'react-native'; import { MenuActionConfig } from 'react-native-ios-context-menu'; import LinearGradient from 'react-native-linear-gradient'; import ActivityIndicator from '../components/ActivityIndicator'; @@ -35,6 +35,8 @@ import { } from '@/hooks'; import Routes from '@/navigation/routesNames'; import { useTheme } from '@/theme'; +import { IS_ANDROID } from '@/env'; +import ContextMenu from '@/components/context-menu/ContextMenu.android'; enum AnotherENSEnum { search = 'search', @@ -45,6 +47,92 @@ const topPadding = android ? 29 : 19; const minHeight = 740; +type ContextMenuRendererProps = { + children: React.ReactNode; + handleSelectExistingName: () => void; + handleNavigateToSearch: () => void; +}; + +const ContextMenuRenderer = ({ + children, + handleSelectExistingName, + handleNavigateToSearch, +}: ContextMenuRendererProps) => { + const menuConfig = useMemo(() => { + return { + menuItems: [ + { + actionKey: AnotherENSEnum.my_ens, + actionTitle: lang.t('profiles.intro.my_ens_names'), + icon: { + iconType: 'SYSTEM', + iconValue: 'rectangle.stack.badge.person.crop', + }, + }, + { + actionKey: AnotherENSEnum.search, + actionTitle: lang.t('profiles.intro.search_new_ens'), + icon: { + iconType: 'SYSTEM', + iconValue: 'magnifyingglass', + }, + }, + ] as MenuActionConfig[], + menuTitle: '', + }; + }, []); + + const handlePressMenuItem = useCallback( + // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly + ({ nativeEvent: { actionKey } }) => { + if (actionKey === AnotherENSEnum.my_ens) { + handleSelectExistingName(); + } else if (actionKey === AnotherENSEnum.search) { + handleNavigateToSearch(); + } + }, + [handleNavigateToSearch, handleSelectExistingName] + ); + + const handlePressActionSheet = useCallback( + (buttonIndex: number) => { + switch (buttonIndex) { + case 0: + handleSelectExistingName(); + break; + case 1: + handleNavigateToSearch(); + break; + } + }, + [handleNavigateToSearch, handleSelectExistingName] + ); + + if (IS_ANDROID) { + return ( + i.actionTitle)} + > + {children} + + ); + } + + return ( + + {children} + + ); +}; + export default function ENSIntroSheet() { const { width: deviceWidth, height: deviceHeight } = useDimensions(); const { colors } = useTheme(); @@ -64,6 +152,8 @@ export default function ENSIntroSheet() { enabled: Boolean(uniqueDomain?.name), }); + console.log(nonPrimaryDomains.length); + // We are not using `isSmallPhone` from `useDimensions` here as we // want to explicitly set a min height. const isSmallPhone = deviceHeight < minHeight; @@ -121,42 +211,6 @@ export default function ENSIntroSheet() { }); }, [navigate, navigateToAssignRecords]); - const menuConfig = useMemo(() => { - return { - menuItems: [ - { - actionKey: AnotherENSEnum.my_ens, - actionTitle: lang.t('profiles.intro.my_ens_names'), - icon: { - iconType: 'SYSTEM', - iconValue: 'rectangle.stack.badge.person.crop', - }, - }, - { - actionKey: AnotherENSEnum.search, - actionTitle: lang.t('profiles.intro.search_new_ens'), - icon: { - iconType: 'SYSTEM', - iconValue: 'magnifyingglass', - }, - }, - ] as MenuActionConfig[], - menuTitle: '', - }; - }, []); - - const handlePressMenuItem = useCallback( - // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly - ({ nativeEvent: { actionKey } }) => { - if (actionKey === AnotherENSEnum.my_ens) { - handleSelectExistingName(); - } else if (actionKey === AnotherENSEnum.search) { - handleNavigateToSearch(); - } - }, - [handleNavigateToSearch, handleSelectExistingName] - ); - return ( )} {nonPrimaryDomains?.length > 0 ? ( - - + ) : ( { + if (IS_TESTING === 'true') return; + progress.value = 0; progress.value = withDelay( 500, diff --git a/src/screens/WalletConnectApprovalSheet.js b/src/screens/WalletConnectApprovalSheet.js index 478e482d91b..e1d4d25d74e 100644 --- a/src/screens/WalletConnectApprovalSheet.js +++ b/src/screens/WalletConnectApprovalSheet.js @@ -48,8 +48,6 @@ import * as lang from '@/languages'; import { ETH_ADDRESS, ETH_SYMBOL } from '@/references'; import { AssetType } from '@/entities'; import { RainbowNetworks, getNetworkObj } from '@/networks'; -import { handleReviewPromptAction } from '@/utils/reviewAlert'; -import { ReviewPromptAction } from '@/storage/schema'; import { IS_IOS } from '@/env'; const LoadingSpinner = styled(android ? Spinner : ActivityIndicator).attrs( @@ -362,10 +360,6 @@ export default function WalletConnectApprovalSheet() { }); } handleSuccess(true); - - setTimeout(() => { - handleReviewPromptAction(ReviewPromptAction.DappConnections); - }, 500); }, [handleSuccess, goBack, navigate]); const handleCancel = useCallback(() => { diff --git a/src/screens/discover/DiscoverScreen.tsx b/src/screens/discover/DiscoverScreen.tsx index 7db86f7d89a..e7066335203 100644 --- a/src/screens/discover/DiscoverScreen.tsx +++ b/src/screens/discover/DiscoverScreen.tsx @@ -103,7 +103,7 @@ export default function DiscoverScreen() { scrollEnabled={!isSearchModeEnabled} bounces={!isSearchModeEnabled} removeClippedSubviews - scrollIndicatorInsets={{ bottom: safeAreaInsetValues.bottom + 197 }} + scrollIndicatorInsets={{ bottom: safeAreaInsetValues.bottom + 167 }} testID="discover-sheet" > diff --git a/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx b/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx index e789b9c48f7..4ac68935326 100644 --- a/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx +++ b/src/screens/transaction-details/components/TransactionDetailsAddressRow.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { View } from 'react-native'; import { addressHashedColorIndex, addressHashedEmoji, @@ -27,89 +28,30 @@ import { import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Navigation } from '@/navigation'; import Routes from '@rainbow-me/routes'; -import { IS_IOS } from '@/env'; +import { IS_ANDROID, IS_IOS } from '@/env'; import { isENSAddressFormat } from '@/helpers/validators'; import * as i18n from '@/languages'; +import ContextMenu from '@/components/context-menu/ContextMenu.android'; -type Props = { - address: string; - title: string; - onAddressCopied?: () => void; - contact?: Contact; - account?: RainbowAccount; -}; +type ContextMenuRendererProps = { + children: React.ReactNode; + name: string | undefined; + color: number | null; + formattedAddress: string | undefined; + fetchedEnsName: string | undefined; +} & Omit; -export const TransactionDetailsAddressRow: React.FC = ({ +const ContextMenuRenderer = ({ + children, + account, address, - title, - onAddressCopied, contact, - account, -}) => { - const formattedAddress = formatAddressForDisplay(address); - const [fetchedEnsName, setFetchedEnsName] = useState(); - const [fetchedEnsImage, setFetchedEnsImage] = useState(); - const [imageLoaded, setImageLoaded] = useState(!!account?.image); - const ensNameSharedValue = useTiming(!!fetchedEnsName, { - duration: 420, - easing: Easing.linear, - }); - - const accountEmoji = useMemo(() => returnStringFirstEmoji(account?.label), [ - account, - ]); - const accountName = useMemo( - () => removeFirstEmojiFromString(account?.label), - [] - ); - const color = - account?.color ?? contact?.color ?? addressHashedColorIndex(address); - const emoji = accountEmoji || addressHashedEmoji(address); - const name = - accountName || contact?.nickname || contact?.ens || formattedAddress; - - const imageUrl = fetchedEnsImage ?? account?.image; - const ensAvatarSharedValue = useTiming(!!imageUrl && imageLoaded, { - duration: account?.image ? 0 : 420, - }); - - useEffect(() => { - if (!contact?.nickname && !accountName) { - fetchReverseRecord(address).then(name => { - if (name) { - setFetchedEnsName(name); - } - }); - } - }, []); - - useEffect(() => { - if (!account?.image && (fetchedEnsName || contact?.ens)) { - const ens = fetchedEnsName ?? contact?.ens; - if (ens) { - fetchENSAvatar(ens, { cacheFirst: true }).then(avatar => { - if (avatar?.imageUrl) { - setFetchedEnsImage(avatar.imageUrl); - } - }); - } - } - }, [fetchedEnsName]); - - const addressAnimatedStyle = useAnimatedStyle(() => ({ - opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [1, 0, 0]), - })); - const ensNameAnimatedStyle = useAnimatedStyle(() => ({ - opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [0, 0, 1]), - })); - - const emojiAvatarAnimatedStyle = useAnimatedStyle(() => ({ - opacity: interpolate(ensAvatarSharedValue.value, [0, 1], [1, 0]), - })); - const ensAvatarAnimatedStyle = useAnimatedStyle(() => ({ - opacity: ensAvatarSharedValue.value, - })); - + name, + color, + formattedAddress, + fetchedEnsName, + onAddressCopied, +}: ContextMenuRendererProps) => { const menuConfig = useMemo( () => ({ menuTitle: '', @@ -151,7 +93,7 @@ export const TransactionDetailsAddressRow: React.FC = ({ }, ], }), - [fetchedEnsName, address, contact, account] + [contact, account, formattedAddress, name] ); const onPressMenuItem = useCallback( @@ -193,18 +135,157 @@ export const TransactionDetailsAddressRow: React.FC = ({ return; } }, - [address, fetchedEnsName, contact] + [address, fetchedEnsName, contact, color, onAddressCopied] ); - const onImageLoad = () => { - setImageLoaded(true); - }; + const onPressActionSheetItem = useCallback( + (buttonIndex: number) => { + switch (buttonIndex) { + case 0: + Navigation.handleAction(Routes.SEND_FLOW, { + params: { + address, + }, + screen: Routes.SEND_SHEET, + }); + return; + case 1: + Navigation.handleAction(Routes.MODAL_SCREEN, { + address: address, + color: color, + contact, + ens: fetchedEnsName || contact?.ens, + type: 'contact_profile', + }); + return; + case 2: + onAddressCopied?.(); + haptics.notificationSuccess(); + Clipboard.setString(address); + return; + } + }, + [address, color, contact, fetchedEnsName, onAddressCopied] + ); + + if (IS_ANDROID) { + return ( + i.actionTitle)} + > + {children} + + ); + } return ( + {children} + + ); +}; + +type Props = { + address: string; + title: string; + onAddressCopied?: () => void; + contact?: Contact; + account?: RainbowAccount; +}; + +export const TransactionDetailsAddressRow: React.FC = ({ + address, + title, + onAddressCopied, + contact, + account, +}) => { + const formattedAddress = formatAddressForDisplay(address); + const [fetchedEnsName, setFetchedEnsName] = useState(); + const [fetchedEnsImage, setFetchedEnsImage] = useState(); + const [imageLoaded, setImageLoaded] = useState(!!account?.image); + const ensNameSharedValue = useTiming(!!fetchedEnsName, { + duration: 420, + easing: Easing.linear, + }); + + const accountEmoji = useMemo(() => returnStringFirstEmoji(account?.label), [ + account, + ]); + const accountName = useMemo( + () => removeFirstEmojiFromString(account?.label), + [] + ); + const color = + account?.color ?? contact?.color ?? addressHashedColorIndex(address); + const emoji = accountEmoji || addressHashedEmoji(address); + const name = + accountName || contact?.nickname || contact?.ens || formattedAddress; + + const imageUrl = fetchedEnsImage ?? account?.image; + const ensAvatarSharedValue = useTiming(!!imageUrl && imageLoaded, { + duration: account?.image ? 0 : 420, + }); + + useEffect(() => { + if (!contact?.nickname && !accountName) { + fetchReverseRecord(address).then(name => { + if (name) { + setFetchedEnsName(name); + } + }); + } + }, []); + + useEffect(() => { + if (!account?.image && (fetchedEnsName || contact?.ens)) { + const ens = fetchedEnsName ?? contact?.ens; + if (ens) { + fetchENSAvatar(ens, { cacheFirst: true }).then(avatar => { + if (avatar?.imageUrl) { + setFetchedEnsImage(avatar.imageUrl); + } + }); + } + } + }, [fetchedEnsName]); + + const addressAnimatedStyle = useAnimatedStyle(() => ({ + opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [1, 0, 0]), + })); + const ensNameAnimatedStyle = useAnimatedStyle(() => ({ + opacity: interpolate(ensNameSharedValue.value, [0, 0.5, 1], [0, 0, 1]), + })); + + const emojiAvatarAnimatedStyle = useAnimatedStyle(() => ({ + opacity: interpolate(ensAvatarSharedValue.value, [0, 1], [1, 0]), + })); + const ensAvatarAnimatedStyle = useAnimatedStyle(() => ({ + opacity: ensAvatarSharedValue.value, + })); + + const onImageLoad = () => { + setImageLoaded(true); + }; + + return ( + @@ -263,6 +344,6 @@ export const TransactionDetailsAddressRow: React.FC = ({ - + ); }; diff --git a/src/utils/reviewAlert.ts b/src/utils/reviewAlert.ts index 2aab922e9a6..5c7dc3e0cc4 100644 --- a/src/utils/reviewAlert.ts +++ b/src/utils/reviewAlert.ts @@ -5,6 +5,7 @@ import { WrappedAlert as Alert } from '@/helpers/alert'; import { ReviewPromptAction } from '@/storage/schema'; import { IS_IOS } from '@/env'; import { logger } from '@/logger'; +import { IS_TESTING } from 'react-native-dotenv'; const { RainbowRequestReview, RNReview } = NativeModules; @@ -32,6 +33,10 @@ export const numberOfTimesBeforePrompt: { export const handleReviewPromptAction = async (action: ReviewPromptAction) => { logger.info(`handleReviewPromptAction: ${action}`); + if (IS_TESTING === 'true') { + return; + } + if (action === ReviewPromptAction.UserPrompt) { promptForReview(); return; diff --git a/src/walletConnect/index.tsx b/src/walletConnect/index.tsx index c81358602d2..20ab6be155f 100644 --- a/src/walletConnect/index.tsx +++ b/src/walletConnect/index.tsx @@ -53,6 +53,8 @@ import { AuthRequest } from '@/walletConnect/sheets/AuthRequest'; import { getProviderForNetwork } from '@/handlers/web3'; import { RainbowNetworks } from '@/networks'; import { uniq } from 'lodash'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; +import { ReviewPromptAction } from '@/storage/schema'; const SUPPORTED_EVM_CHAIN_IDS = RainbowNetworks.filter( ({ features }) => features.walletconnect @@ -784,6 +786,10 @@ export async function onSessionRequest( }); } + InteractionManager.runAfterInteractions(() => { + handleReviewPromptAction(ReviewPromptAction.DappConnections); + }); + maybeGoBackAndClearHasPendingRedirect({ delay: 300 }); }, },