From 06500452fcb36e14ab83005fe3584218849a61e8 Mon Sep 17 00:00:00 2001 From: dominhquang Date: Tue, 25 Jul 2023 18:41:10 +0700 Subject: [PATCH] [Issue-281]: Implement Wallet Connect --- .../app/subwallet/mobile/MainApplication.java | 2 + .../mobile/nativeModules/RCTMinimizer.java | 27 + .../nativeModules/RCTMinimizerPackage.java | 29 ++ ios/Podfile.lock | 6 +- ios/SubWalletMobile.xcodeproj/project.pbxproj | 22 + .../NativeModules/RCTMinimizer/RCTMinimizer.h | 15 + .../NativeModules/RCTMinimizer/RCTMinimizer.m | 30 ++ package.json | 1 + src/AppNavigator.tsx | 64 ++- src/AppNew.tsx | 96 ++-- src/NativeModules.ts | 7 + src/components/ContainerWithSubHeader.tsx | 40 +- src/components/FlatListScreen.tsx | 13 +- src/components/Header.tsx | 2 - src/components/Input/InputAddressV2.tsx | 1 + src/components/Input/InputCheckBox.tsx | 17 +- src/components/Input/InputConnectUrl.tsx | 52 +- .../Modal/AddressBook/AddContactModal.tsx | 22 +- .../Modal/AddressBook/AddressBookModal.tsx | 21 +- .../Modal/AddressBook/EditContactModal.tsx | 16 +- src/components/Modal/Base/ModalBase.tsx | 6 +- src/components/Modal/ConnectWebsiteModal.tsx | 23 +- src/components/Modal/PasswordModal.tsx | 24 +- .../Modal/SelectAccountTypeModal.tsx | 1 + src/components/Modal/common/PoolSelector.tsx | 5 +- .../Modal/common/PoolSelectorDetailModal.tsx | 16 +- .../Modal/common/ValidatorSelector.tsx | 5 +- .../common/ValidatorSelectorDetailModal.tsx | 17 +- .../Modal/style/ConnectWebsiteModal.ts | 6 +- src/components/ScreenContainer.tsx | 2 +- .../Signing/Password/PasswordRequest.tsx | 1 + .../WalletConnect/Account/WCAccountInput.tsx | 6 +- .../WalletConnect/Account/WCAccountSelect.tsx | 37 +- .../Network/WCNetworkAvatarGroup.tsx | 10 +- .../WalletConnect/Network/WCNetworkItem.tsx | 4 +- .../Network/WCNetworkSelected.tsx | 2 + .../Network/WCNetworkSupported.tsx | 11 +- .../Account/AccountCreationArea/index.tsx | 4 +- .../ConfirmationContent/index.tsx | 4 +- .../ConfirmationContent/styles/index.ts | 2 +- .../ConfirmationGeneralInfo/index.tsx | 11 +- src/components/common/FilterModal/index.tsx | 1 + .../common/ForgotPasswordModal/index.tsx | 2 + .../common/Modal/ConfirmModal/index.tsx | 31 +- .../common/Modal/ConfirmModal/styles/index.ts | 3 +- .../common/Modal/DeleteModal/index.tsx | 21 +- .../common/Modal/DeleteModal/styles/index.ts | 7 +- .../common/Modal/DeriveAccountModal/index.tsx | 12 +- .../common/Modal/UnlockModal/index.tsx | 115 ++--- .../common/Modal/UnlockModal/style/index.ts | 35 +- .../common/RpcSelectorModal/index.tsx | 4 +- .../common/SelectAccountType/index.tsx | 30 +- .../common/SelectExportType/index.tsx | 52 +- .../common/SelectModal/BasicSelectModal.tsx | 50 +- src/components/common/SelectModal/index.tsx | 96 ++-- .../design-system-ui/modal/ModalBase.tsx | 8 +- .../design-system-ui/modal/ModalBaseV2.tsx | 126 +++++ .../modal/SwFullSizeModal.tsx | 60 ++- .../design-system-ui/modal/SwModal.tsx | 145 ++++-- .../design-system-ui/modal/styleV2/index.ts | 61 +++ .../select-item/styles/index.ts | 1 - src/hooks/modal/useConfirmModal.ts | 4 +- src/hooks/modal/useUnlockModal.ts | 32 +- src/hooks/screen/Home/Crypto/useReceiveQR.ts | 34 +- src/routes/index.ts | 4 +- src/routes/wrapper.ts | 1 + src/screens/Account/AccountDetail/index.tsx | 11 +- src/screens/Account/AccountExport/index.tsx | 2 +- src/screens/Account/AccountsScreen/index.tsx | 1 + src/screens/Account/AttachReadOnly/index.tsx | 4 +- src/screens/Account/ConnectQrSigner/index.tsx | 9 +- .../CreateAccount/VerifySecretPhrase.tsx | 9 +- src/screens/Account/CreateAccount/index.tsx | 2 +- .../Account/ImportPrivateKey/index.tsx | 4 +- src/screens/Account/ImportQrCode/index.tsx | 9 +- .../Account/ImportSecretPhrase/index.tsx | 4 +- src/screens/Account/RestoreJson/index.tsx | 12 +- .../Confirmations/parts/Detail/Base.tsx | 2 + src/screens/Confirmations/parts/Sign/Evm.tsx | 14 +- .../Confirmations/parts/Sign/Substrate.tsx | 8 +- .../variants/AuthorizeConfirmation/index.tsx | 13 +- .../index.tsx | 42 +- .../Home/Browser/BrowserOptionModal.tsx | 16 +- src/screens/Home/Browser/BrowserTab.tsx | 17 +- .../Home/Browser/BrowserTabsManager.tsx | 6 +- src/screens/Home/Crowdloans/index.tsx | 2 - src/screens/Home/Crypto/BuyToken.tsx | 2 - .../Home/Crypto/CustomizationModal.tsx | 2 - src/screens/Home/Crypto/ReceiveModal.tsx | 15 +- src/screens/Home/Crypto/TokenDetailModal.tsx | 15 +- src/screens/Home/Crypto/TokenGroups.tsx | 4 +- src/screens/Home/Crypto/TokenGroupsDetail.tsx | 11 +- src/screens/Home/History/Detail/index.tsx | 23 +- src/screens/Home/History/index.tsx | 12 +- .../Home/NFT/Collection/NftCollectionList.tsx | 2 - src/screens/Home/NFT/Detail/NftDetail.tsx | 2 - src/screens/Home/NFT/Item/NftItemList.tsx | 5 +- .../Staking/Balance/StakingBalanceList.tsx | 17 +- .../StakingDetail/StakingActionModal.tsx | 42 +- .../StakingDetailModal/index.tsx | 472 +++++++++--------- src/screens/Home/index.tsx | 63 ++- src/screens/LockScreen.tsx | 122 +++-- .../ApplyMasterPassword/index.tsx | 10 +- .../index.tsx | 2 +- src/screens/NetworkSettingDetail.tsx | 2 + src/screens/NetworksSetting.tsx | 1 + src/screens/Settings/AddressBook/index.tsx | 16 +- src/screens/Settings/General/index.tsx | 2 +- src/screens/Settings/Languages.tsx | 2 +- src/screens/Settings/NetworkConfig.tsx | 1 + .../DAppAccess/DAppAccessDetailScreen.tsx | 3 +- .../Security/DAppAccess/MoreOptionModal.tsx | 14 +- .../Settings/Security/DAppAccess/index.tsx | 3 +- src/screens/Settings/Security/index.tsx | 121 ++--- .../WalletConnect/ConnectWalletConnect.tsx | 10 +- .../WalletConnect/ConnectionDetail.tsx | 26 +- .../Settings/WalletConnect/ConnectionList.tsx | 108 +++- src/screens/Settings/index.tsx | 110 ++-- src/screens/Tokens/ConfigureToken.tsx | 2 + src/screens/Tokens/index.tsx | 1 + src/screens/Transaction/NFT/index.tsx | 2 - .../Transaction/Stake/NetworkDetailModal.tsx | 13 +- src/screens/Transaction/Stake/index.tsx | 2 +- src/stores/base/Settings.ts | 11 +- src/stores/types.ts | 1 + src/styles/sharedStyles.ts | 2 +- src/utils/deeplink/index.ts | 7 + src/utils/i18n/en_US.ts | 2 + src/utils/i18n/vi_VN.ts | 2 + src/utils/i18n/zh_CN.ts | 2 + src/utils/permission/camera.ts | 3 +- src/utils/walletConnect/index.ts | 21 +- yarn.lock | 9 +- 133 files changed, 1986 insertions(+), 1136 deletions(-) create mode 100644 android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizer.java create mode 100644 android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizerPackage.java create mode 100644 ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.h create mode 100644 ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.m create mode 100644 src/NativeModules.ts create mode 100644 src/components/design-system-ui/modal/ModalBaseV2.tsx create mode 100644 src/components/design-system-ui/modal/styleV2/index.ts diff --git a/android/app/src/main/java/app/subwallet/mobile/MainApplication.java b/android/app/src/main/java/app/subwallet/mobile/MainApplication.java index 0fed2eeac..fcff7ae2d 100644 --- a/android/app/src/main/java/app/subwallet/mobile/MainApplication.java +++ b/android/app/src/main/java/app/subwallet/mobile/MainApplication.java @@ -9,6 +9,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; import com.facebook.react.defaults.DefaultReactNativeHost; import com.facebook.soloader.SoLoader; +import app.subwallet.mobile.nativeModules.RCTMinimizerPackage; import java.util.List; public class MainApplication extends Application implements ReactApplication { @@ -26,6 +27,7 @@ protected List getPackages() { List packages = new PackageList(this).getPackages(); // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); + packages.add(new RCTMinimizerPackage()); return packages; } diff --git a/android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizer.java b/android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizer.java new file mode 100644 index 000000000..8d4e4fe15 --- /dev/null +++ b/android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizer.java @@ -0,0 +1,27 @@ +package app.subwallet.mobile.nativeModules; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import android.util.Log; + +public class RCTMinimizer extends ReactContextBaseJavaModule { + private ReactApplicationContext reactContext; + RCTMinimizer(ReactApplicationContext context) { + super(context); + this.reactContext = reactContext; + } + + @Override + public String getName() { + return "Minimizer"; + } + + @ReactMethod + public void goBack() { + android.app.Activity activity = getCurrentActivity(); + if (activity != null) { + activity.moveTaskToBack(true); + } + } +} diff --git a/android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizerPackage.java b/android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizerPackage.java new file mode 100644 index 000000000..aeaf89009 --- /dev/null +++ b/android/app/src/main/java/app/subwallet/mobile/nativeModules/RCTMinimizerPackage.java @@ -0,0 +1,29 @@ +package app.subwallet.mobile.nativeModules; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RCTMinimizerPackage implements ReactPackage { + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new RCTMinimizer(reactContext)); + + return modules; + } + +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2ced1911d..187c9ad76 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -824,10 +824,10 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: a7c83b31436843459a1961bfd74b96033dc77234 + boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 612a04ff38e8480291f3379ee5b5a2c571f03fe0 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 - DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662 + DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 4eb7ee83e8d0ad7e20a829485295ff48823c4e4c FBReactNativeSpec: b24809f97ae83c786928d56b732957195b4fa390 Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 @@ -841,7 +841,7 @@ SPEC CHECKSUMS: FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4 - glog: 476ee3e89abb49e07f822b48323c51c57124b572 + glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b hermes-engine: b60ebc812e0179a612d8146ac54730d533c804a2 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef diff --git a/ios/SubWalletMobile.xcodeproj/project.pbxproj b/ios/SubWalletMobile.xcodeproj/project.pbxproj index 6c4277578..f24bef64e 100644 --- a/ios/SubWalletMobile.xcodeproj/project.pbxproj +++ b/ios/SubWalletMobile.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 0C80B921A6F3F58F76C31292 /* libPods-SubWalletMobile.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-SubWalletMobile.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 355FCA172A72335100B34579 /* RCTMinimizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 355FCA152A72335100B34579 /* RCTMinimizer.m */; }; 3577831F29CEA0080036830D /* fonts in Resources */ = {isa = PBXBuildFile; fileRef = 3577831E29CEA0080036830D /* fonts */; }; 7699B88040F8A987B510C191 /* libPods-SubWalletMobile-SubWalletMobileTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-SubWalletMobile-SubWalletMobileTests.a */; }; 81AB9BB82411601600AC10FF /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* SplashScreen.storyboard */; }; @@ -40,6 +41,8 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SubWalletMobile/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = SubWalletMobile/main.m; sourceTree = ""; }; 19F6CBCC0A4E27FBF8BF4A61 /* libPods-SubWalletMobile-SubWalletMobileTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SubWalletMobile-SubWalletMobileTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 355FCA152A72335100B34579 /* RCTMinimizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RCTMinimizer.m; path = SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.m; sourceTree = ""; }; + 355FCA162A72335100B34579 /* RCTMinimizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RCTMinimizer.h; path = SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.h; sourceTree = ""; }; 3577831E29CEA0080036830D /* fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fonts; sourceTree = ""; }; 3B4392A12AC88292D35C810B /* Pods-SubWalletMobile.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SubWalletMobile.debug.xcconfig"; path = "Target Support Files/Pods-SubWalletMobile/Pods-SubWalletMobile.debug.xcconfig"; sourceTree = ""; }; 5709B34CF0A7D63546082F79 /* Pods-SubWalletMobile.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SubWalletMobile.release.xcconfig"; path = "Target Support Files/Pods-SubWalletMobile/Pods-SubWalletMobile.release.xcconfig"; sourceTree = ""; }; @@ -94,6 +97,7 @@ 13B07FAE1A68108700A75B9A /* SubWalletMobile */ = { isa = PBXGroup; children = ( + 355FCA132A7232F000B34579 /* NativeModules */, 990CC4AB2A33104500AB6D8B /* SubWalletMobile.entitlements */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.mm */, @@ -115,6 +119,23 @@ name = Frameworks; sourceTree = ""; }; + 355FCA132A7232F000B34579 /* NativeModules */ = { + isa = PBXGroup; + children = ( + 355FCA142A72333200B34579 /* RCTMinimizer */, + ); + name = NativeModules; + sourceTree = ""; + }; + 355FCA142A72333200B34579 /* RCTMinimizer */ = { + isa = PBXGroup; + children = ( + 355FCA162A72335100B34579 /* RCTMinimizer.h */, + 355FCA152A72335100B34579 /* RCTMinimizer.m */, + ); + name = RCTMinimizer; + sourceTree = ""; + }; 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( @@ -437,6 +458,7 @@ buildActionMask = 2147483647; files = ( 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, + 355FCA172A72335100B34579 /* RCTMinimizer.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.h b/ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.h new file mode 100644 index 000000000..f47efe946 --- /dev/null +++ b/ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.h @@ -0,0 +1,15 @@ +// +// RCTMinimizer.h +// SubWalletMobile +// +// Created by Do Minh Quang on 27/07/2023. +// + +#ifndef RCTMinimizer_h +#define RCTMinimizer_h + +#import +@interface RCTMinimizer : NSObject +@end + +#endif /* RCTMinimizer_h */ diff --git a/ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.m b/ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.m new file mode 100644 index 000000000..32e541166 --- /dev/null +++ b/ios/SubWalletMobile/NativeModules/RCTMinimizer/RCTMinimizer.m @@ -0,0 +1,30 @@ +#import +#import "RCTMinimizer.h" + +@import UIKit; +@import ObjectiveC.runtime; + +@interface UISystemNavigationAction : NSObject +@property(nonatomic, readonly, nonnull) NSArray* destinations; +-(BOOL)sendResponseForDestination:(NSUInteger)destination; +@end + + +@implementation RCTMinimizer + +RCT_EXPORT_METHOD(goBack) +{ + Ivar sysNavIvar = class_getInstanceVariable(UIApplication.class, "_systemNavigationAction"); + UIApplication* app = UIApplication.sharedApplication; + UISystemNavigationAction* action = object_getIvar(app, sysNavIvar); + if (!action) { + return; + } + NSUInteger destination = action.destinations.firstObject.unsignedIntegerValue; + [action sendResponseForDestination:destination]; + return; +} + +RCT_EXPORT_MODULE(); + +@end diff --git a/package.json b/package.json index 7281c576b..34c45578c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@fortawesome/free-regular-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/react-native-fontawesome": "^0.3.0", + "@gorhom/portal": "^1.0.14", "@polkadot/api": "^10.9.1", "@polkadot/react-qr": "^3.5.1", "@polkadot/reactnative-identicon": "^3.5.1", diff --git a/src/AppNavigator.tsx b/src/AppNavigator.tsx index 924b61594..407357608 100644 --- a/src/AppNavigator.tsx +++ b/src/AppNavigator.tsx @@ -1,12 +1,11 @@ import { NavigationState } from '@react-navigation/routers'; import { ALL_ACCOUNT_KEY } from '@subwallet/extension-base/constants'; -import React, { ComponentType, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { ComponentType, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { LinkingOptions, NavigationContainer, useNavigationContainerRef } from '@react-navigation/native'; import AttachReadOnly from 'screens/Account/AttachReadOnly'; import ConnectKeystone from 'screens/Account/ConnectQrSigner/ConnectKeystone'; import ConnectParitySigner from 'screens/Account/ConnectQrSigner/ConnectParitySigner'; import ImportQrCode from 'screens/Account/ImportQrCode'; -import Login from 'screens/MasterPassword/Login'; import { NetworksSetting } from 'screens/NetworksSetting'; import { GeneralSettings } from 'screens/Settings/General'; import { SendFund } from 'screens/Transaction/SendFund'; @@ -38,7 +37,7 @@ import { LoadingScreen } from 'screens/LoadingScreen'; import { RootRouteProps, RootStackParamList } from './routes'; import { THEME_PRESET } from 'styles/themes'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { deeplinks, getProtocol, getValidURL } from 'utils/browser'; +import { deeplinks, getValidURL } from 'utils/browser'; import ErrorBoundary from 'react-native-error-boundary'; import ApplyMasterPassword from 'screens/MasterPassword/ApplyMasterPassword'; import { NetworkSettingDetail } from 'screens/NetworkSettingDetail'; @@ -55,7 +54,7 @@ import { AddProvider } from 'screens/AddProvider'; import TransactionScreen from 'screens/Transaction/TransactionScreen'; import SendNFT from 'screens/Transaction/NFT'; import changeNavigationBarColor from 'react-native-navigation-bar-color'; -import { Linking, Platform } from 'react-native'; +import { Platform, StatusBar } from 'react-native'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { Home } from 'screens/Home'; import { deviceWidth } from 'constants/index'; @@ -68,10 +67,11 @@ import useCheckEmptyAccounts from 'hooks/useCheckEmptyAccounts'; import { ConnectionList } from 'screens/Settings/WalletConnect/ConnectionList'; import { ConnectWalletConnect } from 'screens/Settings/WalletConnect/ConnectWalletConnect'; import { ConnectionDetail } from 'screens/Settings/WalletConnect/ConnectionDetail'; -import urlParse from 'url-parse'; -import queryString from 'querystring'; -import { connectWalletConnect } from 'utils/walletConnect'; -import { useToast } from 'react-native-toast-notifications'; +import useAppLock from 'hooks/useAppLock'; +import { LockScreen } from 'screens/LockScreen'; +import { STATUS_BAR_LIGHT_CONTENT } from 'styles/sharedStyles'; +import { UnlockModal } from 'components/common/Modal/UnlockModal'; +import { AppModalContext } from 'providers/AppModalContext'; interface Props { isAppReady: boolean; @@ -142,7 +142,8 @@ const AppNavigator = ({ isAppReady }: Props) => { const isEmptyAccounts = useCheckEmptyAccounts(); const { hasConfirmations } = useSelector((state: RootState) => state.requestState); const { accounts, hasMasterPassword } = useSelector((state: RootState) => state.accountState); - const toast = useToast(); + const { isLocked } = useAppLock(); + const appModalContext = useContext(AppModalContext); const needMigrate = useMemo( () => @@ -168,8 +169,9 @@ const AppNavigator = ({ isAppReady }: Props) => { let amount = true; if (hasConfirmations && currentRoute && amount) { if (currentRoute.name !== 'Confirmations' && amount) { - if (currentRoute.name !== 'CreateAccount' && amount) { - navigationRef.current?.navigate('Confirmations'); + if (!['CreateAccount', 'CreatePassword', 'Login', 'UnlockModal'].includes(currentRoute.name) && amount) { + appModalContext.hideConfirmModal(); + setTimeout(() => navigationRef.current?.navigate('Confirmations'), 1000); } } } @@ -177,6 +179,7 @@ const AppNavigator = ({ isAppReady }: Props) => { return () => { amount = false; }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasConfirmations, navigationRef, currentRoute]); useEffect(() => { @@ -191,6 +194,20 @@ const AppNavigator = ({ isAppReady }: Props) => { }; }, [currentRoute, hasMasterPassword, navigationRef, needMigrate]); + useEffect(() => { + let amount = true; + if (isLocked && currentRoute && amount) { + if (currentRoute.name !== 'Login' && amount) { + appModalContext.hideConfirmModal(); + setTimeout(() => navigationRef.current?.navigate('Login'), 500); + } + } + return () => { + amount = false; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentRoute, hasMasterPassword, isLocked, navigationRef, needMigrate]); + useEffect(() => { if (isEmptyAccounts) { navigationRef.current?.reset({ @@ -200,29 +217,9 @@ const AppNavigator = ({ isAppReady }: Props) => { } }, [isEmptyAccounts, navigationRef]); - useEffect(() => { - Linking.addEventListener('url', ({ url }) => { - const urlParsed = new urlParse(url); - if (getProtocol(url) === 'subwallet') { - if (urlParsed.hostname === 'wc') { - const decodedWcUrl = queryString.decode(urlParsed.query.slice(5)); - const finalWcUrl = Object.keys(decodedWcUrl)[0]; - connectWalletConnect(finalWcUrl, toast); - } - } else if (getProtocol(url) === 'https') { - if (urlParsed.pathname.split('/')[1] === 'wc') { - const decodedWcUrl = queryString.decode(urlParsed.query.slice(5)); - const finalWcUrl = Object.keys(decodedWcUrl)[0]; - connectWalletConnect(finalWcUrl, toast); - } - } - }); - - return () => Linking.removeAllListeners('url'); - }, [toast]); - return ( + { component={Confirmations} options={{ gestureEnabled: false, animationDuration: 100 }} /> - + + {} )} diff --git a/src/AppNew.tsx b/src/AppNew.tsx index ac54cf639..e6aabc4da 100644 --- a/src/AppNew.tsx +++ b/src/AppNew.tsx @@ -5,7 +5,7 @@ import { QrSignerContextProvider } from 'providers/QrSignerContext'; import { ScannerContextProvider } from 'providers/ScannerContext'; import { SigningContextProvider } from 'providers/SigningContext'; import React, { useEffect } from 'react'; -import { AppState, Platform, StatusBar, StyleProp, View } from 'react-native'; +import { AppState, Dimensions, Platform, StatusBar, StyleProp, View } from 'react-native'; import { ThemeContext } from 'providers/contexts'; import { THEME_PRESET } from 'styles/themes'; import { ToastProvider } from 'react-native-toast-notifications'; @@ -16,7 +16,6 @@ import useAppLock from 'hooks/useAppLock'; import useCryptoReady from 'hooks/init/useCryptoReady'; import useSetupI18n from 'hooks/init/useSetupI18n'; import SplashScreen from 'react-native-splash-screen'; -import { LockScreen } from 'screens/LockScreen'; import { LoadingScreen } from 'screens/LoadingScreen'; import { ColorMap } from 'styles/color'; import { AutoLockState } from 'utils/autoLock'; @@ -30,11 +29,9 @@ import { setBuildNumber } from './stores/AppVersion'; import { getBuildNumber } from 'react-native-device-info'; import { AppModalContextProvider } from './providers/AppModalContext'; import { CustomToast } from 'components/design-system-ui/toast'; - -const viewContainerStyle: StyleProp = { - position: 'relative', - flex: 1, -}; +import { PortalProvider } from '@gorhom/portal'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; const layerScreenStyle: StyleProp = { top: 0, @@ -109,7 +106,7 @@ export const AppNew = () => { ); const { hasMasterPassword } = useSelector((state: RootState) => state.accountState); const { buildNumber } = useSelector((state: RootState) => state.appVersion); - const { isLocked, lock } = useAppLock(); + const { lock } = useAppLock(); const dispatch = useDispatch(); const isCryptoReady = useCryptoReady(); @@ -156,44 +153,53 @@ export const AppNew = () => { const isAppReady = isRequiredStoresReady && isCryptoReady && isI18nReady; return ( - - - } - placement="top" - normalColor={theme.colors.notification} - textStyle={{ textAlign: 'center', ...FontMedium }} - successColor={theme.colors.primary} - warningColor={theme.colors.notification_warning} - offsetTop={STATUS_BAR_HEIGHT + 40} - dangerColor={theme.colors.notification_danger}> - - - - - - - - - - - - - - - - {!isAppReady && ( - - - - )} - {isLocked && ( - - + + <> + + } + placement="top" + normalColor={theme.colors.notification} + textStyle={{ textAlign: 'center', ...FontMedium }} + successColor={theme.colors.primary} + warningColor={theme.colors.notification_warning} + offsetTop={STATUS_BAR_HEIGHT + 40} + dangerColor={theme.colors.notification_danger}> + + + + + + + + + + + + + + + + + + - )} - + {!isAppReady && ( + + + + )} + + ); }; diff --git a/src/NativeModules.ts b/src/NativeModules.ts new file mode 100644 index 000000000..46ed315cb --- /dev/null +++ b/src/NativeModules.ts @@ -0,0 +1,7 @@ +import { NativeModules } from 'react-native'; + +// Minimizer module allows the app to be pushed to the background +const { Minimizer } = NativeModules; + +// TODO: add native modules named exports here +export { Minimizer }; diff --git a/src/components/ContainerWithSubHeader.tsx b/src/components/ContainerWithSubHeader.tsx index e9f500b42..9a82fe35f 100644 --- a/src/components/ContainerWithSubHeader.tsx +++ b/src/components/ContainerWithSubHeader.tsx @@ -1,68 +1,46 @@ import React from 'react'; -import { KeyboardAvoidingView, Platform, SafeAreaView, StatusBar, StyleProp, View } from 'react-native'; +import { KeyboardAvoidingView, Platform, SafeAreaView, StyleProp, View } from 'react-native'; import { SubHeader, SubHeaderProps } from 'components/SubHeader'; -import { getStatusBarPlaceholderStyle, STATUS_BAR_HEIGHT, STATUS_BAR_LIGHT_CONTENT } from 'styles/sharedStyles'; -import { ColorMap } from 'styles/color'; import { Header } from 'components/Header'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; export interface ContainerWithSubHeaderProps extends SubHeaderProps { children: JSX.Element | JSX.Element[]; style?: StyleProp; isShowMainHeader?: boolean; isShowPlaceHolder?: boolean; - statusBarColor?: string; - needGapWithStatusBar?: boolean; androidKeyboardVerticalOffset?: number; disabledMainHeader?: boolean; } -const getContainerStyle: ( +const getContainerStyle: (insetTop: number, backgroundColor?: string) => StyleProp = ( + insetTop: number, backgroundColor?: string, - needGapWithStatusBar?: boolean, - isShowPlaceHolder?: boolean, -) => StyleProp = (backgroundColor?: string, needGapWithStatusBar?: boolean, isShowPlaceHolder?: boolean) => { - let marginTop = 0; - if (isShowPlaceHolder) { - if (needGapWithStatusBar) { - marginTop = STATUS_BAR_HEIGHT + 8; - } else { - marginTop = STATUS_BAR_HEIGHT; - } - } else { - if (needGapWithStatusBar) { - marginTop = 8; - } - } +) => { return { flex: 1, backgroundColor: backgroundColor || '#0C0C0C', - paddingTop: marginTop, + paddingTop: insetTop, }; }; export const ContainerWithSubHeader = ({ children, style, - isShowPlaceHolder = true, isShowMainHeader = false, - statusBarColor = ColorMap.dark1, - needGapWithStatusBar = true, androidKeyboardVerticalOffset, titleTextAlign, disabledMainHeader, ...subHeaderProps }: ContainerWithSubHeaderProps) => { + const insets = useSafeAreaInsets(); return ( - {isShowPlaceHolder && } - - - + style={[getContainerStyle(insets.top, subHeaderProps.backgroundColor), style]}> {isShowMainHeader && ( - +
)} diff --git a/src/components/FlatListScreen.tsx b/src/components/FlatListScreen.tsx index 247d3886c..2f9d07382 100644 --- a/src/components/FlatListScreen.tsx +++ b/src/components/FlatListScreen.tsx @@ -4,8 +4,6 @@ import { ListRenderItemInfo, RefreshControlProps, StyleProp, TextInput, View, Vi import { ContainerWithSubHeader } from 'components/ContainerWithSubHeader'; import { Search } from 'components/Search'; import { SortFunctionInterface } from 'types/ui-types'; -import { useNavigation } from '@react-navigation/native'; -import { RootNavigationProps } from 'routes/index'; import { defaultSortFunc } from 'utils/function'; import i18n from 'utils/i18n/i18n'; import { LazyFlatList } from 'components/LazyFlatList'; @@ -59,9 +57,7 @@ interface Props { groupBy: (item: T) => string; sortSection?: SortFunctionInterface>; }; - needGapWithStatusBar?: boolean; isShowMainHeader?: boolean; - isShowPlaceHolder?: boolean; defaultSearchString?: string; androidKeyboardVerticalOffset?: number; titleTextAlign?: 'left' | 'center'; @@ -102,15 +98,12 @@ export function FlatListScreen({ isShowListWrapper = false, beforeListItem, grouping, - needGapWithStatusBar, isShowMainHeader, - isShowPlaceHolder, defaultSearchString, androidKeyboardVerticalOffset, titleTextAlign, getItemLayout, }: Props) { - const navigation = useNavigation(); const [searchString, setSearchString] = useState(defaultSearchString || ''); const searchRef = useRef(null); const { filterSelectionMap, openFilterModal, onApplyFilter, onChangeFilterOption, selectedFilters, filterModalRef } = @@ -126,7 +119,7 @@ export function FlatListScreen({ const _onPressBack = () => { searchRef && searchRef.current && searchRef.current.blur(); - onPressBack ? onPressBack() : navigation.canGoBack() && navigation.goBack(); + onPressBack && onPressBack(); }; const renderContent = () => ( @@ -220,10 +213,8 @@ export function FlatListScreen({ rightButtonTitle={rightIconOption?.title} disableRightButton={rightIconOption?.disabled} rightIconColor={rightIconOption?.color} - isShowPlaceHolder={isShowPlaceHolder} isShowMainHeader={isShowMainHeader} - androidKeyboardVerticalOffset={androidKeyboardVerticalOffset} - needGapWithStatusBar={needGapWithStatusBar}> + androidKeyboardVerticalOffset={androidKeyboardVerticalOffset}> {renderContent()} ); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 905cd2815..7206eaf6e 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -49,8 +49,6 @@ export const Header = ({ rightComponent, disabled }: HeaderProps) => { const onScanAddress = useCallback( (data: string) => { - const _error = validWalletConnectUri(data); - console.log('error', _error); if (isAddress(data)) { setError(undefined); setIsScanning(false); diff --git a/src/components/Input/InputAddressV2.tsx b/src/components/Input/InputAddressV2.tsx index 0594a9fed..c887a6094 100644 --- a/src/components/Input/InputAddressV2.tsx +++ b/src/components/Input/InputAddressV2.tsx @@ -255,6 +255,7 @@ const Component = ( onSelect={onSelectAddressBook} value={value} onClose={closeAddressBookModal} + setVisible={setShowAddressBookModal} /> )} diff --git a/src/components/Input/InputCheckBox.tsx b/src/components/Input/InputCheckBox.tsx index a1748fea2..743956068 100644 --- a/src/components/Input/InputCheckBox.tsx +++ b/src/components/Input/InputCheckBox.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { StyleProp, TextStyle, View, ViewStyle } from 'react-native'; import { CheckBox } from 'react-native-elements'; import { ColorMap } from 'styles/color'; @@ -39,6 +39,17 @@ const LabelStyle: StyleProp = { }; const InputCheckBox = ({ checked, onPress, disable, label, checkBoxSize = 20 }: Props) => { + const UncheckedIcon = ( + + + + ); + + const CheckedIcon = ( + + + + ); return ( } - checkedIcon={} + uncheckedIcon={UncheckedIcon} + checkedIcon={CheckedIcon} checkedColor={ColorMap.light} uncheckedColor={ColorMap.light} disabled={disable} diff --git a/src/components/Input/InputConnectUrl.tsx b/src/components/Input/InputConnectUrl.tsx index 1b634d6c9..d1c0397db 100644 --- a/src/components/Input/InputConnectUrl.tsx +++ b/src/components/Input/InputConnectUrl.tsx @@ -10,21 +10,39 @@ import { AddressScanner, AddressScannerProps } from 'components/Scanner/AddressS import { requestCameraPermission } from 'utils/permission/camera'; import { RESULTS } from 'react-native-permissions'; import { setAdjustResize } from 'rn-android-keyboard-adjust'; +import { addConnection } from 'messaging/index'; +import i18n from 'utils/i18n/i18n'; +import { validWalletConnectUri } from 'utils/scanner/walletConnect'; +import { useToast } from 'react-native-toast-notifications'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; interface Props extends InputProps { isValidValue?: boolean; showAvatar?: boolean; scannerProps?: Omit; + isShowQrModalVisible: boolean; + setQrModalVisible: React.Dispatch>; + setLoading: React.Dispatch>; } const Component = ( - { isValidValue, scannerProps = {}, value = '', ...inputProps }: Props, + { + isValidValue, + scannerProps = {}, + value = '', + isShowQrModalVisible, + setQrModalVisible, + setLoading, + ...inputProps + }: Props, ref: ForwardedRef, ) => { const theme = useSubWalletTheme().swThemes; - const [isShowQrModalVisible, setIsShowQrModalVisible] = useState(false); const isAddressValid = isValidValue !== undefined ? isValidValue : true; const [error, setError] = useState(undefined); + const navigation = useNavigation(); + const toast = useToast(); useEffect(() => setAdjustResize(), []); @@ -32,9 +50,9 @@ const Component = ( const result = await requestCameraPermission(); if (result === RESULTS.GRANTED) { - setIsShowQrModalVisible(true); + setQrModalVisible(true); } - }, []); + }, [setQrModalVisible]); const RightPart = useMemo(() => { return ( @@ -78,10 +96,28 @@ const Component = ( const onScanInputText = useCallback( (data: string) => { setError(undefined); - setIsShowQrModalVisible(false); + setQrModalVisible(false); onChangeInputText(data); + setLoading(true); + if (!validWalletConnectUri(data)) { + addConnection({ uri: data }) + .then(() => { + setLoading(false); + navigation.goBack(); + }) + .catch(e => { + const errMessage = (e as Error).message; + const message = errMessage.includes('Pairing already exists') + ? i18n.errorMessage.connectionAlreadyExist + : i18n.errorMessage.failToAddConnection; + + toast.hideAll(); + toast.show(message, { type: 'danger' }); + setLoading(false); + }); + } }, - [onChangeInputText], + [navigation, onChangeInputText, setLoading, setQrModalVisible, toast], ); const onInputFocus = (e: NativeSyntheticEvent) => { @@ -94,8 +130,8 @@ const Component = ( const closeAddressScanner = useCallback(() => { setError(undefined); - setIsShowQrModalVisible(false); - }, []); + setQrModalVisible(false); + }, [setQrModalVisible]); return ( <> diff --git a/src/components/Modal/AddressBook/AddContactModal.tsx b/src/components/Modal/AddressBook/AddContactModal.tsx index 2e9ad8068..fbed92347 100644 --- a/src/components/Modal/AddressBook/AddContactModal.tsx +++ b/src/components/Modal/AddressBook/AddContactModal.tsx @@ -1,7 +1,7 @@ import { Button, Icon, SwModal } from 'components/design-system-ui'; import { View } from 'react-native'; import i18n from 'utils/i18n/i18n'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; @@ -17,10 +17,11 @@ import { PlusCircle } from 'phosphor-react-native'; import { useToast } from 'react-native-toast-notifications'; import createStylesheet from './style/AddContactModal'; import { TextInputProps } from 'react-native/Libraries/Components/TextInput/TextInput'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; type Props = { modalVisible: boolean; - onChangeModalVisible: () => void; + setModalVisible: (arg: boolean) => void; }; enum FormFieldName { @@ -56,12 +57,17 @@ const ButtonIcon = (color: string) => { return ; }; -export const AddContactModal = ({ modalVisible, onChangeModalVisible }: Props) => { +export const AddContactModal = ({ modalVisible, setModalVisible }: Props) => { const theme = useSubWalletTheme().swThemes; const contacts = useSelector((state: RootState) => state.accountState.contacts); const [loading, setLoading] = useState(false); const { show, hideAll } = useToast(); const stylesheet = createStylesheet(theme); + const modalBaseV2Ref = useRef(null); + + const onChangeModalVisible = useCallback(() => { + modalBaseV2Ref?.current?.close(); + }, []); const { control, @@ -109,7 +115,7 @@ export const AddContactModal = ({ modalVisible, onChangeModalVisible }: Props) = setLoading(true); editContactAddress(address, name) .then(() => { - onChangeModalVisible(); + modalBaseV2Ref?.current?.close(); }) .catch((e: Error) => { hideAll(); @@ -119,7 +125,7 @@ export const AddContactModal = ({ modalVisible, onChangeModalVisible }: Props) = setLoading(false); }); }, - [hideAll, onChangeModalVisible, show], + [hideAll, show], ); // don't use memo with this, or else it will cause bug @@ -151,10 +157,12 @@ export const AddContactModal = ({ modalVisible, onChangeModalVisible }: Props) = return ( + onBackButtonPress={onChangeModalVisible}> void; value?: string; addressPrefix?: number; onSelect: (val: string) => void; networkGenesisHash?: string; + setVisible: (arg: boolean) => void; } enum AccountGroup { @@ -116,14 +117,17 @@ export const AddressBookModal = ({ addressPrefix, networkGenesisHash, modalVisible, - onClose, onSelect, value = '', + setVisible, }: Props) => { const { accounts, contacts, recent } = useSelector((state: RootState) => state.accountState); const formatAddress = useFormatAddress(addressPrefix); const theme = useSubWalletTheme().swThemes; const stylesheet = createStylesheet(theme); + const modalBaseV2Ref = useRef(null); + + const onClose = useCallback(() => modalBaseV2Ref?.current?.close(), []); const AccountGroupNameMap = useMemo( () => ({ [AccountGroup.WALLET]: i18n.addressBook.typeWallet, @@ -236,7 +240,12 @@ export const AddressBookModal = ({ const BeforeListItem = useMemo(() => , [stylesheet.beforeListBlock]); return ( - + - + ); }; diff --git a/src/components/Modal/AddressBook/EditContactModal.tsx b/src/components/Modal/AddressBook/EditContactModal.tsx index 7321bc339..72e4d4ebe 100644 --- a/src/components/Modal/AddressBook/EditContactModal.tsx +++ b/src/components/Modal/AddressBook/EditContactModal.tsx @@ -22,11 +22,12 @@ import { deviceHeight, TOAST_DURATION } from 'constants/index'; import { ColorMap } from 'styles/color'; import { STATUS_BAR_HEIGHT } from 'styles/sharedStyles'; import { TextInputProps } from 'react-native/Libraries/Components/TextInput/TextInput'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; type Props = { addressJson: AddressJson; modalVisible: boolean; - onChangeModalVisible: () => void; + setModalVisible: (arg: boolean) => void; }; enum FormFieldName { @@ -37,7 +38,7 @@ interface FormValues { [FormFieldName.NAME]: string; } -export const EditContactModal = ({ modalVisible, onChangeModalVisible, addressJson }: Props) => { +export const EditContactModal = ({ modalVisible, addressJson, setModalVisible }: Props) => { const theme = useSubWalletTheme().swThemes; const { address, name: defaultName = '' } = addressJson; const contacts = useSelector((state: RootState) => state.accountState.contacts); @@ -58,12 +59,15 @@ export const EditContactModal = ({ modalVisible, onChangeModalVisible, addressJs [FormFieldName.NAME]: defaultName, }, }); + const modalBaseV2Ref = useRef(null); const { name: nameValue } = { ...useWatch({ control }), ...getValues(), }; + const onChangeModalVisible = useCallback(() => modalBaseV2Ref?.current?.close(), []); + const existNames = useMemo( () => contacts @@ -140,6 +144,7 @@ export const EditContactModal = ({ modalVisible, onChangeModalVisible, addressJs onCancelModal: onCancelDelete, visible: deleteVisible, onCompleteModal: onCompleteDelete, + setVisible, } = useConfirmModal(handeDelete); useEffect(() => { @@ -151,10 +156,12 @@ export const EditContactModal = ({ modalVisible, onChangeModalVisible, addressJs return ( <> + onBackButtonPress={onChangeModalVisible}> diff --git a/src/components/Modal/Base/ModalBase.tsx b/src/components/Modal/Base/ModalBase.tsx index f6727c58f..6e57338ab 100644 --- a/src/components/Modal/Base/ModalBase.tsx +++ b/src/components/Modal/Base/ModalBase.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import RNModal from 'react-native-modal'; import { ModalProps } from 'react-native-modal/dist/modal'; import useAppLock from 'hooks/useAppLock'; +import useConfirmationsInfo from 'hooks/screen/Confirmation/useConfirmationsInfo'; export interface SWModalProps extends ModalProps { id?: string; @@ -12,14 +13,15 @@ export default function ModalBase(props: SWModalProps) { const { isLocked } = useAppLock(); const { isUseForceHidden = true } = props; const [isForcedHidden, setForcedHidden] = useState(false); + const { numberOfConfirmations } = useConfirmationsInfo(); useEffect(() => { - if (isLocked && isUseForceHidden) { + if (isUseForceHidden && (isLocked || !!numberOfConfirmations)) { setForcedHidden(true); } else { setForcedHidden(false); } - }, [isLocked, isUseForceHidden]); + }, [isLocked, isUseForceHidden, numberOfConfirmations]); return ( diff --git a/src/components/Modal/ConnectWebsiteModal.tsx b/src/components/Modal/ConnectWebsiteModal.tsx index c4311e225..4cf28ead8 100644 --- a/src/components/Modal/ConnectWebsiteModal.tsx +++ b/src/components/Modal/ConnectWebsiteModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AuthUrlInfo } from '@subwallet/extension-base/background/handlers/State'; import { ScrollView, TouchableOpacity, View } from 'react-native'; import { RootState } from 'stores/index'; @@ -15,14 +15,15 @@ import SwModal from 'components/design-system-ui/modal/SwModal'; import { ButtonPropsType } from 'components/design-system-ui/button/PropsType'; import createStylesheet from './style/ConnectWebsiteModal'; import i18n from 'utils/i18n/i18n'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; interface Props { modalVisible: boolean; - onChangeModalVisible: () => void; isNotConnected: boolean; isBlocked: boolean; authInfo?: AuthUrlInfo; url: string; + setVisible: (arg: boolean) => void; } type ConnectIcon = { @@ -37,23 +38,18 @@ const ButtonIconMap = { }; // todo: i18n; -export const ConnectWebsiteModal = ({ - modalVisible, - onChangeModalVisible, - isNotConnected, - isBlocked, - authInfo, - url, -}: Props) => { +export const ConnectWebsiteModal = ({ setVisible, modalVisible, isNotConnected, isBlocked, authInfo, url }: Props) => { const theme = useSubWalletTheme().swThemes; const stylesheet = createStylesheet(theme); - + const modalBaseV2Ref = useRef(null); const [allowedMap, setAllowedMap] = useState>(authInfo?.isAllowedMap || {}); const accounts = useSelector((state: RootState) => state.accountState.accounts); const currentAccount = useSelector((state: RootState) => state.accountState.currentAccount); const [loading, setLoading] = useState(false); const _isNotConnected = isNotConnected || !authInfo; + const onChangeModalVisible = useCallback(() => modalBaseV2Ref?.current?.close(), []); + const handlerUpdateMap = useCallback((address: string, oldValue: boolean) => { return () => { setAllowedMap(values => ({ @@ -248,10 +244,13 @@ export const ConnectWebsiteModal = ({ return ( {actionButtons}}> diff --git a/src/components/Modal/PasswordModal.tsx b/src/components/Modal/PasswordModal.tsx index 33b943a0b..45a90206d 100644 --- a/src/components/Modal/PasswordModal.tsx +++ b/src/components/Modal/PasswordModal.tsx @@ -1,16 +1,17 @@ import { PasswordField } from 'components/Field/Password'; import useFormControl, { FormControlConfig, FormState } from 'hooks/screen/useFormControl'; -import React, { useCallback, useContext, useEffect } from 'react'; +import React, { useCallback, useContext, useEffect, useRef } from 'react'; import { StyleProp, View, ViewStyle } from 'react-native'; import { validatePassword } from 'screens/Shared/AccountNamePasswordCreation'; import i18n from 'utils/i18n/i18n'; import { Warning } from 'components/Warning'; import { WebRunnerContext } from 'providers/contexts'; import { Button, SwModal } from 'components/design-system-ui'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; interface Props { visible: boolean; - closeModal: () => void; + setModalVisible: (arg: boolean) => void; onConfirm: (password: string) => void; isBusy: boolean; errorArr: string[] | undefined; @@ -28,8 +29,17 @@ const PasswordContainerStyle: StyleProp = { marginBottom: 8, }; -const PasswordModal = ({ closeModal, visible, onConfirm, isBusy, errorArr, setErrorArr, onChangePassword }: Props) => { +const PasswordModal = ({ + visible, + onConfirm, + isBusy, + errorArr, + setErrorArr, + onChangePassword, + setModalVisible, +}: Props) => { const isNetConnected = useContext(WebRunnerContext).isNetConnected; + const modalBaseV2Ref = useRef(null); const formConfig: FormControlConfig = { password: { name: i18n.common.walletPassword, @@ -38,6 +48,9 @@ const PasswordModal = ({ closeModal, visible, onConfirm, isBusy, errorArr, setEr require: true, }, }; + + const closeModal = () => modalBaseV2Ref?.current?.close(); + const onSubmit = useCallback( (formState: FormState) => { const password = formState.data.password; @@ -80,10 +93,11 @@ const PasswordModal = ({ closeModal, visible, onConfirm, isBusy, errorArr, setEr return ( + onBackButtonPress={!isBusy ? closeModal : undefined}> { return ( <> selectTypeRef?.current?.onCloseModal()} selectedValueMap={selectedValueMap} diff --git a/src/components/Modal/common/PoolSelector.tsx b/src/components/Modal/common/PoolSelector.tsx index b00ec59f1..c879134dc 100644 --- a/src/components/Modal/common/PoolSelector.tsx +++ b/src/components/Modal/common/PoolSelector.tsx @@ -233,11 +233,12 @@ export const PoolSelector = ({ chain, onSelectItem, from, poolLoading, selectedP setDetailModalVisible(false)} + setVisible={setDetailModalVisible} /> )} { ) : ( - + {availableAccounts.length > 1 && ( { }, [networks]); const getAvatarStyle = useCallback( - (index: number) => { + (index: number, arrLength: number) => { let avatarStyles: StyleProp = [_style.avatarContent]; if (index === 0) { - avatarStyles.push({ marginLeft: 0, opacity: 0.5 }); + if (index === arrLength - 1) { + avatarStyles.push({ marginLeft: 0, opacity: 1 }); + } else { + avatarStyles.push({ marginLeft: 0, opacity: 0.5 }); + } } if (index === 2) { @@ -56,7 +60,7 @@ export const WCNetworkAvatarGroup = ({ networks }: Props) => { 0 && _style.mlStrong]}> {networks.slice(0, 3).map((network, index) => { return ( - + { return selectedValueMap[item.slug]; }, [item.slug, selectedValueMap]); return ( - + <> {getNetworkLogo(item.slug, 28)} @@ -62,6 +62,6 @@ export const WCNetworkItem = ({ item, selectedValueMap }: Props) => { )} - + ); }; diff --git a/src/components/WalletConnect/Network/WCNetworkSelected.tsx b/src/components/WalletConnect/Network/WCNetworkSelected.tsx index e7c8253db..7c0d99533 100644 --- a/src/components/WalletConnect/Network/WCNetworkSelected.tsx +++ b/src/components/WalletConnect/Network/WCNetworkSelected.tsx @@ -47,11 +47,13 @@ export const WCNetworkSelected = ({ networks }: Props) => { return ( ( { const networkNumber = networks.length; return ( ( - {}} /> + {}} /> )} beforeListItem={ { ...FontSemiBold, color: theme.colorWhite, paddingBottom: theme.paddingXS, - }}>{`${networkNumber} networks support`} + }}> + {i18n.message.networkSupported(networkNumber)} + } renderCustomItem={renderItem} /> diff --git a/src/components/common/Account/AccountCreationArea/index.tsx b/src/components/common/Account/AccountCreationArea/index.tsx index 678d26506..a6e52a07c 100644 --- a/src/components/common/Account/AccountCreationArea/index.tsx +++ b/src/components/common/Account/AccountCreationArea/index.tsx @@ -23,6 +23,7 @@ import { KeypairType } from '@polkadot/util-crypto/types'; import { canDerive } from '@subwallet/extension-base/utils'; import { AccountActionSelectModal, ActionItemType } from 'components/Modal/AccountActionSelectModal'; import { ModalRef } from 'types/modalRef'; +import useGoHome from 'hooks/screen/useGoHome'; interface Props { createAccountRef: React.MutableRefObject; @@ -41,6 +42,7 @@ export const AccountCreationArea = ({ const { accounts, hasMasterPassword } = useSelector((state: RootState) => state.accountState); const selectTypeRef = useRef(); const deriveAccModalRef = useRef(); + const goHome = useGoHome(); const importAccountActions = [ { key: 'secretPhrase', @@ -214,7 +216,7 @@ export const AccountCreationArea = ({ onSelectItem={createAccountFunc}> - + = (props: Props) => { const styles = useMemo(() => createStyle(theme, gap), [theme, gap]); return ( - + {children} ); diff --git a/src/components/common/Confirmation/ConfirmationContent/styles/index.ts b/src/components/common/Confirmation/ConfirmationContent/styles/index.ts index 071f5b4b0..85c7eb143 100644 --- a/src/components/common/Confirmation/ConfirmationContent/styles/index.ts +++ b/src/components/common/Confirmation/ConfirmationContent/styles/index.ts @@ -10,7 +10,7 @@ export default (theme: ThemeTypes, gap = 20) => { return StyleSheet.create({ container: { paddingHorizontal: theme.padding, - paddingTop: theme.padding, + marginTop: theme.padding, width: '100%', }, content: { diff --git a/src/components/common/Confirmation/ConfirmationGeneralInfo/index.tsx b/src/components/common/Confirmation/ConfirmationGeneralInfo/index.tsx index 991f7e015..6d5fd17fe 100644 --- a/src/components/common/Confirmation/ConfirmationGeneralInfo/index.tsx +++ b/src/components/common/Confirmation/ConfirmationGeneralInfo/index.tsx @@ -7,19 +7,19 @@ import React, { useMemo } from 'react'; import { Text, View } from 'react-native'; import createStyle from './styles'; import { ImageLogosMap } from 'assets/logo'; +import { isWalletConnectRequest } from '@subwallet/extension-base/services/wallet-connect-service/helpers'; +import { SVGImages } from 'assets/index'; interface Props { request: ConfirmationRequestBase; gap?: number; - linkIcon?: React.ReactNode; - linkIconBg?: string; } const ConfirmationGeneralInfo: React.FC = (props: Props) => { - const { request, gap = 0, linkIcon, linkIconBg } = props; + const { request, gap = 0 } = props; const domain = getDomainFromUrl(request.url); const leftLogoUrl = `https://icons.duckduckgo.com/ip2/${domain}.ico`; - + const isWCRequest = useMemo(() => isWalletConnectRequest(request.id), [request.id]); const theme = useSubWalletTheme().swThemes; const styles = useMemo(() => createStyle(theme, gap), [theme, gap]); @@ -28,8 +28,7 @@ const ConfirmationGeneralInfo: React.FC = (props: Props) => { } - linkIcon={linkIcon} - linkIconBg={linkIconBg} + linkIcon={isWCRequest ? : undefined} rightLogo={} /> {domain} diff --git a/src/components/common/FilterModal/index.tsx b/src/components/common/FilterModal/index.tsx index 662bc8dc2..347656271 100644 --- a/src/components/common/FilterModal/index.tsx +++ b/src/components/common/FilterModal/index.tsx @@ -30,6 +30,7 @@ const FilterModal = ({ return ( <> = ({ }: Props) => { const theme = useSubWalletTheme().swThemes; const styles = useMemo(() => createStyle(theme), [theme]); + const insets = useSafeAreaInsets(); return ( - - - + <> + + + + + } onBackButtonPress={onCancelModal} onChangeModalVisible={onCancelModal}> diff --git a/src/components/common/Modal/ConfirmModal/styles/index.ts b/src/components/common/Modal/ConfirmModal/styles/index.ts index 669f8bd46..0f784fa45 100644 --- a/src/components/common/Modal/ConfirmModal/styles/index.ts +++ b/src/components/common/Modal/ConfirmModal/styles/index.ts @@ -1,6 +1,6 @@ import { StyleSheet, TextStyle, ViewStyle } from 'react-native'; import { ThemeTypes } from 'styles/themes'; -import { ContainerHorizontalPadding, FontMedium, FontSemiBold, MarginBottomForSubmitButton } from 'styles/sharedStyles'; +import { ContainerHorizontalPadding, FontMedium, FontSemiBold } from 'styles/sharedStyles'; export interface ModalStyle { container: ViewStyle; @@ -16,7 +16,6 @@ export default (theme: ThemeTypes) => width: '100%', flexDirection: 'row', ...ContainerHorizontalPadding, - ...MarginBottomForSubmitButton, }, deleteModalConfirmationStyle: { fontSize: theme.fontSizeLG, diff --git a/src/components/common/Modal/DeleteModal/index.tsx b/src/components/common/Modal/DeleteModal/index.tsx index ed4721dd2..c21511608 100644 --- a/src/components/common/Modal/DeleteModal/index.tsx +++ b/src/components/common/Modal/DeleteModal/index.tsx @@ -1,26 +1,27 @@ import { Button, Icon, PageIcon, SwModal } from 'components/design-system-ui'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { IconProps, Trash, XCircle } from 'phosphor-react-native'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useRef } from 'react'; import { Text, View } from 'react-native'; import { VoidFunction } from 'types/index'; import i18n from 'utils/i18n/i18n'; import createStyle from './styles'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; interface Props { message: string; - onCancelModal: VoidFunction; + onCancelModal?: VoidFunction; onCompleteModal: VoidFunction; title: string; visible: boolean; buttonTitle?: string; buttonIcon?: (iconProps: IconProps) => JSX.Element; loading?: boolean; + setVisible: (arg: boolean) => void; } const DeleteModal: React.FC = (props: Props) => { const { - onCancelModal, onCompleteModal, visible, title, @@ -28,14 +29,22 @@ const DeleteModal: React.FC = (props: Props) => { buttonTitle, buttonIcon: ButtonIcon, loading, + setVisible, + onCancelModal, } = props; - + const deleteModalRef = useRef(null); const theme = useSubWalletTheme().swThemes; - const styles = useMemo(() => createStyle(theme), [theme]); + const closeModal = useCallback(() => { + onCancelModal && onCancelModal(); + deleteModalRef?.current?.close(); + }, [onCancelModal]); return ( = (props: Props) => { } - onBackButtonPress={onCancelModal} + onBackButtonPress={closeModal} onChangeModalVisible={onCancelModal}> diff --git a/src/components/common/Modal/DeleteModal/styles/index.ts b/src/components/common/Modal/DeleteModal/styles/index.ts index 52b0fd1c8..50463885e 100644 --- a/src/components/common/Modal/DeleteModal/styles/index.ts +++ b/src/components/common/Modal/DeleteModal/styles/index.ts @@ -12,7 +12,12 @@ export interface ModalStyle { export default (theme: ThemeTypes) => StyleSheet.create({ container: { width: '100%' }, - footerModalStyle: { width: '100%', ...ContainerHorizontalPadding, ...MarginBottomForSubmitButton }, + footerModalStyle: { + width: '100%', + ...ContainerHorizontalPadding, + ...MarginBottomForSubmitButton, + paddingTop: theme.paddingXS, + }, deleteModalConfirmationStyle: { fontSize: theme.fontSizeLG, lineHeight: theme.fontSizeLG * theme.lineHeightLG, diff --git a/src/components/common/Modal/DeriveAccountModal/index.tsx b/src/components/common/Modal/DeriveAccountModal/index.tsx index 31307dd60..517f9ed10 100644 --- a/src/components/common/Modal/DeriveAccountModal/index.tsx +++ b/src/components/common/Modal/DeriveAccountModal/index.tsx @@ -4,11 +4,9 @@ import { AccountJson } from '@subwallet/extension-base/background/types'; import { canDerive } from '@subwallet/extension-base/utils'; import AccountItemWithName from 'components/common/Account/Item/AccountItemWithName'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import { ActivityIndicator } from 'components/design-system-ui'; import { deviceHeight, EVM_ACCOUNT_TYPE, TOAST_DURATION } from 'constants/index'; import useUnlockModal from 'hooks/modal/useUnlockModal'; -import useGoHome from 'hooks/screen/useGoHome'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { deriveAccountV3 } from 'messaging/index'; import React, { useCallback, useMemo, useRef, useState } from 'react'; @@ -22,9 +20,13 @@ import { STATUS_BAR_HEIGHT } from 'styles/sharedStyles'; import createStyles from './styles'; import { ModalRef } from 'types/modalRef'; import { AccountSelector } from 'components/Modal/common/AccountSelector'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { RootStackParamList } from 'routes/index'; type Props = { deriveAccModalRef: React.MutableRefObject; + goHome: () => void; + navigation: NativeStackNavigationProp; }; const renderLoaderIcon = (x: React.ReactNode): React.ReactNode => { @@ -37,9 +39,8 @@ const renderLoaderIcon = (x: React.ReactNode): React.ReactNode => { }; const DeriveAccountModal: React.FC = (props: Props) => { - const { deriveAccModalRef } = props; + const { deriveAccModalRef, goHome, navigation } = props; const theme = useSubWalletTheme().swThemes; - const goHome = useGoHome(); const { accounts } = useSelector((state: RootState) => state.accountState); @@ -89,7 +90,7 @@ const DeriveAccountModal: React.FC = (props: Props) => { [goHome, toastError], ); - const { visible, onPasswordComplete, onPress: onPressSubmit, onHideModal } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); const renderItem = useCallback( ({ item: account }: ListRenderItemInfo): JSX.Element => { @@ -131,7 +132,6 @@ const DeriveAccountModal: React.FC = (props: Props) => { placement={'bottom'} offsetBottom={deviceHeight - STATUS_BAR_HEIGHT - (Platform.OS === 'android' ? 120 : 80)} /> - ); diff --git a/src/components/common/Modal/UnlockModal/index.tsx b/src/components/common/Modal/UnlockModal/index.tsx index 33339e246..ae10bac96 100644 --- a/src/components/common/Modal/UnlockModal/index.tsx +++ b/src/components/common/Modal/UnlockModal/index.tsx @@ -1,8 +1,7 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Button, Icon, SwModal } from 'components/design-system-ui'; -import { View } from 'react-native'; +import React, { useCallback, useMemo, useState } from 'react'; +import { Button, Icon, Typography } from 'components/design-system-ui'; +import { DeviceEventEmitter, KeyboardAvoidingView, Platform, TouchableOpacity, View } from 'react-native'; import { PasswordField } from 'components/Field/Password'; -import { VoidFunction } from 'types/index'; import i18n from 'utils/i18n/i18n'; import { validatePassword } from 'screens/Shared/AccountNamePasswordCreation'; import useFormControl from 'hooks/screen/useFormControl'; @@ -10,15 +9,12 @@ import { CheckCircle } from 'phosphor-react-native'; import { keyringUnlock } from 'messaging/index'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import createStyle from './style'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; +import { SafeAreaView } from 'react-native-safe-area-context'; -interface Props { - onPasswordComplete: VoidFunction; - visible: boolean; - onHideModal: VoidFunction; -} - -export const UnlockModal: React.FC = (props: Props) => { - const { visible, onPasswordComplete, onHideModal } = props; +export const UnlockModal = () => { + const navigation = useNavigation(); const theme = useSubWalletTheme().swThemes; const styles = useMemo(() => createStyle(theme), [theme]); @@ -44,7 +40,8 @@ export const UnlockModal: React.FC = (props: Props) => { if (!data.status) { onUpdateErrors('password')([i18n.errorMessage.invalidMasterPassword]); } else { - onPasswordComplete(); + DeviceEventEmitter.emit('unlockModal', { type: 'onComplete' }); + navigation.goBack(); } }) .catch((e: Error) => { @@ -56,7 +53,7 @@ export const UnlockModal: React.FC = (props: Props) => { }); }; - const { formState, onChangeValue, onSubmitField, focus, onUpdateErrors } = useFormControl(formConfig, { + const { formState, onChangeValue, onSubmitField, onUpdateErrors } = useFormControl(formConfig, { onSubmitForm: onSubmit, }); @@ -74,53 +71,51 @@ export const UnlockModal: React.FC = (props: Props) => { [onChangeValue, onUpdateErrors], ); - useEffect(() => { - if (!visible) { - onChangeValue('password')(''); - onUpdateErrors('password')([]); - } else { - focus('password')(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [visible]); - return ( - - - + + { + DeviceEventEmitter.emit('unlockModal', { type: 'onCancel' }); + navigation.goBack(); + }}> + + + + + {i18n.header.enterPassword} + + + + + + - - } - onBackButtonPress={onHideModal} - onChangeModalVisible={onHideModal}> - - - - + + + ); }; diff --git a/src/components/common/Modal/UnlockModal/style/index.ts b/src/components/common/Modal/UnlockModal/style/index.ts index 2c290336f..1bb50550d 100644 --- a/src/components/common/Modal/UnlockModal/style/index.ts +++ b/src/components/common/Modal/UnlockModal/style/index.ts @@ -1,20 +1,47 @@ -import { StyleSheet, ViewStyle } from 'react-native'; +import { StyleSheet, TextStyle, ViewStyle } from 'react-native'; import { ThemeTypes } from 'styles/themes'; +import { FontSemiBold } from 'styles/sharedStyles'; export interface ComponentStyle { footer: ViewStyle; - field: ViewStyle; + wrapper: ViewStyle; + separator: ViewStyle; + header: TextStyle; + container: ViewStyle; } export default (theme: ThemeTypes) => { return StyleSheet.create({ + container: { + maxHeight: '40%', + width: '100%', + backgroundColor: theme.colorBgDefault, + borderTopLeftRadius: theme.borderRadiusXXL, + borderTopRightRadius: theme.borderRadiusXXL, + alignItems: 'center', + }, footer: { width: '100%', - paddingHorizontal: theme.padding, marginBottom: theme.margin, + marginTop: theme.marginXS, }, - field: { + wrapper: { width: '100%', + paddingHorizontal: theme.padding, + }, + separator: { + width: 70, + height: 5, + borderRadius: 100, + backgroundColor: 'rgba(255, 255, 255, 0.2)', + marginBottom: 16, + marginTop: theme.marginXS, + }, + header: { + color: theme.colorWhite, + ...FontSemiBold, + textAlign: 'center', + marginBottom: theme.margin, }, }); }; diff --git a/src/components/common/RpcSelectorModal/index.tsx b/src/components/common/RpcSelectorModal/index.tsx index 94fd9edc2..0e1d9464c 100644 --- a/src/components/common/RpcSelectorModal/index.tsx +++ b/src/components/common/RpcSelectorModal/index.tsx @@ -57,7 +57,7 @@ export const RpcSelectorModal = ({ const renderItem = useCallback( ({ item }: ListRenderItemInfo<{ label: string; value: string }>) => ( - + ), - [theme.colorTextLight4, selectedValueMap, onSelectItem, onPressBack], + [theme.marginXS, theme.colorTextLight4, selectedValueMap, onSelectItem, onPressBack], ); return ( diff --git a/src/components/common/SelectAccountType/index.tsx b/src/components/common/SelectAccountType/index.tsx index 1ad93a727..a6b107315 100644 --- a/src/components/common/SelectAccountType/index.tsx +++ b/src/components/common/SelectAccountType/index.tsx @@ -69,21 +69,23 @@ export const SelectAccountType = (props: SelectAccountTypeProps) => { {title} )} - {items.map(item => { - const _selected = selectedItems.find(i => i === item.key); + + {items.map(item => { + const _selected = selectedItems.find(i => i === item.key); - return ( - } - isSelected={!!_selected} - onPress={item.onClick} - showUnselect={true} - /> - ); - })} + return ( + } + isSelected={!!_selected} + onPress={item.onClick} + showUnselect={true} + /> + ); + })} + ); }; diff --git a/src/components/common/SelectExportType/index.tsx b/src/components/common/SelectExportType/index.tsx index 94957fbe9..988747b1f 100644 --- a/src/components/common/SelectExportType/index.tsx +++ b/src/components/common/SelectExportType/index.tsx @@ -107,33 +107,35 @@ export const SelectExportType = (props: SelectAccountTypeProps) => { {title} )} - {items.map(item => { - const _selected = selectedItems.find(i => i === item.key); + + {items.map(item => { + const _selected = selectedItems.find(i => i === item.key); - if (item.hidden) { - return null; - } + if (item.hidden) { + return null; + } - return ( - - } - isSelected={!!_selected} - onPress={item.disable || loading ? undefined : item.onClick} - showUnselect={true} - disabled={item.disable} - /> - ); - })} + return ( + + } + isSelected={!!_selected} + onPress={item.disable || loading ? undefined : item.onClick} + showUnselect={true} + disabled={item.disable} + /> + ); + })} + ); }; diff --git a/src/components/common/SelectModal/BasicSelectModal.tsx b/src/components/common/SelectModal/BasicSelectModal.tsx index 699e1f241..492ca2da2 100644 --- a/src/components/common/SelectModal/BasicSelectModal.tsx +++ b/src/components/common/SelectModal/BasicSelectModal.tsx @@ -1,12 +1,15 @@ -import React, { ForwardedRef, forwardRef, useImperativeHandle, useState } from 'react'; -import { Button, Divider, Icon, SwModal } from 'components/design-system-ui'; +import React, { ForwardedRef, forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'; +import { Button, Icon, SwModal } from 'components/design-system-ui'; import { IconProps } from 'phosphor-react-native'; import { SelectModalField } from 'components/common/SelectModal/parts/SelectModalField'; -import { View } from 'react-native'; +import { ScrollView, View } from 'react-native'; import { ActionSelectItem } from 'components/common/SelectModal/parts/ActionSelectItem'; import { FilterSelectItem } from 'components/common/SelectModal/parts/FilterSelectItem'; import { ActionItemType } from 'components/Modal/AccountActionSelectModal'; import { OptionType } from 'components/common/FilterModal'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; +import { MarginBottomForSubmitButton } from 'styles/sharedStyles'; +import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; interface Props { title: string; @@ -32,6 +35,9 @@ interface Props { onBackButtonPress?: () => void; titleTextAlign?: 'center' | 'left'; beforeListItem?: React.ReactNode; + isUseForceHidden?: boolean; + level?: number; + isUseModalV2?: boolean; } function _BasicSelectModal(selectModalProps: Props, ref: ForwardedRef) { @@ -54,10 +60,21 @@ function _BasicSelectModal(selectModalProps: Props, ref: ForwardedRef onBackButtonPress, titleTextAlign, beforeListItem, + isUseForceHidden, + isUseModalV2 = true, + level, } = selectModalProps; + const modalBaseV2Ref = useRef(null); const [isOpen, setOpen] = useState(false); - const onCloseModal = () => setOpen(false); + const onCloseModal = useCallback(() => { + if (isUseModalV2) { + modalBaseV2Ref?.current?.close(); + } else { + setOpen(false); + } + }, [isUseModalV2]); const onOpenModal = () => setOpen(true); + const theme = useSubWalletTheme().swThemes; useImperativeHandle( ref, @@ -66,7 +83,7 @@ function _BasicSelectModal(selectModalProps: Props, ref: ForwardedRef onCloseModal: onCloseModal, isModalOpen: isOpen, }), - [isOpen], + [isOpen, onCloseModal], ); const renderItem = (item: T) => { if (selectModalItemType === 'select') { @@ -97,8 +114,13 @@ function _BasicSelectModal(selectModalProps: Props, ref: ForwardedRef const renderFooter = () => { return ( <> - - diff --git a/src/screens/Account/AccountExport/index.tsx b/src/screens/Account/AccountExport/index.tsx index b214a45a0..33cd70dc0 100644 --- a/src/screens/Account/AccountExport/index.tsx +++ b/src/screens/Account/AccountExport/index.tsx @@ -323,7 +323,7 @@ export const AccountExport = ({ setModalVisible(false)} + setModalVisible={setModalVisible} isBusy={isBusy} onConfirm={onPressSubmit} errorArr={errorArr} diff --git a/src/screens/Account/AccountsScreen/index.tsx b/src/screens/Account/AccountsScreen/index.tsx index ab1526241..d13836eb9 100644 --- a/src/screens/Account/AccountsScreen/index.tsx +++ b/src/screens/Account/AccountsScreen/index.tsx @@ -150,6 +150,7 @@ export const AccountsScreen = ({ <> navigation.goBack()} title={i18n.header.selectAccount} items={accounts} renderItem={renderItem} diff --git a/src/screens/Account/AttachReadOnly/index.tsx b/src/screens/Account/AttachReadOnly/index.tsx index e1115207b..39817b7aa 100644 --- a/src/screens/Account/AttachReadOnly/index.tsx +++ b/src/screens/Account/AttachReadOnly/index.tsx @@ -1,5 +1,4 @@ import { useNavigation } from '@react-navigation/native'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import { ContainerWithSubHeader } from 'components/ContainerWithSubHeader'; import { InputAddress } from 'components/Input/InputAddressV2'; import { AddressScanner } from 'components/Scanner/AddressScanner'; @@ -45,7 +44,7 @@ const AttachReadOnly = () => { const [errors, setErrors] = useState([]); const [scanError, setScanError] = useState(undefined); const [isBusy, setIsBusy] = useState(false); - const { visible, onPasswordComplete, onPress: onPressSubmit, onHideModal } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); const accountRef = useRef(null); useEffect(() => { @@ -214,7 +213,6 @@ const AttachReadOnly = () => { - ); }; diff --git a/src/screens/Account/ConnectQrSigner/index.tsx b/src/screens/Account/ConnectQrSigner/index.tsx index fa24bb985..953ff3597 100644 --- a/src/screens/Account/ConnectQrSigner/index.tsx +++ b/src/screens/Account/ConnectQrSigner/index.tsx @@ -4,7 +4,6 @@ import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { ImageLogosMap } from 'assets/logo'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import { ContainerWithSubHeader } from 'components/ContainerWithSubHeader'; import { Button, Icon, Image } from 'components/design-system-ui'; import { SWImageProps } from 'components/design-system-ui/image'; @@ -95,12 +94,7 @@ const ConnectQrSigner: React.FC = (props: Props) => { ); const { onOpenModal, onScan, isScanning, onHideModal } = useModalScanner(onSubmit); - const { - visible: unlockVisible, - onPasswordComplete, - onPress: onPressSubmit, - onHideModal: onHidePasswordModal, - } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); return ( @@ -131,7 +125,6 @@ const ConnectQrSigner: React.FC = (props: Props) => { - // ); diff --git a/src/screens/Account/CreateAccount/VerifySecretPhrase.tsx b/src/screens/Account/CreateAccount/VerifySecretPhrase.tsx index 12c117a23..08e2e769a 100644 --- a/src/screens/Account/CreateAccount/VerifySecretPhrase.tsx +++ b/src/screens/Account/CreateAccount/VerifySecretPhrase.tsx @@ -1,4 +1,3 @@ -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import useUnlockModal from 'hooks/modal/useUnlockModal'; import React, { useEffect, useState } from 'react'; import { StyleProp, View } from 'react-native'; @@ -12,11 +11,14 @@ import i18n from 'utils/i18n/i18n'; import { Button, Icon } from 'components/design-system-ui'; import { ArrowCircleRight } from 'phosphor-react-native'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { RootStackParamList } from 'routes/index'; interface Props { onPressSubmit: () => void; seed: string; isBusy: boolean; + navigation: NativeStackNavigationProp; } const bodyAreaStyle: StyleProp = { @@ -60,7 +62,7 @@ const isCorrectWord = (selectedWords: SelectedWordType[], seed: string) => { return selectedWords.map(item => item.word).join(' ') === seed; }; -export const VerifySecretPhrase = ({ onPressSubmit, seed, isBusy }: Props) => { +export const VerifySecretPhrase = ({ onPressSubmit, seed, isBusy, navigation }: Props) => { const [selectedWords, setSelectedWords] = useState([]); const [shuffleWords, setShuffleWords] = useState(null); const seedWords: string[] = seed.split(' '); @@ -102,7 +104,7 @@ export const VerifySecretPhrase = ({ onPressSubmit, seed, isBusy }: Props) => { ); }; - const { visible, onPasswordComplete, onPress: onSubmit, onHideModal } = useUnlockModal(); + const { onPress: onSubmit } = useUnlockModal(navigation); const getCreateAccBtn = (color: string) => { return ; @@ -133,7 +135,6 @@ export const VerifySecretPhrase = ({ onPressSubmit, seed, isBusy }: Props) => { {i18n.common.continue} - ); }; diff --git a/src/screens/Account/CreateAccount/index.tsx b/src/screens/Account/CreateAccount/index.tsx index 1bc8a87b3..940e00b36 100644 --- a/src/screens/Account/CreateAccount/index.tsx +++ b/src/screens/Account/CreateAccount/index.tsx @@ -89,7 +89,7 @@ export const CreateAccount = ({ route: { params } }: CreateAccountProps) => { )} {currentViewStep === ViewStep.VERIFY_SP && ( - + )} )} diff --git a/src/screens/Account/ImportPrivateKey/index.tsx b/src/screens/Account/ImportPrivateKey/index.tsx index c27d4f831..a5e193c17 100644 --- a/src/screens/Account/ImportPrivateKey/index.tsx +++ b/src/screens/Account/ImportPrivateKey/index.tsx @@ -1,4 +1,3 @@ -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import useUnlockModal from 'hooks/modal/useUnlockModal'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { SubScreenContainer } from 'components/SubScreenContainer'; @@ -38,7 +37,7 @@ export const ImportPrivateKey = () => { const navigation = useNavigation(); const goHome = useGoHome(); const accountName = useGetDefaultAccountName(); - const { visible, onPasswordComplete, onPress: onPressSubmit, onHideModal } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); const privateKeyFormConfig: FormControlConfig = { privateKey: { @@ -174,7 +173,6 @@ export const ImportPrivateKey = () => { {i18n.buttonTitles.importAccount} - ); diff --git a/src/screens/Account/ImportQrCode/index.tsx b/src/screens/Account/ImportQrCode/index.tsx index baf2cdd1e..92099d8d5 100644 --- a/src/screens/Account/ImportQrCode/index.tsx +++ b/src/screens/Account/ImportQrCode/index.tsx @@ -4,7 +4,6 @@ import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { ImageLogosMap } from 'assets/logo'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import { ContainerWithSubHeader } from 'components/ContainerWithSubHeader'; import { Button, Icon, Image } from 'components/design-system-ui'; import { SWImageProps } from 'components/design-system-ui/image'; @@ -108,12 +107,7 @@ const ImportQrCode: React.FC = (props: Props) => { ); const { onOpenModal, onScan, isScanning, onHideModal } = useModalScanner(onSubmit); - const { - visible: unlockVisible, - onPasswordComplete, - onPress: onPressSubmit, - onHideModal: onHidePasswordModal, - } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); return ( @@ -140,7 +134,6 @@ const ImportQrCode: React.FC = (props: Props) => { - ); }; diff --git a/src/screens/Account/ImportSecretPhrase/index.tsx b/src/screens/Account/ImportSecretPhrase/index.tsx index 2474b2b69..2c36481a0 100644 --- a/src/screens/Account/ImportSecretPhrase/index.tsx +++ b/src/screens/Account/ImportSecretPhrase/index.tsx @@ -1,6 +1,5 @@ import { KeypairType } from '@polkadot/util-crypto/types'; import { useNavigation } from '@react-navigation/native'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import { SelectAccountType } from 'components/common/SelectAccountType'; import { ContainerWithSubHeader } from 'components/ContainerWithSubHeader'; import { Button, Icon, Typography } from 'components/design-system-ui'; @@ -39,7 +38,7 @@ export const ImportSecretPhrase = () => { const navigation = useNavigation(); const goHome = useGoHome(); const accountName = useGetDefaultAccountName(); - const { visible, onPasswordComplete, onPress: onPressSubmit, onHideModal } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); const timeOutRef = useRef(); @@ -177,7 +176,6 @@ export const ImportSecretPhrase = () => { {i18n.buttonTitles.importAccount} - ); diff --git a/src/screens/Account/RestoreJson/index.tsx b/src/screens/Account/RestoreJson/index.tsx index d8b93c995..f0382527c 100644 --- a/src/screens/Account/RestoreJson/index.tsx +++ b/src/screens/Account/RestoreJson/index.tsx @@ -1,9 +1,8 @@ import AvatarGroup from 'components/common/AvatarGroup'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import useUnlockModal from 'hooks/modal/useUnlockModal'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { DotsThree, FileArrowDown, X } from 'phosphor-react-native'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FlatList, ListRenderItemInfo, Platform, ScrollView, View } from 'react-native'; import { InputFile } from 'components/common/Field/InputFile'; import { KeyringPair$Json } from '@polkadot/keyring/types'; @@ -28,6 +27,7 @@ import { Button, Icon, SelectItem, SwModal, Typography } from 'components/design import createStyles from './styles'; import { getButtonIcon } from 'utils/button'; import { SelectAccountItem } from 'components/common/SelectAccountItem'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; const getAccountsInfo = (jsonFile: KeyringPairs$Json) => { let currentAccountsInfo: ResponseJsonGetAccountInfo[] = []; @@ -86,6 +86,7 @@ export const RestoreJson = () => { const [isBusy, setIsBusy] = useState(false); const [accountsInfo, setAccountsInfo] = useState([]); const [visible, setVisible] = useState(false); + const modalBaseV2Ref = useRef(null); const addresses = useMemo(() => accountsInfo.map(acc => acc.address), [accountsInfo]); @@ -221,7 +222,7 @@ export const RestoreJson = () => { setVisible(false); }, []); - const { visible: unlockVisible, onPasswordComplete, onPress: onPressSubmit, onHideModal } = useUnlockModal(); + const { onPress: onPressSubmit } = useUnlockModal(navigation); useEffect(() => { let amount = true; @@ -309,13 +310,14 @@ export const RestoreJson = () => { - ); }; diff --git a/src/screens/Confirmations/parts/Detail/Base.tsx b/src/screens/Confirmations/parts/Detail/Base.tsx index 893b06922..ee5d8463d 100644 --- a/src/screens/Confirmations/parts/Detail/Base.tsx +++ b/src/screens/Confirmations/parts/Detail/Base.tsx @@ -37,10 +37,12 @@ const BaseDetailModal: React.FC = (props: Props) => { {i18n.common.viewDetail} diff --git a/src/screens/Confirmations/parts/Sign/Evm.tsx b/src/screens/Confirmations/parts/Sign/Evm.tsx index c64352ec0..eab618041 100644 --- a/src/screens/Confirmations/parts/Sign/Evm.tsx +++ b/src/screens/Confirmations/parts/Sign/Evm.tsx @@ -1,4 +1,3 @@ -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import ConfirmationFooter from 'components/common/Confirmation/ConfirmationFooter'; import SignatureScanner from 'components/Scanner/SignatureScanner'; import useUnlockModal from 'hooks/modal/useUnlockModal'; @@ -16,6 +15,8 @@ import { isEvmMessage } from 'utils/confirmation/confirmation'; import i18n from 'utils/i18n/i18n'; import { HIDE_MODAL_DURATION } from 'constants/index'; import { getButtonIcon } from 'utils/button'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; interface Props { id: string; @@ -55,6 +56,7 @@ export const EvmSignArea = (props: Props) => { const [loading, setLoading] = useState(false); const [isScanning, setIsScanning] = useState(false); const [isShowQr, setIsShowQr] = useState(false); + const navigation = useNavigation(); const approveIcon = useMemo((): React.ElementType => { switch (signMode) { @@ -110,7 +112,7 @@ export const EvmSignArea = (props: Props) => { [onApproveSignature], ); - const { onPress: onConfirmPassword, onPasswordComplete, visible, onHideModal } = useUnlockModal(); + const { onPress: onConfirmPassword } = useUnlockModal(navigation); const onConfirm = useCallback(() => { switch (signMode) { @@ -140,7 +142,12 @@ export const EvmSignArea = (props: Props) => { - {signMode === AccountSignMode.QR && ( @@ -151,7 +158,6 @@ export const EvmSignArea = (props: Props) => { )} - ); }; diff --git a/src/screens/Confirmations/parts/Sign/Substrate.tsx b/src/screens/Confirmations/parts/Sign/Substrate.tsx index 9cd913d45..9fdbfa34e 100644 --- a/src/screens/Confirmations/parts/Sign/Substrate.tsx +++ b/src/screens/Confirmations/parts/Sign/Substrate.tsx @@ -1,4 +1,3 @@ -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import ConfirmationFooter from 'components/common/Confirmation/ConfirmationFooter'; import SignatureScanner from 'components/Scanner/SignatureScanner'; import useUnlockModal from 'hooks/modal/useUnlockModal'; @@ -18,6 +17,8 @@ import { Button } from 'components/design-system-ui'; import i18n from 'utils/i18n/i18n'; import { HIDE_MODAL_DURATION } from 'constants/index'; import { getButtonIcon } from 'utils/button'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; interface Props { account: AccountJson; @@ -35,7 +36,7 @@ const modeCanSignMessage: AccountSignMode[] = [AccountSignMode.QR, AccountSignMo export const SubstrateSignArea = (props: Props) => { const { account, id, payload } = props; - + const navigation = useNavigation(); const { chainInfoMap } = useSelector((state: RootState) => state.chainStore); const [loading, setLoading] = useState(false); @@ -104,7 +105,7 @@ export const SubstrateSignArea = (props: Props) => { setIsShowQr(true); }, []); - const { onPress: onConfirmPassword, onPasswordComplete, visible, onHideModal } = useUnlockModal(); + const { onPress: onConfirmPassword } = useUnlockModal(navigation); const onConfirm = useCallback(() => { switch (signMode) { @@ -158,7 +159,6 @@ export const SubstrateSignArea = (props: Props) => { )} - ); }; diff --git a/src/screens/Confirmations/variants/AuthorizeConfirmation/index.tsx b/src/screens/Confirmations/variants/AuthorizeConfirmation/index.tsx index 2a8f7a503..26bd9e218 100644 --- a/src/screens/Confirmations/variants/AuthorizeConfirmation/index.tsx +++ b/src/screens/Confirmations/variants/AuthorizeConfirmation/index.tsx @@ -4,7 +4,6 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { AccountAuthType, AccountJson, AuthorizeRequest } from '@subwallet/extension-base/background/types'; import { ALL_ACCOUNT_KEY } from '@subwallet/extension-base/constants'; import AccountItemWithName from 'components/common/Account/Item/AccountItemWithName'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import { ConfirmationContent, ConfirmationGeneralInfo } from 'components/common/Confirmation'; import ConfirmationFooter from 'components/common/Confirmation/ConfirmationFooter'; import { Button, Icon } from 'components/design-system-ui'; @@ -13,7 +12,7 @@ import useUnlockModal from 'hooks/modal/useUnlockModal'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { PlusCircle, ShieldSlash, XCircle } from 'phosphor-react-native'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { ScrollView, Text } from 'react-native'; +import { Text, View } from 'react-native'; import { approveAuthRequestV2, cancelAuthRequestV2, rejectAuthRequestV2 } from 'messaging/index'; import { useSelector } from 'react-redux'; import { RootStackParamList } from 'routes/index'; @@ -135,7 +134,7 @@ const AuthorizeConfirmation: React.FC = (props: Props) => { navigation.replace('CreateAccount', { keyTypes: types, isBack: true }); }, [accountAuthType, navigation]); - const { onPress: onPressCreateOne, onPasswordComplete, visible, onHideModal } = useUnlockModal(); + const { onPress: onPressCreateOne } = useUnlockModal(navigation); const onAccountSelect = useCallback( (address: string) => { @@ -179,10 +178,7 @@ const AuthorizeConfirmation: React.FC = (props: Props) => { {i18n.common.youDonotHaveAnyAcc(accountTypeMessage || '')} )} - + <> {visibleAccounts.length > 1 && ( = (props: Props) => { /> ))} - + {visibleAccounts.length > 0 ? ( @@ -239,7 +235,6 @@ const AuthorizeConfirmation: React.FC = (props: Props) => { icon={}> Create one - )} diff --git a/src/screens/Confirmations/variants/ConnectWalletConnectConfirmation/index.tsx b/src/screens/Confirmations/variants/ConnectWalletConnectConfirmation/index.tsx index 12b28487b..9ecb02bc6 100644 --- a/src/screens/Confirmations/variants/ConnectWalletConnectConfirmation/index.tsx +++ b/src/screens/Confirmations/variants/ConnectWalletConnectConfirmation/index.tsx @@ -25,7 +25,10 @@ import { WCAccountSelect } from 'components/WalletConnect/Account/WCAccountSelec import createStyle from './styles'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { WCNetworkSupported } from 'components/WalletConnect/Network/WCNetworkSupported'; -import { SVGImages } from 'assets/index'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from 'stores/index'; +import { Minimizer } from '../../../../NativeModules'; +import { updateIsDeepLinkConnect } from 'stores/base/Settings'; interface Props { request: WalletConnectSessionRequest; @@ -48,6 +51,8 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { const navigation = useNavigation(); const { params } = request.request; const toast = useToast(); + const { hasMasterPassword } = useSelector((state: RootState) => state.accountState); + const { isDeepLinkConnect } = useSelector((state: RootState) => state.settings); const nameSpaceNameMap = useMemo( (): Record => ({ [WALLET_CONNECT_EIP155_NAMESPACE]: 'EVM networks', @@ -57,6 +62,7 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { ); const theme = useSubWalletTheme().swThemes; const styles = useMemo(() => createStyle(theme), [theme]); + const dispatch = useDispatch(); const { isExpired, @@ -102,17 +108,27 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { .flat(); handleConfirm(request, selectedAccounts) + .then(() => { + console.log('isDeepLinkConnect', isDeepLinkConnect); + toast.show('Connect successfully', { type: 'success' }); + isDeepLinkConnect && Minimizer.goBack(); + dispatch(updateIsDeepLinkConnect(false)); + }) .catch(e => { toast.show((e as Error).message, { type: 'danger' }); }) .finally(() => { setLoading(false); }); - }, [namespaceAccounts, request, toast]); + }, [dispatch, isDeepLinkConnect, namespaceAccounts, request, toast]); const onAddAccount = useCallback(() => { - navigation.replace('CreateAccount', { keyTypes: convertKeyTypes(missingType), isBack: true }); - }, [missingType, navigation]); + if (hasMasterPassword) { + navigation.replace('CreateAccount', { keyTypes: convertKeyTypes(missingType), isBack: true }); + } else { + navigation.replace('CreatePassword', { pathName: 'CreateAccount', state: convertKeyTypes(missingType) }); + } + }, [hasMasterPassword, missingType, navigation]); const onApplyModal = useCallback( (namespace: string) => { @@ -137,17 +153,13 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { return ( - } - /> + {isUnSupportCase && ( @@ -158,8 +170,8 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { {!isUnSupportCase && isExpired && ( <> @@ -174,7 +186,7 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { {!supportOneChain && ( <> - {supportOneNamespace ? 'Networks' : nameSpaceNameMap[namespace]} + {supportOneNamespace ? i18n.common.networks : nameSpaceNameMap[namespace]} @@ -244,7 +256,7 @@ export const ConnectWalletConnectConfirmation = ({ request }: Props) => { disabled={loading} icon={} onPress={onAddAccount}> - {'Create one'} + {i18n.buttonTitles.createOne} )} diff --git a/src/screens/Home/Browser/BrowserOptionModal.tsx b/src/screens/Home/Browser/BrowserOptionModal.tsx index 4461555ac..0d15ead8b 100644 --- a/src/screens/Home/Browser/BrowserOptionModal.tsx +++ b/src/screens/Home/Browser/BrowserOptionModal.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef, useImperativeHandle, useState } from 'react'; +import React, { ForwardedRef, forwardRef, useImperativeHandle, useRef, useState } from 'react'; import { Linking, View } from 'react-native'; import { ArrowSquareUpRight, IconProps, Star, StarHalf } from 'phosphor-react-native'; import { SiteInfo } from 'stores/types'; @@ -9,10 +9,11 @@ import i18n from 'utils/i18n/i18n'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { SelectItem, SwModal } from 'components/design-system-ui'; import { searchDomain } from 'utils/browser'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; interface Props { visibleModal: boolean; - onClose: () => void; + setVisibleModal: (arg: boolean) => void; } interface OptionType { @@ -27,9 +28,12 @@ export interface BrowserOptionModalRef { onUpdateSiteInfo: (siteInfo: SiteInfo) => void; } -const Component = ({ visibleModal, onClose }: Props, ref: ForwardedRef) => { +const Component = ({ visibleModal, setVisibleModal }: Props, ref: ForwardedRef) => { const theme = useSubWalletTheme().swThemes; const bookmarks = useSelector((state: RootState) => state.browser.bookmarks); + const modalRef = useRef(null); + + const onClose = () => modalRef?.current?.close(); const [siteInfo, setSiteInfo] = useState({ url: '', @@ -75,11 +79,13 @@ const Component = ({ visibleModal, onClose }: Props, ref: ForwardedRef - + {OPTIONS.map(opt => ( { - setModalVisible(false); - }, []); - const onLoadProgress = ({ nativeEvent: { progress } }: WebViewProgressEvent) => { setProgressNumber(progress); }; @@ -519,7 +506,7 @@ const Component = ({ tabId, onOpenBrowserTabs, connectionTrigger }: Props, ref: - + ); }; diff --git a/src/screens/Home/Browser/BrowserTabsManager.tsx b/src/screens/Home/Browser/BrowserTabsManager.tsx index fd0f137c4..9c9175081 100644 --- a/src/screens/Home/Browser/BrowserTabsManager.tsx +++ b/src/screens/Home/Browser/BrowserTabsManager.tsx @@ -296,10 +296,6 @@ export const BrowserTabsManager = ({ route: { params } }: BrowserTabsManagerProp [activeTab], ); - const onCloseConnectWebsiteModal = useCallback(() => { - setConnectWebsiteModalVisible(false); - }, []); - const ConnectionTrigger = ( @@ -66,16 +72,19 @@ export function HistoryDetailModal({ data, onChangeModalVisible, modalVisible }: } return null; - }, [chainInfoMap, data, openBlockExplorer, theme.size]); + }, [chainInfoMap, data, openBlockExplorer]); return ( - {data && } + {data && } ); } diff --git a/src/screens/Home/History/index.tsx b/src/screens/Home/History/index.tsx index 609581919..00c862cd9 100644 --- a/src/screens/Home/History/index.tsx +++ b/src/screens/Home/History/index.tsx @@ -36,9 +36,10 @@ import { SectionListData } from 'react-native/Libraries/Lists/SectionList'; import Typography from '../../../components/design-system-ui/typography'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { EmptyList } from 'components/EmptyList'; -import { HistoryProps } from 'routes/index'; +import { HistoryProps, RootNavigationProps } from 'routes/index'; import { SortFunctionInterface } from 'types/ui-types'; import { SectionItem } from 'components/LazySectionList'; +import { useNavigation } from '@react-navigation/native'; type Props = {}; @@ -228,6 +229,7 @@ function History({ const [isOpenByLink, setIsOpenByLink] = useState(false); const [loading, setLoading] = useState(true); const language = useSelector((state: RootState) => state.mobileSettings.language) as LanguageType; + const navigation = useNavigation(); const accountMap = useMemo(() => { return accounts.reduce((accMap, cur) => { @@ -463,6 +465,7 @@ function History({ navigation.goBack()} items={historyList} title={i18n.header.history} placeholder={i18n.placeholder.searchHistory} @@ -478,7 +481,12 @@ function History({ flatListStyle={{ paddingHorizontal: theme.padding, paddingBottom: theme.padding }} /> - + ); } diff --git a/src/screens/Home/NFT/Collection/NftCollectionList.tsx b/src/screens/Home/NFT/Collection/NftCollectionList.tsx index 685cfe966..4a0e73c39 100644 --- a/src/screens/Home/NFT/Collection/NftCollectionList.tsx +++ b/src/screens/Home/NFT/Collection/NftCollectionList.tsx @@ -89,10 +89,8 @@ const NftCollectionList = () => { androidKeyboardVerticalOffset={0} numberColumns={2} searchMarginBottom={16} - isShowPlaceHolder={false} isShowMainHeader getItemLayout={getItemLayout} - needGapWithStatusBar={false} /> ); diff --git a/src/screens/Home/NFT/Detail/NftDetail.tsx b/src/screens/Home/NFT/Detail/NftDetail.tsx index 17be75c01..e39ee0869 100644 --- a/src/screens/Home/NFT/Detail/NftDetail.tsx +++ b/src/screens/Home/NFT/Detail/NftDetail.tsx @@ -232,9 +232,7 @@ const NftDetail = ({ navigation.goBack()}> diff --git a/src/screens/Home/NFT/Item/NftItemList.tsx b/src/screens/Home/NFT/Item/NftItemList.tsx index 16d5f8ab3..f060e79ca 100644 --- a/src/screens/Home/NFT/Item/NftItemList.tsx +++ b/src/screens/Home/NFT/Item/NftItemList.tsx @@ -159,15 +159,15 @@ const NftItemList = ({ onCancelModal: onCancelDelete, visible: deleteVisible, onCompleteModal: onCompleteDeleteModal, + setVisible, } = useConfirmModal(handeDelete); return ( navigation.goBack()} isShowMainHeader - isShowPlaceHolder={false} - needGapWithStatusBar={false} autoFocus={false} showLeftBtn={true} renderItem={renderItem} @@ -202,6 +202,7 @@ const NftItemList = ({ message={i18n.message.deleteNftMessage} onCompleteModal={onCompleteDeleteModal} onCancelModal={onCancelDelete} + setVisible={setVisible} /> ); diff --git a/src/screens/Home/Staking/Balance/StakingBalanceList.tsx b/src/screens/Home/Staking/Balance/StakingBalanceList.tsx index 7140fb155..2ec7b63e7 100644 --- a/src/screens/Home/Staking/Balance/StakingBalanceList.tsx +++ b/src/screens/Home/Staking/Balance/StakingBalanceList.tsx @@ -12,7 +12,6 @@ import { reloadCron } from 'messaging/index'; import { useRefresh } from 'hooks/useRefresh'; import useGetStakingList from 'hooks/screen/Home/Staking/useGetStakingList'; import { StakingDetailModal } from 'screens/Home/Staking/StakingDetail/StakingDetailModal'; -import StakingActionModal from 'screens/Home/Staking/StakingDetail/StakingActionModal'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { StakingType } from '@subwallet/extension-base/background/KoniTypes'; import { RootNavigationProps } from 'routes/index'; @@ -74,7 +73,6 @@ const StakingBalanceList = () => { const [isRefresh, refresh] = useRefresh(); const [selectedItem, setSelectedItem] = useState(undefined); const [detailModalVisible, setDetailModalVisible] = useState(false); - const [moreActionModalVisible, setMoreActionModalVisible] = useState(false); const handleOnPress = useCallback((stakingData: StakingDataType): (() => void) => { return () => { Keyboard.dismiss(); @@ -156,31 +154,18 @@ const StakingBalanceList = () => { }} /> } - isShowPlaceHolder={false} - needGapWithStatusBar={false} /> {selectedItem && ( setDetailModalVisible(false)} - onOpenMoreActionModal={() => setMoreActionModalVisible(true)} chainStakingMetadata={selectedItem.chainStakingMetadata} nominatorMetadata={selectedItem.nominatorMetadata} rewardItem={selectedItem.reward} staking={selectedItem.staking} + setDetailModalVisible={setDetailModalVisible} /> )} - - setMoreActionModalVisible(false)} - openModal={() => setMoreActionModalVisible(true)} - visible={moreActionModalVisible} - chainStakingMetadata={selectedItem?.chainStakingMetadata} - nominatorMetadata={selectedItem?.nominatorMetadata} - staking={selectedItem?.staking} - reward={selectedItem?.reward} - /> ); }; diff --git a/src/screens/Home/Staking/StakingDetail/StakingActionModal.tsx b/src/screens/Home/Staking/StakingDetail/StakingActionModal.tsx index 7e31de3ac..167743e2f 100644 --- a/src/screens/Home/Staking/StakingDetail/StakingActionModal.tsx +++ b/src/screens/Home/Staking/StakingDetail/StakingActionModal.tsx @@ -26,15 +26,17 @@ import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import i18n from 'utils/i18n/i18n'; import { CustomToast } from 'components/design-system-ui/toast'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; interface Props { visible: boolean; - closeModal: () => void; openModal: () => void; staking?: StakingItem; reward?: StakingRewardItem; chainStakingMetadata?: ChainStakingMetadata; nominatorMetadata?: NominatorMetadata; + setModalVisible: (arg: boolean) => void; + stakingDetailModalRef: React.RefObject; } type ActionListType = { @@ -49,14 +51,17 @@ const OFFSET_BOTTOM = deviceHeight - STATUS_BAR_HEIGHT - 140; const StakingActionModal = (props: Props) => { const theme = useSubWalletTheme().swThemes; - const { chainStakingMetadata, nominatorMetadata, closeModal, visible, reward } = props; + const { chainStakingMetadata, nominatorMetadata, visible, reward, setModalVisible, stakingDetailModalRef } = props; const toastRef = useRef(null); const navigation = useNavigation(); const [selected, setSelected] = useState(); const { currentAccount } = useSelector((state: RootState) => state.accountState); const onClickButton = usePreCheckReadOnly(toastRef, currentAccount?.address); + const modalRef = useRef(null); + const closeModal = useCallback(() => modalRef?.current?.close(), []); const unStakeAction = useCallback(() => { + stakingDetailModalRef?.current?.close(); closeModal(); navigation.navigate('Drawer', { screen: 'TransactionAction', @@ -68,9 +73,10 @@ const StakingActionModal = (props: Props) => { }, }, }); - }, [chainStakingMetadata?.chain, chainStakingMetadata?.type, closeModal, navigation]); + }, [chainStakingMetadata?.chain, chainStakingMetadata?.type, closeModal, navigation, stakingDetailModalRef]); const stakeAction = useCallback(() => { + stakingDetailModalRef?.current?.close(); closeModal(); navigation.navigate('Drawer', { screen: 'TransactionAction', @@ -82,7 +88,7 @@ const StakingActionModal = (props: Props) => { }, }, }); - }, [chainStakingMetadata?.type, closeModal, navigation, nominatorMetadata?.chain]); + }, [chainStakingMetadata?.type, closeModal, navigation, nominatorMetadata?.chain, stakingDetailModalRef]); const handleWithdrawalAction = useCallback(() => { if (!nominatorMetadata) { @@ -90,6 +96,7 @@ const StakingActionModal = (props: Props) => { return; } + stakingDetailModalRef?.current?.close(); closeModal(); setSelected(undefined); @@ -103,9 +110,17 @@ const StakingActionModal = (props: Props) => { }, }, }); - }, [nominatorMetadata, closeModal, navigation, chainStakingMetadata?.type, chainStakingMetadata?.chain]); + }, [ + nominatorMetadata, + stakingDetailModalRef, + closeModal, + navigation, + chainStakingMetadata?.type, + chainStakingMetadata?.chain, + ]); const cancelUnstakeAction = useCallback(() => { + stakingDetailModalRef?.current?.close(); closeModal(); navigation.navigate('Drawer', { screen: 'TransactionAction', @@ -117,7 +132,7 @@ const StakingActionModal = (props: Props) => { }, }, }); - }, [chainStakingMetadata?.chain, chainStakingMetadata?.type, closeModal, navigation]); + }, [chainStakingMetadata?.chain, chainStakingMetadata?.type, closeModal, navigation, stakingDetailModalRef]); const handleClaimRewardAction = useCallback(() => { if (!nominatorMetadata) { @@ -127,6 +142,7 @@ const StakingActionModal = (props: Props) => { } setSelected(undefined); + stakingDetailModalRef?.current?.close(); closeModal(); navigation.navigate('Drawer', { screen: 'TransactionAction', @@ -138,7 +154,14 @@ const StakingActionModal = (props: Props) => { }, }, }); - }, [chainStakingMetadata?.chain, chainStakingMetadata?.type, closeModal, nominatorMetadata, navigation]); + }, [ + nominatorMetadata, + stakingDetailModalRef, + closeModal, + navigation, + chainStakingMetadata?.type, + chainStakingMetadata?.chain, + ]); const availableActions = useMemo(() => { if (!nominatorMetadata) { @@ -209,9 +232,12 @@ const StakingActionModal = (props: Props) => { return ( {actionList.map(item => { const actionDisable = !availableActions.includes(item.action); diff --git a/src/screens/Home/Staking/StakingDetail/StakingDetailModal/index.tsx b/src/screens/Home/Staking/StakingDetail/StakingDetailModal/index.tsx index f0bd5019e..8d10f7a02 100644 --- a/src/screens/Home/Staking/StakingDetail/StakingDetailModal/index.tsx +++ b/src/screens/Home/Staking/StakingDetail/StakingDetailModal/index.tsx @@ -23,7 +23,7 @@ import { _getChainSubstrateAddressPrefix, } from '@subwallet/extension-base/services/chain-service/utils'; import useGetAccountByAddress from 'hooks/screen/useGetAccountByAddress'; -import { ALL_KEY, deviceHeight, HIDE_MODAL_DURATION, TOAST_DURATION } from 'constants/index'; +import { ALL_KEY, deviceHeight, TOAST_DURATION } from 'constants/index'; import { useNavigation } from '@react-navigation/native'; import { StakingStatusUi } from 'constants/stakingStatusUi'; import MetaInfo from 'components/MetaInfo'; @@ -33,7 +33,7 @@ import { ScrollView, TouchableHighlight, View } from 'react-native'; import { Avatar, Button, Icon, Number, SwModal, Typography } from 'components/design-system-ui'; import { ArrowCircleUpRight, DotsThree } from 'phosphor-react-native'; import { ALL_ACCOUNT_KEY } from '@subwallet/extension-base/constants'; -import { FontMedium, MarginBottomForSubmitButton, STATUS_BAR_HEIGHT } from 'styles/sharedStyles'; +import { FontMedium, STATUS_BAR_HEIGHT } from 'styles/sharedStyles'; import { ThemeTypes } from 'styles/themes'; import { isAccountAll } from 'utils/accountAll'; import { RootNavigationProps } from 'routes/index'; @@ -42,15 +42,16 @@ import Toast from 'react-native-toast-notifications'; import { ColorMap } from 'styles/color'; import i18n from 'utils/i18n/i18n'; import { CustomToast } from 'components/design-system-ui/toast'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; +import StakingActionModal from 'screens/Home/Staking/StakingDetail/StakingActionModal'; interface Props { nominatorMetadata?: NominatorMetadata; chainStakingMetadata?: ChainStakingMetadata; staking: StakingItem; rewardItem?: StakingRewardItem; - onCloseDetailModal?: () => void; - onOpenMoreActionModal?: () => void; modalVisible: boolean; + setDetailModalVisible: (arg: boolean) => void; } export const getUnstakingInfo = (unstakings: UnstakingInfo[], address: string) => { @@ -92,9 +93,9 @@ export const StakingDetailModal = ({ nominatorMetadata, rewardItem, staking, - onCloseDetailModal, - onOpenMoreActionModal, + setDetailModalVisible, }: Props) => { + const [moreActionModalVisible, setMoreActionModalVisible] = useState(false); const showingOption = isShowNominationByValidator(nominatorMetadata?.chain || ''); const isRelayChain = _STAKING_CHAIN_GROUP.relay.includes(nominatorMetadata?.chain || ''); const modalTitle = @@ -115,6 +116,9 @@ export const StakingDetailModal = ({ nominated: i18n.filterOptions.nominated, pooled: i18n.filterOptions.pooled, }; + const modalRef = useRef(null); + + const onCloseDetailModal = useCallback(() => modalRef?.current?.close(), []); const onClickStakeMoreBtn = useCallback(() => { onCloseDetailModal && onCloseDetailModal(); @@ -151,11 +155,8 @@ export const StakingDetailModal = ({ }, [chainStakingMetadata?.chain, chainStakingMetadata?.type, navigation, onCloseDetailModal]); const onClickMoreAction = useCallback(() => { - onCloseDetailModal && onCloseDetailModal(); - setTimeout(() => { - onOpenMoreActionModal && onOpenMoreActionModal(); - }, HIDE_MODAL_DURATION); - }, [onCloseDetailModal, onOpenMoreActionModal]); + setMoreActionModalVisible(true); + }, []); const onClickSeeMoreBtn = useCallback(() => { setSeeMore(true); @@ -274,7 +275,7 @@ export const StakingDetailModal = ({ const footer = () => { return ( - + - )} - {seeMore && ( - <> - - {chainStakingMetadata?.expectedReturn && ( - - )} + { + + } + { + } + + + + + {!seeMore && ( + + )} - {!!chainStakingMetadata?.unstakingPeriod && ( - + + {chainStakingMetadata?.expectedReturn && ( + + )} + + - {getUnstakingPeriod(chainStakingMetadata?.unstakingPeriod)} - - )} - - - {showingOption === 'showByValue' && - nominatorMetadata?.nominations && - nominatorMetadata?.nominations.length > 0 && - currentAccount?.address !== ALL_ACCOUNT_KEY && ( - <> - - - - - <> - {nominatorMetadata?.nominations.map(item => { - if (isRelayChain && nominatorMetadata?.type === StakingType.NOMINATED) { + /> + + {!!chainStakingMetadata?.unstakingPeriod && ( + + {getUnstakingPeriod(chainStakingMetadata?.unstakingPeriod)} + + )} + + + {showingOption === 'showByValue' && + nominatorMetadata?.nominations && + nominatorMetadata?.nominations.length > 0 && + currentAccount?.address !== ALL_ACCOUNT_KEY && ( + <> + + + + + <> + {nominatorMetadata?.nominations.map(item => { + if (isRelayChain && nominatorMetadata?.type === StakingType.NOMINATED) { + return ( + + renderAccountItemLabel(theme, item.validatorAddress, item.validatorIdentity) + } + /> + ); + } + return ( - renderAccountItemLabel(theme, item.validatorAddress, item.validatorIdentity) } + suffix={staking.nativeToken} + value={item.activeStake || ''} + valueColorSchema={'gray'} /> ); - } - - return ( + })} + + + + )} + + {(showingOption === 'showByValue' || showingOption === 'mixed') && + nominatorMetadata?.unstakings && + nominatorMetadata?.unstakings.length > 0 && + currentAccount?.address !== ALL_ACCOUNT_KEY && ( + <> + + + + + <> + {nominatorMetadata?.unstakings.map((item, index) => ( - renderAccountItemLabel(theme, item.validatorAddress, item.validatorIdentity) + key={`${item.validatorAddress || item.chain}-${item.status}-${ + item.claimable + }-${index}`} + label={ + getWaitingTime(item.waitingTime) ? getWaitingTime(item.waitingTime) : 'Withdraw' } suffix={staking.nativeToken} - value={item.activeStake || ''} + value={item.claimable || ''} valueColorSchema={'gray'} /> - ); - })} - - - - )} - - {(showingOption === 'showByValue' || showingOption === 'mixed') && - nominatorMetadata?.unstakings && - nominatorMetadata?.unstakings.length > 0 && - currentAccount?.address !== ALL_ACCOUNT_KEY && ( - <> - - - - - <> - {nominatorMetadata?.unstakings.map((item, index) => ( - - ))} - - - - )} - - {(showingOption === 'showByValidator' || showingOption === 'mixed') && - nominatorMetadata?.nominations && - nominatorMetadata?.nominations.length > 0 && - currentAccount?.address !== ALL_ACCOUNT_KEY && ( - <>{nominatorMetadata.nominations.map((item, index) => renderUnstakingInfo(item, index))} - )} - - )} - - - - - } - /> - - + ))} + + + + )} + + {(showingOption === 'showByValidator' || showingOption === 'mixed') && + nominatorMetadata?.nominations && + nominatorMetadata?.nominations.length > 0 && + currentAccount?.address !== ALL_ACCOUNT_KEY && ( + <>{nominatorMetadata.nominations.map((item, index) => renderUnstakingInfo(item, index))} + )} + + )} + + + + + } + /> + + + + setMoreActionModalVisible(true)} + visible={moreActionModalVisible} + chainStakingMetadata={chainStakingMetadata} + nominatorMetadata={nominatorMetadata} + staking={staking} + reward={rewardItem} + /> + ); }; diff --git a/src/screens/Home/index.tsx b/src/screens/Home/index.tsx index 76e8e0e13..6e643983b 100644 --- a/src/screens/Home/index.tsx +++ b/src/screens/Home/index.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { BottomTabBarButtonProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import StakingScreen from './Staking/StakingScreen'; -import { Platform, StyleSheet, TouchableOpacity, View } from 'react-native'; +import { Linking, Platform, StyleSheet, TouchableOpacity, View } from 'react-native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Aperture, Database, Globe, Rocket, Wallet } from 'phosphor-react-native'; import { CryptoScreen } from 'screens/Home/Crypto'; @@ -18,7 +18,7 @@ import { HomeStackParamList } from 'routes/home'; import NFTStackScreen from 'screens/Home/NFT/NFTStackScreen'; import withPageWrapper from 'components/pageWrapper'; import RequestCreateMasterPasswordModal from 'screens/MasterPassword/RequestCreateMasterPasswordModal'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import { ActivityIndicator } from 'components/design-system-ui'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; @@ -29,6 +29,12 @@ import { Settings } from 'screens/Settings'; import i18n from 'utils/i18n/i18n'; import { RootStackParamList } from 'routes/index'; import { handleDeeplinkOnFirstOpen } from 'utils/deeplink'; +import urlParse from 'url-parse'; +import { getProtocol } from 'utils/browser'; +import { updateIsDeepLinkConnect } from 'stores/base/Settings'; +import queryString from 'querystring'; +import { connectWalletConnect } from 'utils/walletConnect'; +import { useToast } from 'react-native-toast-notifications'; interface tabbarIconColor { color: string; @@ -151,6 +157,7 @@ const MainScreen = () => { }; const Wrapper = () => { + const isEmptyAccounts = useCheckEmptyAccounts(); const Drawer = createDrawerNavigator(); return ( { drawerType: 'front', swipeEnabled: false, }}> + {isEmptyAccounts && } ); @@ -174,14 +182,55 @@ export const Home = ({ navigation }: Props) => { const { hasMasterPassword, isReady } = useSelector((state: RootState) => state.accountState); const { isLocked } = useAppLock(); const [isLoading, setLoading] = useState(true); + const isFirstOpen = useRef(true); + const toast = useToast(); + const dispatch = useDispatch(); useEffect(() => { - if (isReady) { - handleDeeplinkOnFirstOpen(navigation); - } if (isReady && isLoading) { setTimeout(() => setLoading(false), 500); } + }, [isReady, isLoading]); + + useEffect(() => { + if (isReady && !isLoading) { + handleDeeplinkOnFirstOpen(navigation); + isFirstOpen.current = false; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isReady, isLoading]); + + useEffect(() => { + if (isReady && !isLoading) { + if (isFirstOpen.current) { + return; + } + Linking.addEventListener('url', ({ url }) => { + const urlParsed = new urlParse(url); + if (getProtocol(url) === 'subwallet') { + if (urlParsed.hostname === 'wc') { + dispatch(updateIsDeepLinkConnect(true)); + if (urlParsed.query.startsWith('?requestId')) { + return; + } + const decodedWcUrl = queryString.decode(urlParsed.query.slice(5)); + const finalWcUrl = Object.keys(decodedWcUrl)[0]; + connectWalletConnect(finalWcUrl, toast); + } + } else if (getProtocol(url) === 'https') { + if (urlParsed.pathname.split('/')[1] === 'wc') { + dispatch(updateIsDeepLinkConnect(true)); + if (urlParsed.query.startsWith('?requestId')) { + return; + } + const decodedWcUrl = queryString.decode(urlParsed.query.slice(5)); + const finalWcUrl = Object.keys(decodedWcUrl)[0]; + connectWalletConnect(finalWcUrl, toast); + } + } + }); + return () => Linking.removeAllListeners('url'); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isReady, isLoading]); @@ -195,7 +244,7 @@ export const Home = ({ navigation }: Props) => { return ( <> - {isEmptyAccounts ? : } + {!isLocked && } ); diff --git a/src/screens/LockScreen.tsx b/src/screens/LockScreen.tsx index 25b1e27b7..cedc76aae 100644 --- a/src/screens/LockScreen.tsx +++ b/src/screens/LockScreen.tsx @@ -17,6 +17,8 @@ import { Button, WarningText } from 'components/design-system-ui'; import { resetWallet } from 'messaging/index'; import { useToast } from 'react-native-toast-notifications'; import { ForgotPasswordModal } from 'components/common/ForgotPasswordModal'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; const optionalConfigObject = { title: 'Authentication Required', // Android @@ -42,6 +44,7 @@ export const LockScreen = () => { const toast = useToast(); const [resetAccLoading, setAccLoading] = useState(false); const [eraseAllLoading, setEraseAllLoading] = useState(false); + const navigation = useNavigation(); const unlockWithBiometric = useAppLock().unlockWithBiometric; @@ -61,20 +64,20 @@ export const LockScreen = () => { .catch(() => setAuthMethod('pinCode')); } setAuthMethod(_authMethod); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [faceIdEnabled, unlockWithBiometric]); useEffect(() => { if (value.length === 6) { if (unlock(value)) { setValue(''); + navigation.canGoBack() ? navigation.goBack() : navigation.navigate('Home'); } else { setValue(''); setError(i18n.errorMessage.invalidPinCode); ref.current?.focus(); } } - }, [ref, unlock, value]); + }, [navigation, ref, unlock, value]); const onReset = useCallback( (resetAll: boolean) => { @@ -107,61 +110,72 @@ export const LockScreen = () => { ); return ( - - - - - - + + + + + + + - - {i18n.welcomeScreen.welcomeBackTitle} - - {authMethod === 'pinCode' && ( - <> - - {i18n.common.enterPinToUnlock} - - - - )} + + {i18n.welcomeScreen.welcomeBackTitle} + + {authMethod === 'pinCode' && ( + <> + + {i18n.common.enterPinToUnlock} + + + + )} - {!!error && } + {!!error && } - + - setModalVisible(false)} - resetAccLoading={resetAccLoading} - eraseAllLoading={eraseAllLoading} - /> - - - + setModalVisible(false)} + resetAccLoading={resetAccLoading} + eraseAllLoading={eraseAllLoading} + /> + + + + ); }; diff --git a/src/screens/MasterPassword/ApplyMasterPassword/index.tsx b/src/screens/MasterPassword/ApplyMasterPassword/index.tsx index c563ef6f6..48efcb766 100644 --- a/src/screens/MasterPassword/ApplyMasterPassword/index.tsx +++ b/src/screens/MasterPassword/ApplyMasterPassword/index.tsx @@ -22,10 +22,11 @@ import useGoHome from 'hooks/screen/useGoHome'; import i18n from 'utils/i18n/i18n'; import DeleteModal from 'components/common/Modal/DeleteModal'; import useHandlerHardwareBackPress from 'hooks/screen/useHandlerHardwareBackPress'; -import { UnlockModal } from 'components/common/Modal/UnlockModal'; import useUnlockModal from 'hooks/modal/useUnlockModal'; import { SelectedActionType } from 'stores/types'; import { noop } from 'utils/function'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; type PageStep = 'Introduction' | 'Migrate' | 'Done'; @@ -51,6 +52,7 @@ const ApplyMasterPassword = () => { const [loading, setLoading] = useState(false); const [isError, setIsError] = useState(false); const selectedAction = useRef(); + const navigation = useNavigation(); useHandlerHardwareBackPress(true); const migrateAddressRef = useRef(''); const formConfig: FormControlConfig = { @@ -207,6 +209,7 @@ const ApplyMasterPassword = () => { onCancelModal: onCancelDelete, visible: deleteVisible, onCompleteModal: onCompleteDeleteModal, + setVisible, } = useConfirmModal(onDelete); const onChangePasswordValue = (currentValue: string) => { @@ -238,7 +241,7 @@ const ApplyMasterPassword = () => { } }, []); - const { onPress, visible, onPasswordComplete, onHideModal } = useUnlockModal(); + const { onPress } = useUnlockModal(navigation); const onPressActionButton = useCallback( (action: SelectedActionType) => { @@ -350,6 +353,7 @@ const ApplyMasterPassword = () => { {step === 'Done' && } { /> {renderFooterButton()} - - ); diff --git a/src/screens/MasterPassword/RequestCreateMasterPasswordModal/index.tsx b/src/screens/MasterPassword/RequestCreateMasterPasswordModal/index.tsx index 874567bd7..bc88603af 100644 --- a/src/screens/MasterPassword/RequestCreateMasterPasswordModal/index.tsx +++ b/src/screens/MasterPassword/RequestCreateMasterPasswordModal/index.tsx @@ -24,7 +24,7 @@ const RequestCreateMasterPasswordModal = ({ visible }: Props) => { }; return ( - + {i18n.message.requestCreateMasterPassword} diff --git a/src/screens/NetworkSettingDetail.tsx b/src/screens/NetworkSettingDetail.tsx index d34fc6cb4..ac98f4d4c 100644 --- a/src/screens/NetworkSettingDetail.tsx +++ b/src/screens/NetworkSettingDetail.tsx @@ -259,6 +259,7 @@ export const NetworkSettingDetail = ({ onCancelModal: onCancelDelete, visible: deleteVisible, onCompleteModal: onCompleteDeleteModal, + setVisible, } = useConfirmModal(handeDeleteCustomToken); useEffect(() => { @@ -378,6 +379,7 @@ export const NetworkSettingDetail = ({ message={i18n.message.deleteNetworkMessage} onCompleteModal={onCompleteDeleteModal} onCancelModal={onCancelDelete} + setVisible={setVisible} /> diff --git a/src/screens/NetworksSetting.tsx b/src/screens/NetworksSetting.tsx index 97c8a896e..d1efcc356 100644 --- a/src/screens/NetworksSetting.tsx +++ b/src/screens/NetworksSetting.tsx @@ -200,6 +200,7 @@ export const NetworksSetting = ({}: Props) => { setToggleItem(false); }, }} + onPressBack={() => navigation.goBack()} items={currentChainList} title={i18n.header.manageNetworks} placeholder={i18n.placeholder.searchNetwork} diff --git a/src/screens/Settings/AddressBook/index.tsx b/src/screens/Settings/AddressBook/index.tsx index 8815d7c37..9a7bad727 100644 --- a/src/screens/Settings/AddressBook/index.tsx +++ b/src/screens/Settings/AddressBook/index.tsx @@ -20,6 +20,8 @@ import AccountItemWithName from 'components/common/Account/Item/AccountItemWithN import AccountItemBase from 'components/common/Account/Item/AccountItemBase'; import { Icon } from 'components/design-system-ui'; import createStylesheet from './style'; +import { useNavigation } from '@react-navigation/native'; +import { RootNavigationProps } from 'routes/index'; enum AccountGroup { CONTACT = 'contact', @@ -95,6 +97,7 @@ const sortFunction = (a: AccountItem, b: AccountItem) => { export const ManageAddressBook = () => { const theme = useSubWalletTheme().swThemes; + const navigation = useNavigation(); const { contacts, recent } = useSelector((state: RootState) => state.accountState); const [isShowAddContactModal, setShowAddContactModal] = useState(false); const [isShowEditContactModal, setShowEditContactModal] = useState(false); @@ -218,14 +221,6 @@ export const ManageAddressBook = () => { }; }, []); - const closeAddContactModal = useCallback(() => { - setShowAddContactModal(false); - }, []); - - const closeEditContactModal = useCallback(() => { - setShowEditContactModal(false); - }, []); - return ( <> { sortFunction={sortFunction} searchMarginBottom={theme.sizeXS} flatListStyle={stylesheet.flatListStyle} + onPressBack={() => navigation.goBack()} /> - + {selectedItem && ( )} diff --git a/src/screens/Settings/General/index.tsx b/src/screens/Settings/General/index.tsx index 4e94ea097..7cfa28c9c 100644 --- a/src/screens/Settings/General/index.tsx +++ b/src/screens/Settings/General/index.tsx @@ -10,7 +10,7 @@ import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { Icon, SelectItem } from 'components/design-system-ui'; import { useToast } from 'react-native-toast-notifications'; -const containerStyle = { ...sharedStyles.layoutContainer, paddingTop: 16 }; +const containerStyle = { ...sharedStyles.layoutContainer, paddingTop: 16, gap: 8 }; export const GeneralSettings = () => { const theme = useSubWalletTheme().swThemes; const toast = useToast(); diff --git a/src/screens/Settings/Languages.tsx b/src/screens/Settings/Languages.tsx index 58ae8d440..71ebfdb88 100644 --- a/src/screens/Settings/Languages.tsx +++ b/src/screens/Settings/Languages.tsx @@ -46,7 +46,7 @@ export const Languages = () => { // @ts-ignore const renderItem = ({ item }) => { return ( - + { return ( navigation.goBack()} title={i18n.title.networks} items={sortedNetworkConfigList} renderItem={renderItem} diff --git a/src/screens/Settings/Security/DAppAccess/DAppAccessDetailScreen.tsx b/src/screens/Settings/Security/DAppAccess/DAppAccessDetailScreen.tsx index 87577cdf2..8dffcc425 100644 --- a/src/screens/Settings/Security/DAppAccess/DAppAccessDetailScreen.tsx +++ b/src/screens/Settings/Security/DAppAccess/DAppAccessDetailScreen.tsx @@ -221,6 +221,7 @@ const Content = ({ origin, accountAuthType, authInfo }: Props) => { title={origin} flatListStyle={{ gap: theme.paddingXS }} autoFocus={false} + onPressBack={() => navigation.goBack()} beforeListItem={renderBeforeListItem()} items={accountItems} searchFunction={searchFunction} @@ -238,7 +239,7 @@ const Content = ({ origin, accountAuthType, authInfo }: Props) => { setModalVisible(false)} + setModalVisible={setModalVisible} /> } /> diff --git a/src/screens/Settings/Security/DAppAccess/MoreOptionModal.tsx b/src/screens/Settings/Security/DAppAccess/MoreOptionModal.tsx index 5f732eafd..0c0dff33e 100644 --- a/src/screens/Settings/Security/DAppAccess/MoreOptionModal.tsx +++ b/src/screens/Settings/Security/DAppAccess/MoreOptionModal.tsx @@ -1,8 +1,9 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { View } from 'react-native'; import i18n from 'utils/i18n/i18n'; import { SelectItem, SwModal } from 'components/design-system-ui'; import { IconProps } from 'phosphor-react-native'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; export type MoreOptionItemType = { key: string; @@ -15,16 +16,19 @@ export type MoreOptionItemType = { interface Props { modalVisible: boolean; moreOptionList: MoreOptionItemType[]; - onChangeModalVisible: () => void; + setModalVisible: (arg: boolean) => void; } -export const MoreOptionModal = ({ modalVisible, moreOptionList, onChangeModalVisible }: Props) => { +export const MoreOptionModal = ({ modalVisible, moreOptionList, setModalVisible }: Props) => { + const modalBaseV2Ref = useRef(null); return ( - + {moreOptionList.map(item => ( { title={i18n.header.manageWebsiteAccess} autoFocus={false} items={dAppItems} + onPressBack={() => navigation.goBack()} searchFunction={searchFunction} flatListStyle={{ gap: 8 }} placeholder={i18n.placeholder.searchOrEnterWebsite} @@ -162,7 +163,7 @@ export const DAppAccessScreen = () => { setModalVisible(false)} + setModalVisible={setModalVisible} /> } /> diff --git a/src/screens/Settings/Security/index.tsx b/src/screens/Settings/Security/index.tsx index 8f6b4b3d9..415050bfb 100644 --- a/src/screens/Settings/Security/index.tsx +++ b/src/screens/Settings/Security/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useRef, useState } from 'react'; import { SubScreenContainer } from 'components/SubScreenContainer'; import { useNavigation } from '@react-navigation/native'; import { RootNavigationProps } from 'routes/index'; @@ -13,6 +13,7 @@ import i18n from 'utils/i18n/i18n'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { Icon, SelectItem, SwModal } from 'components/design-system-ui'; import { useToast } from 'react-native-toast-notifications'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; export const Security = () => { const theme = useSubWalletTheme().swThemes; @@ -23,6 +24,8 @@ export const Security = () => { const [iShowAutoLockModal, setIsShowAutoLockModal] = useState(false); const navigation = useNavigation(); const dispatch = useDispatch(); + const modalRef = useRef(null); + const AUTO_LOCK_LIST: { text: string; value: number | undefined }[] = [ { text: i18n.settings.immediately, @@ -76,7 +79,7 @@ export const Security = () => { const onChangeAutoLockTime = (value: number | undefined) => { dispatch(updateAutoLockTime(value)); - setIsShowAutoLockModal(false); + modalRef?.current?.close(); }; return ( @@ -104,68 +107,72 @@ export const Security = () => { onValueChange={onValueChangeFaceId} /> - navigation.navigate('PinCode', { screen: 'ChangePinCode' })} - rightIcon={ - - } - disabled={!pinCode} - /> + + navigation.navigate('PinCode', { screen: 'ChangePinCode' })} + rightIcon={ + + } + disabled={!pinCode} + /> - navigation.navigate('ChangePassword')} - rightIcon={} - /> + navigation.navigate('ChangePassword')} + rightIcon={} + /> - navigation.navigate('DAppAccess')} - rightIcon={} - /> + navigation.navigate('DAppAccess')} + rightIcon={} + /> - { - toast.hideAll(); - toast.show(i18n.notificationMessage.comingSoon); - }} - rightIcon={} - /> - setIsShowAutoLockModal(true)} - rightIcon={ - - } - disabled={!pinCode} - /> + { + toast.hideAll(); + toast.show(i18n.notificationMessage.comingSoon); + }} + rightIcon={} + /> + setIsShowAutoLockModal(true)} + rightIcon={ + + } + disabled={!pinCode} + /> + setIsShowAutoLockModal(false)} - onBackButtonPress={() => setIsShowAutoLockModal(false)} + onBackButtonPress={() => modalRef?.current?.close()} modalTitle={i18n.common.autoLock}> - + {AUTO_LOCK_LIST.map(item => ( { const [loading, setLoading] = useState(false); const theme = useSubWalletTheme().swThemes; const toast = useToast(); + const [isShowQrModalVisible, setQrModalVisible] = useState(false); const formConfig = useMemo((): FormControlConfig => { return { @@ -59,7 +60,9 @@ export const ConnectWalletConnect = () => { ? i18n.errorMessage.connectionAlreadyExist : i18n.errorMessage.failToAddConnection; + toast.hideAll(); toast.show(message, { type: 'danger' }); + setLoading(false); }); }; @@ -68,7 +71,7 @@ export const ConnectWalletConnect = () => { }); return ( - navigation.goBack()} title={i18n.header.walletConnect}> + navigation.navigate('Home')} title={i18n.header.walletConnect}> @@ -92,6 +95,9 @@ export const ConnectWalletConnect = () => { placeholder={i18n.placeholder.connectWalletPlaceholder} disabled={loading} onSubmitEditing={formState.errors.uri.length > 0 ? () => Keyboard.dismiss() : onSubmitField('uri')} + isShowQrModalVisible={isShowQrModalVisible} + setQrModalVisible={setQrModalVisible} + setLoading={setLoading} /> {formState.errors.uri.length > 0 && @@ -109,7 +115,7 @@ export const ConnectWalletConnect = () => { color={!formState.isValidated.uri || loading ? theme.colorTextLight5 : theme.colorWhite} /> }> - Connect + {i18n.common.connect} ); diff --git a/src/screens/Settings/WalletConnect/ConnectionDetail.tsx b/src/screens/Settings/WalletConnect/ConnectionDetail.tsx index 82564a1d7..110f26382 100644 --- a/src/screens/Settings/WalletConnect/ConnectionDetail.tsx +++ b/src/screens/Settings/WalletConnect/ConnectionDetail.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { useToast } from 'react-native-toast-notifications'; import { stripUrl } from '@subwallet/extension-base/utils'; import { ContainerWithSubHeader } from 'components/ContainerWithSubHeader'; @@ -10,7 +10,7 @@ import { chainsToWalletConnectChainInfos, getWCAccountList } from 'utils/walletC import { useSelector } from 'react-redux'; import { RootState } from 'stores/index'; import { useNavigation } from '@react-navigation/native'; -import { ConnectDetailProps } from 'routes/index'; +import { ConnectDetailProps, RootNavigationProps } from 'routes/index'; import { ScrollView, TouchableOpacity, View } from 'react-native'; import { useSubWalletTheme } from 'hooks/useSubWalletTheme'; import { FontMedium } from 'styles/sharedStyles'; @@ -24,6 +24,7 @@ import { EmptyList } from 'components/EmptyList'; import { SessionTypes } from '@walletconnect/types'; import { BUTTON_ACTIVE_OPACITY } from 'constants/index'; import { WCNetworkItem } from 'components/WalletConnect/Network/WCNetworkItem'; +import { SWModalRefProps } from 'components/design-system-ui/modal/ModalBaseV2'; export const ConnectionDetail = ({ route: { @@ -33,8 +34,9 @@ export const ConnectionDetail = ({ const { sessions } = useSelector((state: RootState) => state.walletConnect); const theme = useSubWalletTheme().swThemes; const currentSession: SessionTypes.Struct = sessions[topic]; + const networkDetailModalRef = useRef(null); - const navigation = useNavigation(); + const navigation = useNavigation(); const toast = useToast(); const [disconnectModalVisible, setDisconnectModalVisible] = useState(false); const [networkModalVisible, setNetworkModalVisible] = useState(false); @@ -85,7 +87,7 @@ export const ConnectionDetail = ({ .finally(() => { setLoading(false); setDisconnectModalVisible(false); - navigation.goBack(); + navigation.navigate('ConnectList', { isDelete: true }); }); }; @@ -139,9 +141,11 @@ export const ConnectionDetail = ({ {i18n.message.connectedAccounts(accountItems.length)} - {accountItems.map(item => ( - - ))} + + {accountItems.map(item => ( + + ))} + - } - /> + <> + {(items?.length || isDelete) && ( + navigation.goBack()} + flatListStyle={{ gap: 8 }} + renderItem={renderItem} + renderListEmptyComponent={renderEmptyList} + searchFunction={searchFunc} + afterListItem={ + + } + /> + )} + + { + setError(undefined); + setIsScanning(false); + !(items?.length || isDelete) && navigation.goBack(); + }} + onChangeAddress={onScanAddress} + error={error} + isShowError={true} + /> + ); }; diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 7f508620a..77bcd76d2 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState } from 'react'; import { SubScreenContainer } from 'components/SubScreenContainer'; import { useNavigation } from '@react-navigation/native'; -import { Linking, ScrollView, StyleProp } from 'react-native'; +import { Linking, ScrollView, StyleProp, View } from 'react-native'; import Text from 'components/Text'; import { ArrowSquareOut, @@ -112,7 +112,7 @@ export const Settings = ({ navigation: drawerNavigation }: DrawerContentComponen icon: Clock, title: i18n.header.walletConnect, rightIcon: , - onPress: () => navigation.navigate('ConnectList'), + onPress: () => navigation.navigate('ConnectList', { isDelete: false }), backgroundColor: '#004BFF', }, ], @@ -214,64 +214,72 @@ export const Settings = ({ navigation: drawerNavigation }: DrawerContentComponen - {settingList[0].map(setting => ( - - ))} + + {settingList[0].map(setting => ( + + ))} + {i18n.settings.networksAndTokens.toUpperCase()} - {settingList[1].map(setting => ( - } - /> - ) : undefined - } - icon={setting.icon} - backgroundColor={setting.backgroundColor} - onPress={setting.onPress} - /> - ))} + + {settingList[1].map(setting => ( + } + /> + ) : undefined + } + icon={setting.icon} + backgroundColor={setting.backgroundColor} + onPress={setting.onPress} + /> + ))} + {i18n.settings.communityAndSupport.toUpperCase()} - {settingList[2].map(setting => ( - - ))} + + {settingList[2].map(setting => ( + + ))} + {i18n.settings.aboutSubwallet.toUpperCase()} - {settingList[3].map(setting => ( - - ))} + + {settingList[3].map(setting => ( + + ))} +