diff --git a/.github/workflows/android-build.yaml b/.github/workflows/android-build.yaml index 536c43302..1753d60a0 100644 --- a/.github/workflows/android-build.yaml +++ b/.github/workflows/android-build.yaml @@ -174,13 +174,13 @@ jobs: - name: Rename the rust lib for uniffi compatibility working-directory: ./rust/target/${{ env.TARGET }}/release - run: mv ./libzingo.so ./libuniffi_zingo.so + run: mv ./libzingo.so ./libzingo.so - name: Upload native rust uses: actions/upload-artifact@v4 with: name: native-android-uniffi-${{ env.ARCH }}-${{ env.CACHE-KEY }} - path: rust/target/${{ env.TARGET }}/release/libuniffi_zingo.so + path: rust/target/${{ env.TARGET }}/release/libzingo.so cache-native-android-uniffi: name: Cache native rust diff --git a/.github/workflows/inactive/android-ubuntu-foss-script-artifact.yaml b/.github/workflows/inactive/android-ubuntu-foss-script-artifact.yaml index 7b076432f..5d1e9ed5d 100644 --- a/.github/workflows/inactive/android-ubuntu-foss-script-artifact.yaml +++ b/.github/workflows/inactive/android-ubuntu-foss-script-artifact.yaml @@ -69,10 +69,10 @@ jobs: sudo mkdir -p ../../android/app/src/main/jniLibs/x86_64 sudo mkdir -p ../../android/app/build/generated/source/uniffi/debug/java/uniffi/zingo sudo mkdir -p ../../android/app/build/generated/source/uniffi/release/java/uniffi/zingo - sudo cp /opt/jniLibs/x86_64/libuniffi_zingo.so ../../android/app/src/main/jniLibs/x86_64/libuniffi_zingo.so - sudo cp /opt/jniLibs/x86/libuniffi_zingo.so ../../android/app/src/main/jniLibs/x86/libuniffi_zingo.so - sudo cp /opt/jniLibs/armeabi-v7a/libuniffi_zingo.so ../../android/app/src/main/jniLibs/armeabi-v7a/libuniffi_zingo.so - sudo cp /opt/jniLibs/arm64-v8a/libuniffi_zingo.so ../../android/app/src/main/jniLibs/arm64-v8a/libuniffi_zingo.so + sudo cp /opt/jniLibs/x86_64/libzingo.so ../../android/app/src/main/jniLibs/x86_64/libzingo.so + sudo cp /opt/jniLibs/x86/libzingo.so ../../android/app/src/main/jniLibs/x86/libzingo.so + sudo cp /opt/jniLibs/armeabi-v7a/libzingo.so ../../android/app/src/main/jniLibs/armeabi-v7a/libzingo.so + sudo cp /opt/jniLibs/arm64-v8a/libzingo.so ../../android/app/src/main/jniLibs/arm64-v8a/libzingo.so sudo cp /opt/jniLibs/kotlin/zingo.kt ../../android/app/build/generated/source/uniffi/debug/java/uniffi/zingo/zingo.kt sudo cp /opt/jniLibs/kotlin/zingo.kt ../../android/app/build/generated/source/uniffi/release/java/uniffi/zingo/zingo.kt diff --git a/.gitignore b/.gitignore index a29f40a58..c3084f5f9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ ios/vendor/ # Exclude compiled libs android/app/src/main/jniLibs android/app/release/ -ios/libuniffi_zingo.a +ios/libzingo.a ios/zingo.swift ios/zingoFFI.h ios/zingoFFI.modulemap diff --git a/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/MainApplication.kt b/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/MainApplication.kt index e1a6e42fc..e53c172d9 100644 --- a/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/MainApplication.kt +++ b/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/MainApplication.kt @@ -56,7 +56,7 @@ class MainApplication : Application(), ReactApplication { } init { - System.loadLibrary("uniffi_zingo") + System.loadLibrary("zingo") } } } diff --git a/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/RPCModule.kt b/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/RPCModule.kt index ece06ff6d..9e2969277 100644 --- a/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/RPCModule.kt +++ b/android/app/src/main/java/org/ZingoLabs/ZingoDelegator/RPCModule.kt @@ -65,38 +65,39 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC } fun saveWalletFile(): Boolean { - try { + return try { uniffi.zingo.initLogging() - // Get the encoded wallet file - val b64encoded: String = uniffi.zingo.saveToB64() - if (b64encoded.lowercase().startsWith(ErrorPrefix.value)) { - // with error don't save the file. Obviously. - Log.e("MAIN", "Error: [Native] Couldn't save the wallet. $b64encoded") - return false + val b64encoded = uniffi.zingo.saveToB64() + + // No data to save + if (b64encoded.isNullOrEmpty()) { + Log.i("MAIN", "[Native] No need to save the wallet.") + return true } - // Log.i("MAIN", b64encoded) - val correct = uniffi.zingo.checkB64(b64encoded) - if (correct == "false") { - Log.e("MAIN", "Error: [Native] Couldn't save the wallet. The Encoded content is incorrect: $b64encoded") + val correct: Boolean = uniffi.zingo.checkB64(b64encoded) + if (!correct) { + Log.e( + "MAIN", + "Error: [Native] Couldn't save the wallet. The encoded content is incorrect." + ) return false } - // check if the content is correct. Stored Decoded. val fileBytes = Base64.decode(b64encoded, Base64.NO_WRAP) Log.i("MAIN", "[Native] file size: ${fileBytes.size} bytes") - if (fileBytes.size > 0) { + if (fileBytes.isNotEmpty()) { writeFile(WalletFileName.value, fileBytes) - return true } else { - Log.e("MAIN", "[Native] No need to save the wallet.") - return true + Log.i("MAIN", "[Native] No need to save the wallet (empty file).") } + + true } catch (e: Exception) { - Log.e("MAIN", "Error: [Native] Unexpected error. Couldn't save the wallet. $e") - return false + Log.e("MAIN", "Error: [Native] Unexpected error. Couldn't save the wallet.", e) + false } } @@ -140,19 +141,26 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC } @ReactMethod - fun createNewWallet(serveruri: String, chainhint: String, performancelevel: String, minconfirmations: String, promise: Promise) { + fun createNewWallet( + serveruri: String, + chainhint: String, + performancelevel: String, + minconfirmations: String, + promise: Promise, + ) { try { uniffi.zingo.initLogging() - // Create a seed - val resp = uniffi.zingo.initNew(serveruri, chainhint, performancelevel, minconfirmations.toUInt()) - // Log.i("MAIN-Seed", resp) + val result = uniffi.zingo.initNew( + serveruri, + chainhint, + performancelevel, + minconfirmations.toUInt() + ) - if (!resp.lowercase().startsWith(ErrorPrefix.value)) { - saveWalletFile() - } + val value = result.value - promise.resolve(resp) + promise.resolve(value) } catch (e: Exception) { val errorMessage = "Error: [Native] create new wallet: ${e.localizedMessage}" Log.e("MAIN", errorMessage, e) @@ -161,18 +169,29 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC } @ReactMethod - fun restoreWalletFromSeed(seed: String, birthday: String, serveruri: String, chainhint: String, performancelevel: String, minconfirmations: String, promise: Promise) { + fun restoreWalletFromSeed( + seed: String, + birthday: String, + serveruri: String, + chainhint: String, + performancelevel: String, + minconfirmations: String, + promise: Promise, + ) { try { uniffi.zingo.initLogging() - val resp = uniffi.zingo.initFromSeed(seed, birthday.toUInt(), serveruri, chainhint, performancelevel, minconfirmations.toUInt()) - // Log.i("MAIN", resp) - - if (!resp.lowercase().startsWith(ErrorPrefix.value)) { - saveWalletFile() - } - - promise.resolve(resp) + val result = uniffi.zingo.initFromSeed( + seed, + birthday.toUInt(), + serveruri, + chainhint, + performancelevel, + minconfirmations.toUInt() + ) + val value = result.value + + promise.resolve(value) } catch (e: Exception) { val errorMessage = "Error: [Native] restore wallet from seed: ${e.localizedMessage}" Log.e("MAIN", errorMessage, e) @@ -181,31 +200,48 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC } @ReactMethod - fun restoreWalletFromUfvk(ufvk: String, birthday: String, serveruri: String, chainhint: String, performancelevel: String, minconfirmations: String, promise: Promise) { + fun restoreWalletFromUfvk( + ufvk: String, + birthday: String, + serveruri: String, + chainhint: String, + performancelevel: String, + minconfirmations: String, + promise: Promise, + ) { try { uniffi.zingo.initLogging() - val resp = uniffi.zingo.initFromUfvk(ufvk, birthday.toUInt(), serveruri, chainhint, performancelevel, minconfirmations.toUInt()) - // Log.i("MAIN", resp) - - if (!resp.lowercase().startsWith(ErrorPrefix.value)) { - saveWalletFile() - } - - promise.resolve(resp) + val result = uniffi.zingo.initFromUfvk( + ufvk, + birthday.toUInt(), + serveruri, + chainhint, + performancelevel, + minconfirmations.toUInt() + ) + val value = result.value + + promise.resolve(value) } catch (e: Exception) { val errorMessage = "Error: [Native] restore wallet from ufvk: ${e.localizedMessage}" Log.e("MAIN", errorMessage, e) promise.resolve(errorMessage) } -} + } @ReactMethod fun loadExistingWallet(serveruri: String, chainhint: String, performancelevel: String, minconfirmations: String, promise: Promise) { promise.resolve(loadExistingWalletNative(serveruri, chainhint, performancelevel, minconfirmations)) } - fun loadExistingWalletNative(serveruri: String, chainhint: String, performancelevel: String, minconfirmations: String): String { + // TODO: https://github.com/zingolabs/crosslink-mobile-client/issues/8 + fun loadExistingWalletNative( + serveruri: String, + chainhint: String, + performancelevel: String, + minconfirmations: String + ): String { try { // Read the file val fileBytes = readFile(WalletFileName.value) @@ -367,9 +403,17 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC Log.i("MAIN", "file size: $middle8w") - val resp = uniffi.zingo.initFromB64(fileb64.toString(), serveruri, chainhint, performancelevel, minconfirmations.toUInt()) - - return resp + val result = uniffi.zingo.initFromB64( + fileb64.toString(), + serveruri, + chainhint, + performancelevel, + minconfirmations.toUInt() + ) + + // UniFFI now returns InitResult; JS expects a String here, + // so we keep returning the underlying value. + return result.value } catch (e: Exception) { val errorMessage = "Error: [Native] load existing wallet: ${e.localizedMessage}" Log.e("MAIN", errorMessage, e) @@ -377,6 +421,7 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC } } + @ReactMethod fun restoreExistingWalletBackup(promise: Promise) { val fileBytesBackup: ByteArray @@ -504,7 +549,7 @@ class RPCModule internal constructor(private val reactContext: ReactApplicationC CoroutineScope(Dispatchers.IO).launch { try { uniffi.zingo.initLogging() - val resp = uniffi.zingo.getLatestBlockServer(serveruri) + val resp = uniffi.zingo.getLatestBlockHeightServer(serveruri) withContext(Dispatchers.Main) { promise.resolve(resp) diff --git a/app/LoadedApp/LoadedApp.tsx b/app/LoadedApp/LoadedApp.tsx index 52c8b29ae..ec57d0c10 100644 --- a/app/LoadedApp/LoadedApp.tsx +++ b/app/LoadedApp/LoadedApp.tsx @@ -11,17 +11,33 @@ import { Platform, ActivityIndicator, } from 'react-native'; -import { BottomTabBarButtonProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { + BottomTabBarButtonProps, + createBottomTabNavigator, +} from '@react-navigation/bottom-tabs'; import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; -import { faDownload, faCog, faRefresh, faPaperPlane, faClockRotateLeft, faComments } from '@fortawesome/free-solid-svg-icons'; +import { + faDownload, + faCog, + faRefresh, + faPaperPlane, + faClockRotateLeft, + faComments, +} from '@fortawesome/free-solid-svg-icons'; import { useTheme } from '@react-navigation/native'; import { I18n } from 'i18n-js'; import * as RNLocalize from 'react-native-localize'; import { isEqual } from 'lodash'; import { StackScreenProps } from '@react-navigation/stack'; import { LoadingAppNavigationState, AppDrawerParamList } from '../types'; -import NetInfo, { NetInfoSubscription, NetInfoState } from '@react-native-community/netinfo/src/index'; -import { activateKeepAwake, deactivateKeepAwake } from '@sayem314/react-native-keep-awake'; +import NetInfo, { + NetInfoSubscription, + NetInfoState, +} from '@react-native-community/netinfo/src/index'; +import { + activateKeepAwake, + deactivateKeepAwake, +} from '@sayem314/react-native-keep-awake'; import RPC from '../rpc'; import RPCModule from '../RPCModule'; @@ -77,7 +93,10 @@ import { RPCSeedType } from '../rpc/types/RPCSeedType'; import { Launching } from '../LoadingApp'; import simpleBiometrics from '../simpleBiometrics'; import ShowAddressAlertAsync from '../../components/Send/components/ShowAddressAlertAsync'; -import { createUpdateRecoveryWalletInfo, removeRecoveryWalletInfo } from '../recoveryWalletInfov10'; +import { + createUpdateRecoveryWalletInfo, + removeRecoveryWalletInfo, +} from '../recoveryWalletInfov10'; import History from '../../components/History'; import Send from '../../components/Send'; @@ -107,7 +126,9 @@ const Rescan = React.lazy(() => import('../../components/Rescan')); const Pools = React.lazy(() => import('../../components/Pools')); const Insight = React.lazy(() => import('../../components/Insight')); const ShowUfvk = React.lazy(() => import('../../components/Ufvk/ShowUfvk')); -const ComputingTxContent = React.lazy(() => import('./components/ComputingTxContent')); +const ComputingTxContent = React.lazy( + () => import('./components/ComputingTxContent'), +); const en = require('../translations/en.json'); const es = require('../translations/es.json'); @@ -122,7 +143,10 @@ const Stack = createNativeStackNavigator(); //const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); type LoadedAppProps = { - navigation: StackScreenProps['navigation']; + navigation: StackScreenProps< + AppStackParamList, + RouteEnum.LoadedApp + >['navigation']; route: StackScreenProps['route']; toggleTheme: (mode: ModeEnum) => void; }; @@ -137,16 +161,27 @@ export default function LoadedApp(props: LoadedAppProps) { const [loading, setLoading] = useState(true); const [language, setLanguage] = useState(LanguageEnum.en); - const [currency, setCurrency] = useState(CurrencyEnum.noCurrency); - const [lightWalletServer, setLightWalletServer] = useState(SERVER_DEFAULT_0); - const [selectLightWalletServer, setSelectLightWalletServer] = useState(SelectServerEnum.custom); - const [validatorServer, setValidatorServer] = useState(SERVER_DEFAULT_0); - const [selectValidatorServer, setSelectValidatorServer] = useState(SelectServerEnum.custom); + const [currency, setCurrency] = useState( + CurrencyEnum.noCurrency, + ); + const [lightWalletServer, setLightWalletServer] = + useState(SERVER_DEFAULT_0); + const [selectLightWalletServer, setSelectLightWalletServer] = + useState(SelectServerEnum.custom); + const [validatorServer, setValidatorServer] = + useState(SERVER_DEFAULT_0); + const [selectValidatorServer, setSelectValidatorServer] = + useState(SelectServerEnum.custom); const [sendAll, setSendAll] = useState(false); const [donation, setDonation] = useState(false); const [privacy, setPrivacy] = useState(false); const [mode, setMode] = useState(ModeEnum.advanced); // by default advanced - const [background, setBackground] = useState({ batches: 0, message: '', date: 0, dateEnd: 0 }); + const [background, setBackground] = useState({ + batches: 0, + message: '', + date: 0, + dateEnd: 0, + }); const [security, setSecurity] = useState({ startApp: true, // activate only this foregroundApp: false, @@ -159,9 +194,12 @@ export default function LoadedApp(props: LoadedAppProps) { }); const [rescanMenu, setRescanMenu] = useState(false); // by default the App store the seed phrase & birthday on KeyChain/KeyStore (Device). - const [recoveryWalletInfoOnDevice, setRecoveryWalletInfoOnDevice] = useState(true); - const [performanceLevel, setPerformanceLevel] = useState(RPCPerformanceLevelEnum.Medium); - const [zenniesDonationAddress, setZenniesDonationAddress] = useState(''); + const [recoveryWalletInfoOnDevice, setRecoveryWalletInfoOnDevice] = + useState(true); + const [performanceLevel, setPerformanceLevel] = + useState(RPCPerformanceLevelEnum.Medium); + const [zenniesDonationAddress, setZenniesDonationAddress] = + useState(''); const file = useMemo( () => ({ en: en, @@ -174,30 +212,54 @@ export default function LoadedApp(props: LoadedAppProps) { ); const i18n = useMemo(() => new I18n(file), [file]); - const translate: (key: string) => TranslateType = (key: string) => i18n.t(key); - - const readOnly = !!props.route.params && props.route.params.readOnly !== undefined ? props.route.params.readOnly : false; - const orchardPool = !!props.route.params && props.route.params.orchardPool !== undefined ? props.route.params.orchardPool : false; - const saplingPool = !!props.route.params && props.route.params.saplingPool !== undefined ? props.route.params.saplingPool : false; - const transparentPool = !!props.route.params && props.route.params.transparentPool !== undefined ? props.route.params.transparentPool : false; - const firstLaunchingMessage = !!props.route.params && props.route.params.firstLaunchingMessage !== undefined ? props.route.params.firstLaunchingMessage : LaunchingModeEnum.opening; + const translate: (key: string) => TranslateType = (key: string) => + i18n.t(key); + + const readOnly = + !!props.route.params && props.route.params.readOnly !== undefined + ? props.route.params.readOnly + : false; + const orchardPool = + !!props.route.params && props.route.params.orchardPool !== undefined + ? props.route.params.orchardPool + : false; + const saplingPool = + !!props.route.params && props.route.params.saplingPool !== undefined + ? props.route.params.saplingPool + : false; + const transparentPool = + !!props.route.params && props.route.params.transparentPool !== undefined + ? props.route.params.transparentPool + : false; + const firstLaunchingMessage = + !!props.route.params && + props.route.params.firstLaunchingMessage !== undefined + ? props.route.params.firstLaunchingMessage + : LaunchingModeEnum.opening; useEffect(() => { (async () => { // fallback if no available language fits const fallback = { languageTag: LanguageEnum.en, isRTL: false }; - const { languageTag, isRTL } = RNLocalize.findBestLanguageTag(Object.keys(file)) || fallback; + const { languageTag, isRTL } = + RNLocalize.findBestLanguageTag(Object.keys(file)) || fallback; // update layout direction I18nManager.forceRTL(isRTL); // If the App is mounting this component, // I know I have to reset the firstInstall prop in settings. - await SettingsFileImpl.writeSettings(SettingsNameEnum.firstInstall, false); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.firstInstall, + false, + ); // If the App is mounting this component, I know I have to update the version prop in settings. - await SettingsFileImpl.writeSettings(SettingsNameEnum.version, translate('version') as string); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.version, + translate('version') as string, + ); //I have to check what language is in the settings const settings = await SettingsFileImpl.readSettings(); @@ -206,7 +268,10 @@ export default function LoadedApp(props: LoadedAppProps) { // for testing //await delay(5000); - if (settings.mode === ModeEnum.basic || settings.mode === ModeEnum.advanced) { + if ( + settings.mode === ModeEnum.basic || + settings.mode === ModeEnum.advanced + ) { setMode(settings.mode); props.toggleTheme(settings.mode); } else { @@ -238,18 +303,26 @@ export default function LoadedApp(props: LoadedAppProps) { await SettingsFileImpl.writeSettings(SettingsNameEnum.language, lang); //console.log('apploaded NO settings', languageTag); } - if (settings.currency === CurrencyEnum.noCurrency || - settings.currency === CurrencyEnum.USDCurrency || - settings.currency === CurrencyEnum.USDTORCurrency) { + if ( + settings.currency === CurrencyEnum.noCurrency || + settings.currency === CurrencyEnum.USDCurrency || + settings.currency === CurrencyEnum.USDTORCurrency + ) { setCurrency(settings.currency); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.currency, currency); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.currency, + currency, + ); } // lightwallet server if (settings.lightWalletserver) { setLightWalletServer(settings.lightWalletserver); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.lightWalletServer, lightWalletServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.lightWalletServer, + lightWalletServer, + ); } if ( settings.selectLightWalletServer === SelectServerEnum.auto || @@ -259,13 +332,19 @@ export default function LoadedApp(props: LoadedAppProps) { ) { setSelectLightWalletServer(settings.selectLightWalletServer); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.selectLightWalletServer, selectLightWalletServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.selectLightWalletServer, + selectLightWalletServer, + ); } // validator server if (settings.validatorServer) { setValidatorServer(settings.validatorServer); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.validatorServer, validatorServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.validatorServer, + validatorServer, + ); } if ( settings.selectValidatorServer === SelectServerEnum.auto || @@ -275,7 +354,10 @@ export default function LoadedApp(props: LoadedAppProps) { ) { setSelectValidatorServer(settings.selectValidatorServer); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.selectValidatorServer, selectValidatorServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.selectValidatorServer, + selectValidatorServer, + ); } if (settings.sendAll === true || settings.sendAll === false) { setSendAll(settings.sendAll); @@ -285,7 +367,10 @@ export default function LoadedApp(props: LoadedAppProps) { if (settings.donation === true || settings.donation === false) { setDonation(settings.donation); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.donation, donation); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.donation, + donation, + ); } if (settings.privacy === true || settings.privacy === false) { setPrivacy(settings.privacy); @@ -295,17 +380,29 @@ export default function LoadedApp(props: LoadedAppProps) { if (settings.security) { setSecurity(settings.security); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.security, security); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.security, + security, + ); } if (settings.rescanMenu === true || settings.rescanMenu === false) { setRescanMenu(settings.rescanMenu); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.rescanMenu, rescanMenu); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.rescanMenu, + rescanMenu, + ); } - if (settings.recoveryWalletInfoOnDevice === true || settings.recoveryWalletInfoOnDevice === false) { + if ( + settings.recoveryWalletInfoOnDevice === true || + settings.recoveryWalletInfoOnDevice === false + ) { setRecoveryWalletInfoOnDevice(settings.recoveryWalletInfoOnDevice); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.recoveryWalletInfoOnDevice, recoveryWalletInfoOnDevice); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.recoveryWalletInfoOnDevice, + recoveryWalletInfoOnDevice, + ); } if ( settings.performanceLevel === RPCPerformanceLevelEnum.High || @@ -315,14 +412,19 @@ export default function LoadedApp(props: LoadedAppProps) { ) { setPerformanceLevel(settings.performanceLevel); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.performanceLevel, performanceLevel); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.performanceLevel, + performanceLevel, + ); } // reading background task info const backgroundJson = await BackgroundFileImpl.readBackground(); setBackground(backgroundJson); - const zenniesAddress = await Utils.getZenniesDonationAddress(lightWalletServer.chainName); + const zenniesAddress = await Utils.getZenniesDonationAddress( + lightWalletServer.chainName, + ); setZenniesDonationAddress(zenniesAddress); setLoading(false); @@ -334,7 +436,11 @@ export default function LoadedApp(props: LoadedAppProps) { if (loading) { return ( - + ); } else { return ( @@ -383,14 +489,18 @@ const Loading: React.FC = ({ backgroundColor, spinColor }) => { alignItems: 'center', backgroundColor: backgroundColor, height: '100%', - }}> + }} + > ); }; type LoadedAppClassProps = { - navigationApp: StackScreenProps['navigation']; + navigationApp: StackScreenProps< + AppStackParamList, + RouteEnum.LoadedApp + >['navigation']; route: StackScreenProps['route']; toggleTheme: (mode: ModeEnum) => void; translate: (key: string) => TranslateType; @@ -420,14 +530,23 @@ type LoadedAppClassProps = { type LoadedAppClassState = AppStateLoaded & AppContextLoaded; -const TabPressable: React.FC = ({ colors, ...props }) => { - return ; +const TabPressable: React.FC< + BottomTabBarButtonProps & { colors: ThemeType } +> = ({ colors, ...props }) => { + return ( + + ); }; -const renderTabPressable = (colors: ThemeType) => (props: BottomTabBarButtonProps) => - ; +const renderTabPressable = + (colors: ThemeType) => (props: BottomTabBarButtonProps) => ( + + ); -export class LoadedAppClass extends Component { +export class LoadedAppClass extends Component< + LoadedAppClassProps, + LoadedAppClassState +> { rpc: RPC; appstate: NativeEventSubscription; linking: EmitterSubscription; @@ -495,7 +614,10 @@ export class LoadedAppClass extends Component { - //console.log('LOADED', 'prior', this.state.appStateStatus, 'next', nextAppState); - // let's catch the prior value - const priorAppState = this.state.appStateStatus; - if (Platform.OS === GlobalConst.platformOSios) { + this.appstate = AppState.addEventListener( + EventListenerEnum.change, + async nextAppState => { + //console.log('LOADED', 'prior', this.state.appStateStatus, 'next', nextAppState); + // let's catch the prior value + const priorAppState = this.state.appStateStatus; + if (Platform.OS === GlobalConst.platformOSios) { + if ( + (priorAppState === AppStateStatusEnum.inactive && + nextAppState === AppStateStatusEnum.active) || + (priorAppState === AppStateStatusEnum.active && + nextAppState === AppStateStatusEnum.inactive) + ) { + //console.log('LOADED SAVED IOS do nothing', nextAppState); + this.setState({ appStateStatus: nextAppState }); + return; + } + if ( + priorAppState === AppStateStatusEnum.inactive && + nextAppState === AppStateStatusEnum.background + ) { + console.log('App LOADED IOS is gone to the background!'); + this.setState({ appStateStatus: nextAppState }); + // setting value for background task Android + await AsyncStorage.setItem(GlobalConst.background, GlobalConst.yes); + //console.log('&&&&& background yes in storage &&&&&'); + await this.rpc.clearTimers(); + //console.log('clear timers IOS'); + this.setSyncingStatus({} as RPCSyncStatusType); + //console.log('clear sync status state'); + //console.log('LOADED SAVED IOS background', nextAppState); + // We need to save the wallet file here because + // sometimes the App can lose the last synced chunk + await RPCModule.doSave(); + return; + } + } + if (Platform.OS === GlobalConst.platformOSandroid) { + if (priorAppState !== nextAppState) { + //console.log('LOADED SAVED Android', nextAppState); + this.setState({ appStateStatus: nextAppState }); + } + } if ( - (priorAppState === AppStateStatusEnum.inactive && nextAppState === AppStateStatusEnum.active) || - (priorAppState === AppStateStatusEnum.active && nextAppState === AppStateStatusEnum.inactive) + (priorAppState === AppStateStatusEnum.inactive || + priorAppState === AppStateStatusEnum.background) && + nextAppState === AppStateStatusEnum.active ) { - //console.log('LOADED SAVED IOS do nothing', nextAppState); - this.setState({ appStateStatus: nextAppState }); - return; - } - if (priorAppState === AppStateStatusEnum.inactive && nextAppState === AppStateStatusEnum.background) { - console.log('App LOADED IOS is gone to the background!'); - this.setState({ appStateStatus: nextAppState }); + //console.log('App LOADED Android & IOS has come to the foreground!'); + if (Platform.OS === GlobalConst.platformOSios) { + //console.log('LOADED SAVED IOS foreground', nextAppState); + this.setState({ appStateStatus: nextAppState }); + } + // (PIN or TouchID or FaceID) + const resultBio = this.state.security.foregroundApp + ? await simpleBiometrics({ translate: this.state.translate }) + : true; + // can be: + // - true -> the user do pass the authentication + // - false -> the user do NOT pass the authentication + // - undefined -> no biometric authentication available -> Passcode -> Nothing. + //console.log('BIOMETRIC FOREGROUND --------> ', resultBio); + if (resultBio === false) { + this.navigateToLoadingApp({ + startingApp: true, + biometricsFailed: true, + }); + } else { + // reading background task info + await this.fetchBackgroundSyncing(); + // setting value for background task Android + await AsyncStorage.setItem(GlobalConst.background, GlobalConst.no); + //console.log('&&&&& background no in storage &&&&&'); + // needs this because when the App go from back to fore + // it have to re-launch all the tasks. + await this.rpc.clearTimers(); + await this.rpc.configure(); + //console.log('configure start timers Android & IOS'); + if ( + this.state.backgroundError && + (this.state.backgroundError.title || + this.state.backgroundError.error) + ) { + Alert.alert( + this.state.backgroundError.title, + this.state.backgroundError.error, + ); + this.setBackgroundError('', ''); + } + } + } else if ( + priorAppState === AppStateStatusEnum.active && + (nextAppState === AppStateStatusEnum.inactive || + nextAppState === AppStateStatusEnum.background) + ) { + console.log('App LOADED is gone to the background!'); // setting value for background task Android await AsyncStorage.setItem(GlobalConst.background, GlobalConst.yes); //console.log('&&&&& background yes in storage &&&&&'); await this.rpc.clearTimers(); - //console.log('clear timers IOS'); + //console.log('clear timers'); this.setSyncingStatus({} as RPCSyncStatusType); //console.log('clear sync status state'); - //console.log('LOADED SAVED IOS background', nextAppState); // We need to save the wallet file here because // sometimes the App can lose the last synced chunk await RPCModule.doSave(); - return; - } - } - if (Platform.OS === GlobalConst.platformOSandroid) { - if (priorAppState !== nextAppState) { - //console.log('LOADED SAVED Android', nextAppState); - this.setState({ appStateStatus: nextAppState }); - } - } - if ( - (priorAppState === AppStateStatusEnum.inactive || priorAppState === AppStateStatusEnum.background) && - nextAppState === AppStateStatusEnum.active - ) { - //console.log('App LOADED Android & IOS has come to the foreground!'); - if (Platform.OS === GlobalConst.platformOSios) { - //console.log('LOADED SAVED IOS foreground', nextAppState); - this.setState({ appStateStatus: nextAppState }); - } - // (PIN or TouchID or FaceID) - const resultBio = this.state.security.foregroundApp - ? await simpleBiometrics({ translate: this.state.translate }) - : true; - // can be: - // - true -> the user do pass the authentication - // - false -> the user do NOT pass the authentication - // - undefined -> no biometric authentication available -> Passcode -> Nothing. - //console.log('BIOMETRIC FOREGROUND --------> ', resultBio); - if (resultBio === false) { - this.navigateToLoadingApp({ startingApp: true, biometricsFailed: true }); - } else { - // reading background task info - await this.fetchBackgroundSyncing(); - // setting value for background task Android - await AsyncStorage.setItem(GlobalConst.background, GlobalConst.no); - //console.log('&&&&& background no in storage &&&&&'); - // needs this because when the App go from back to fore - // it have to re-launch all the tasks. - await this.rpc.clearTimers(); - await this.rpc.configure(); - //console.log('configure start timers Android & IOS'); - if (this.state.backgroundError && (this.state.backgroundError.title || this.state.backgroundError.error)) { - Alert.alert(this.state.backgroundError.title, this.state.backgroundError.error); - this.setBackgroundError('', ''); - } - } - } else if ( - priorAppState === AppStateStatusEnum.active && - (nextAppState === AppStateStatusEnum.inactive || nextAppState === AppStateStatusEnum.background) - ) { - console.log('App LOADED is gone to the background!'); - // setting value for background task Android - await AsyncStorage.setItem(GlobalConst.background, GlobalConst.yes); - //console.log('&&&&& background yes in storage &&&&&'); - await this.rpc.clearTimers(); - //console.log('clear timers'); - this.setSyncingStatus({} as RPCSyncStatusType); - //console.log('clear sync status state'); - // We need to save the wallet file here because - // sometimes the App can lose the last synced chunk - await RPCModule.doSave(); - if (Platform.OS === GlobalConst.platformOSios) { - //console.log('LOADED SAVED IOS background', nextAppState); - this.setState({ appStateStatus: nextAppState }); - } - } else { - if (Platform.OS === GlobalConst.platformOSios) { - if (priorAppState !== nextAppState) { - //console.log('LOADED SAVED IOS', nextAppState); + if (Platform.OS === GlobalConst.platformOSios) { + //console.log('LOADED SAVED IOS background', nextAppState); this.setState({ appStateStatus: nextAppState }); } + } else { + if (Platform.OS === GlobalConst.platformOSios) { + if (priorAppState !== nextAppState) { + //console.log('LOADED SAVED IOS', nextAppState); + this.setState({ appStateStatus: nextAppState }); + } + } } - } - }); + }, + ); const initialUrl = await Linking.getInitialURL(); console.log('INITIAL URI', initialUrl); @@ -666,51 +809,61 @@ export class LoadedAppClass extends Component { - console.log('EVENT LISTENER URI', url); - if (url !== null) { - this.readUrl(url); - } - - this.state.navigationHome?.navigate(RouteEnum.HomeStack, { - screen: RouteEnum.Send, - }); - }); + this.linking = Linking.addEventListener( + EventListenerEnum.url, + async ({ url }) => { + console.log('EVENT LISTENER URI', url); + if (url !== null) { + this.readUrl(url); + } - this.unsubscribeNetInfo = NetInfo.addEventListener(async (state: NetInfoState) => { - const { isConnected, type, isConnectionExpensive } = this.state.netInfo; - if ( - isConnected !== state.isConnected || - type !== state.type || - isConnectionExpensive !== state.details?.isConnectionExpensive - ) { - //console.log('fetch net info'); - this.setState({ - netInfo: { - isConnected: state.isConnected, - type: state.type, - isConnectionExpensive: state.details && state.details.isConnectionExpensive, - }, + this.state.navigationHome?.navigate(RouteEnum.HomeStack, { + screen: RouteEnum.Send, }); - if (isConnected !== state.isConnected) { - if (!state.isConnected) { - //console.log('EVENT Loaded: No internet connection.'); - } else { - //console.log('EVENT Loaded: YES internet connection.'); - // restart the interval process again... - await this.rpc.clearTimers(); - await this.rpc.configure(); + }, + ); + + this.unsubscribeNetInfo = NetInfo.addEventListener( + async (state: NetInfoState) => { + const { isConnected, type, isConnectionExpensive } = this.state.netInfo; + if ( + isConnected !== state.isConnected || + type !== state.type || + isConnectionExpensive !== state.details?.isConnectionExpensive + ) { + //console.log('fetch net info'); + this.setState({ + netInfo: { + isConnected: state.isConnected, + type: state.type, + isConnectionExpensive: + state.details && state.details.isConnectionExpensive, + }, + }); + if (isConnected !== state.isConnected) { + if (!state.isConnected) { + //console.log('EVENT Loaded: No internet connection.'); + } else { + //console.log('EVENT Loaded: YES internet connection.'); + // restart the interval process again... + await this.rpc.clearTimers(); + await this.rpc.configure(); + } } } - } - }); + }, + ); }; componentWillUnmount = async () => { await this.rpc.clearTimers(); - this.appstate && typeof this.appstate.remove === 'function' && this.appstate.remove(); + this.appstate && + typeof this.appstate.remove === 'function' && + this.appstate.remove(); this.linking && typeof this.linking === 'function' && this.linking.remove(); - this.unsubscribeNetInfo && typeof this.unsubscribeNetInfo === 'function' && this.unsubscribeNetInfo(); + this.unsubscribeNetInfo && + typeof this.unsubscribeNetInfo === 'function' && + this.unsubscribeNetInfo(); }; keepAwake = (keep: boolean): void => { @@ -726,7 +879,11 @@ export class LoadedAppClass extends Component { - const backgroundJson: BackgroundType = await BackgroundFileImpl.readBackground(); + const backgroundJson: BackgroundType = + await BackgroundFileImpl.readBackground(); if (!isEqual(this.state.background, backgroundJson)) { //console.log('fetch background sync info'); this.setState({ background: backgroundJson }); @@ -816,8 +976,12 @@ export class LoadedAppClass extends Component { - const basicFirstViewSeed = (await SettingsFileImpl.readSettings()).basicFirstViewSeed; + setValueTransfersList = async ( + valueTransfers: ValueTransferType[], + valueTransfersTotal: number, + ) => { + const basicFirstViewSeed = (await SettingsFileImpl.readSettings()) + .basicFirstViewSeed; // only for basic mode if (this.state.mode === ModeEnum.basic) { // only if the user doesn't see the seed the first time @@ -829,8 +993,8 @@ export class LoadedAppClass extends Component 0 ? valueTransfers.filter((vt: ValueTransferType) => vt.confirmations >= 0 && vt.confirmations < GlobalConst.minConfirmations).length : 0; + valueTransfersTotal > 0 + ? valueTransfers.filter( + (vt: ValueTransferType) => + vt.confirmations >= 0 && + vt.confirmations < GlobalConst.minConfirmations, + ).length + : 0; // if a ValueTransfer go from 3 confirmations to > 3 -> Show a message about a ValueTransfer is confirmed this.state.valueTransfers && this.state.valueTransfersTotal !== null && @@ -854,7 +1030,9 @@ export class LoadedAppClass extends Component { const vtNew = valueTransfers.filter( (vt: ValueTransferType) => - vt.txid === vtOld.txid && vt.address === vtOld.address && vt.poolType === vtOld.poolType, + vt.txid === vtOld.txid && + vt.address === vtOld.address && + vt.poolType === vtOld.poolType, ); //console.log('old', vtOld); //console.log('new', vtNew); @@ -862,7 +1040,10 @@ export class LoadedAppClass extends Component 0 && vtNew[0].confirmations > 0) { let message: string = ''; let title: string = ''; - if (vtNew[0].kind === ValueTransferKindEnum.Received && vtNew[0].amount > 0) { + if ( + vtNew[0].kind === ValueTransferKindEnum.Received && + vtNew[0].amount > 0 + ) { message = (this.state.translate('loadedapp.incoming-funds') as string) + (this.state.translate('history.received') as string) + @@ -870,10 +1051,18 @@ export class LoadedAppClass extends Component 0) { + title = this.state.translate( + 'loadedapp.receive-menu', + ) as string; + } else if ( + vtNew[0].kind === ValueTransferKindEnum.MemoToSelf && + vtNew[0].fee && + vtNew[0].fee > 0 + ) { message = - (this.state.translate('loadedapp.valuetransfer-confirmed') as string) + + (this.state.translate( + 'loadedapp.valuetransfer-confirmed', + ) as string) + (this.state.translate('history.memotoself') as string) + (vtNew[0].fee ? ((' ' + this.state.translate('send.fee')) as string) + @@ -883,9 +1072,15 @@ export class LoadedAppClass extends Component 0) { + } else if ( + vtNew[0].kind === ValueTransferKindEnum.SendToSelf && + vtNew[0].fee && + vtNew[0].fee > 0 + ) { message = - (this.state.translate('loadedapp.valuetransfer-confirmed') as string) + + (this.state.translate( + 'loadedapp.valuetransfer-confirmed', + ) as string) + (this.state.translate('history.sendtoself') as string) + (vtNew[0].fee ? ((' ' + this.state.translate('send.fee')) as string) + @@ -895,7 +1090,10 @@ export class LoadedAppClass extends Component 0) { + } else if ( + vtNew[0].kind === ValueTransferKindEnum.Rejection && + vtNew[0].amount > 0 + ) { // not so sure about this `kind`... // I guess the wallet is receiving some refund from a TEX sent. message = @@ -905,8 +1103,13 @@ export class LoadedAppClass extends Component 0) { + title = this.state.translate( + 'loadedapp.receive-menu', + ) as string; + } else if ( + vtNew[0].kind === ValueTransferKindEnum.Shield && + vtNew[0].amount > 0 + ) { message = (this.state.translate('loadedapp.incoming-funds') as string) + (this.state.translate('history.shield') as string) + @@ -914,8 +1117,13 @@ export class LoadedAppClass extends Component 0) { + title = this.state.translate( + 'loadedapp.receive-menu', + ) as string; + } else if ( + vtNew[0].kind === ValueTransferKindEnum.Sent && + vtNew[0].amount > 0 + ) { message = (this.state.translate('loadedapp.payment-made') as string) + (this.state.translate('history.sent') as string) + @@ -933,7 +1141,7 @@ export class LoadedAppClass extends Component { - if (!isEqual(this.state.messages, messages) || this.state.messagesTotal !== messagesTotal) { + if ( + !isEqual(this.state.messages, messages) || + this.state.messagesTotal !== messagesTotal + ) { //console.log('fetch messages'); //const start = Date.now(); this.setState({ messages, messagesTotal }); @@ -980,7 +1191,9 @@ export class LoadedAppClass extends Component { + setAllAddresses = ( + addresses: (UnifiedAddressClass | TransparentAddressClass)[], + ) => { if (!isEqual(this.state.addresses, addresses)) { //console.log('fetch addresses'); //const start = Date.now(); @@ -989,8 +1202,12 @@ export class LoadedAppClass extends Component 0) { // the last Unified Address created. - const defaultUAArray = addresses.filter((a: UnifiedAddressClass | TransparentAddressClass) => a.addressKind === AddressKindEnum.u); - const defaultUA: string = defaultUAArray[defaultUAArray.length - 1].address; + const defaultUAArray = addresses.filter( + (a: UnifiedAddressClass | TransparentAddressClass) => + a.addressKind === AddressKindEnum.u, + ); + const defaultUA: string = + defaultUAArray[defaultUAArray.length - 1].address; if (this.state.defaultUnifiedAddress !== defaultUA) { this.setState({ defaultUnifiedAddress: defaultUA }); } @@ -1038,7 +1255,10 @@ export class LoadedAppClass extends Component => { + sendTransaction = async ( + sendPageState: SendPageStateClass, + ): Promise => { try { // Construct a sendJson from the sendPage state const { lightWalletserver, donation, defaultUnifiedAddress } = this.state; - const sendJson = await Utils.getSendManyJSON(sendPageState, defaultUnifiedAddress, lightWalletserver, donation); + const sendJson = await Utils.getSendManyJSON( + sendPageState, + defaultUnifiedAddress, + lightWalletserver, + donation, + ); //const start = Date.now(); const txid = await this.rpc.sendTransaction(sendJson); //console.log('&&&&&&&&&&&&&& send tx', Date.now() - start); @@ -1130,34 +1357,34 @@ export class LoadedAppClass extends Component await this.onClickOKChangeWallet({ screen: 3, startingApp: false }), + onPress: async () => + await this.onClickOKChangeWallet({ + screen: 3, + startingApp: false, + }), }, { text: translate('cancel') as string, style: 'cancel' }, ], @@ -1197,7 +1428,10 @@ export class LoadedAppClass extends Component { @@ -1214,7 +1448,9 @@ export class LoadedAppClass extends Component we need tor client Just in case. - if (this.state.currency === CurrencyEnum.USDTORCurrency || this.state.currency === CurrencyEnum.USDCurrency) { + if ( + this.state.currency === CurrencyEnum.USDTORCurrency || + this.state.currency === CurrencyEnum.USDCurrency + ) { const resp: string = await RPCModule.createTorClientProcess(); if (resp && resp.toLowerCase().startsWith(GlobalConst.error)) { this.setLastError(`Create tor client error: ${resp}`); @@ -1309,12 +1555,12 @@ export class LoadedAppClass extends Component => { - await SettingsFileImpl.writeSettings(SettingsNameEnum.selectLightWalletServer, value); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.selectLightWalletServer, + value, + ); this.setState({ selectLightWalletServer: value as SelectServerEnum, }); @@ -1425,8 +1677,13 @@ export class LoadedAppClass extends Component => { - await SettingsFileImpl.writeSettings(SettingsNameEnum.recoveryWalletInfoOnDevice, value); + setRecoveryWalletInfoOnDeviceOption = async ( + value: boolean, + ): Promise => { + await SettingsFileImpl.writeSettings( + SettingsNameEnum.recoveryWalletInfoOnDevice, + value, + ); this.setState({ recoveryWalletInfoOnDevice: value as boolean, }); @@ -1439,8 +1696,13 @@ export class LoadedAppClass extends Component => { - await SettingsFileImpl.writeSettings(SettingsNameEnum.performanceLevel, value); + setPerformanceLevelOption = async ( + value: RPCPerformanceLevelEnum, + ): Promise => { + await SettingsFileImpl.writeSettings( + SettingsNameEnum.performanceLevel, + value, + ); this.setState({ performanceLevel: value as RPCPerformanceLevelEnum, }); @@ -1448,10 +1710,16 @@ export class LoadedAppClass extends Component { - if (this.state.newLightWalletServer && this.state.newSelectLightWalletServer) { - const beforeServer = this.state.lightWalletserver; - - const resultStrServerPromise = await RPCModule.changeServerProcess(this.state.newLightWalletServer.uri); - const timeoutServerPromise = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error('Promise changeserver Timeout 30 seconds')); - }, 30 * 1000); - }); + const { newLightWalletServer, newSelectLightWalletServer } = this.state; + + if (!newLightWalletServer || !newSelectLightWalletServer) { + return; + } + const previousServer = this.state.lightWalletserver; - const resultStrServer: string = await Promise.race([resultStrServerPromise, timeoutServerPromise]); - //console.log(resultStrServer); + const changeServerPromise = RPCModule.changeServerProcess( + newLightWalletServer.uri, + ); - if (resultStrServer && resultStrServer.toLowerCase().startsWith(GlobalConst.error)) { - //console.log(`Error change server ${value} - ${resultStr}`); - this.addLastSnackbar({ - message: `${this.state.translate('loadedapp.changeservernew-error')} ${resultStrServer}`, - screenName: [this.screenName], - }); - return; - } else { - //console.log(`change server ok ${value}`); - } + const timeoutServerPromise: Promise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error('Promise changeserver Timeout 30 seconds')); + }, 30 * 1000); + }); - await SettingsFileImpl.writeSettings(SettingsNameEnum.lightWalletServer, this.state.newLightWalletServer); - await SettingsFileImpl.writeSettings(SettingsNameEnum.selectLightWalletServer, this.state.newSelectLightWalletServer); - this.setState({ - lightWalletserver: this.state.newLightWalletServer, - selectLightWalletServer: this.state.selectLightWalletServer, - newLightWalletServer: {} as ServerType, - newSelectLightWalletServer: null, + // 3) Race them, and handle errors via try/catch + try { + // Option 2 flavored: map success to a constant value if you *really* + // want a string, but in practice you don't need it. + await Promise.race([ + changeServerPromise, // Promise + timeoutServerPromise, // Promise + ]); + // if we get here, server changed successfully within 30s + } catch (err) { + const msg = + err instanceof Error ? err.message : String(err ?? 'Unknown error'); + + this.addLastSnackbar({ + message: `${this.state.translate('loadedapp.changeservernew-error')} ${msg}`, + screenName: [this.screenName], }); - await this.rpc.fetchInfoAndServerHeight(); + return; // abort flow on error + } - let resultStr2 = ''; - // if the server was testnet or regtest -> no need backup the wallet. - if (beforeServer.chainName === ChainNameEnum.mainChainName) { - // backup - resultStr2 = (await this.rpc.changeWallet()) as string; - } else { - // no backup - resultStr2 = (await this.rpc.changeWalletNoBackup()) as string; - } + await SettingsFileImpl.writeSettings( + SettingsNameEnum.lightWalletServer, + newLightWalletServer, + ); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.selectLightWalletServer, + newSelectLightWalletServer, + ); + this.setState({ + lightWalletserver: this.state.newLightWalletServer, + selectLightWalletServer: this.state.selectLightWalletServer, + newLightWalletServer: {} as ServerType, + newSelectLightWalletServer: null, + }); - //console.log("jc change", resultStr); - if (resultStr2 && resultStr2.toLowerCase().startsWith(GlobalConst.error)) { - //console.log(`Error change wallet. ${resultStr}`); - createAlert( - this.setBackgroundError, - this.addLastSnackbar, - [this.screenName], - this.state.translate('loadedapp.changingwallet-label') as string, - resultStr2, - false, - this.state.translate, - sendEmail, - this.state.zingolibVersion, - ); - //return; - } + await this.rpc.fetchInfoAndServerHeight(); - // no need to restart the tasks because is about to restart the app. - this.navigateToLoadingApp({ startingApp: false }); + let resultStr2 = ''; + // if the server was testnet or regtest -> no need backup the wallet. + if (previousServer.chainName === ChainNameEnum.mainChainName) { + // backup + resultStr2 = (await this.rpc.changeWallet()) as string; + } else { + // no backup + resultStr2 = (await this.rpc.changeWalletNoBackup()) as string; } + + //console.log("jc change", resultStr); + if (resultStr2 && resultStr2.toLowerCase().startsWith(GlobalConst.error)) { + //console.log(`Error change wallet. ${resultStr}`); + createAlert( + this.setBackgroundError, + this.addLastSnackbar, + [this.screenName], + this.state.translate('loadedapp.changingwallet-label') as string, + resultStr2, + false, + this.state.translate, + sendEmail, + this.state.zingolibVersion, + ); + //return; + } + + // no need to restart the tasks because is about to restart the app. + this.navigateToLoadingApp({ startingApp: false }); }; setBackgroundError = (title: string, error: string) => { @@ -1606,7 +1893,10 @@ export class LoadedAppClass extends Component { const newSnackbars = this.state.snackbars; // if the last one is the same don't do anything. - if (newSnackbars.length > 0 && newSnackbars[newSnackbars.length - 1].message === snackbar.message) { + if ( + newSnackbars.length > 0 && + newSnackbars[newSnackbars.length - 1].message === snackbar.message + ) { return; } newSnackbars.push(snackbar); @@ -1618,7 +1908,9 @@ export class LoadedAppClass extends Component { - const newSnackbars = this.state.snackbars.filter((s: SnackbarType) => s.screenName.includes(screenName)); + const newSnackbars = this.state.snackbars.filter((s: SnackbarType) => + s.screenName.includes(screenName), + ); newSnackbars.shift(); this.setState({ snackbars: newSnackbars }); }; @@ -1635,7 +1927,9 @@ export class LoadedAppClass extends Component { + setNavigationHome = ( + navigationHome: DrawerContentComponentProps['navigation'], + ) => { if (!this.state.navigationHome) { this.setState({ navigationHome, @@ -1712,7 +2006,10 @@ export class LoadedAppClass extends Component { + const fnTabBarIcon = ( + route: { name: string; key: string }, + focused: boolean, + ) => { var iconName; if (route.name === RouteEnum.History) { @@ -1721,11 +2018,12 @@ export class LoadedAppClass extends Component 0 && totalBalance.confirmedOrchardBalance === 0) || - (totalBalance.totalSaplingBalance > 0 && totalBalance.confirmedSaplingBalance === 0) || - (totalBalance.totalTransparentBalance > 0 && totalBalance.confirmedTransparentBalance === 0) - ) && + ((totalBalance.totalOrchardBalance > 0 && + totalBalance.confirmedOrchardBalance === 0) || + (totalBalance.totalSaplingBalance > 0 && + totalBalance.confirmedSaplingBalance === 0) || + (totalBalance.totalTransparentBalance > 0 && + totalBalance.confirmedTransparentBalance === 0)) && somePending ) { iconName = faRefresh; @@ -1742,7 +2040,11 @@ export class LoadedAppClass extends Component - + ); }; @@ -1762,132 +2064,200 @@ export class LoadedAppClass extends Component - + {props => { useEffect(() => { this.setNavigationHome(props.navigation); }); return ( - <> - {mode === ModeEnum.advanced || - (valueTransfersTotal !== null && valueTransfersTotal > 0) || - (!readOnly && !!totalBalance && totalBalance.confirmedOrchardBalance + totalBalance.confirmedSaplingBalance > 0) ? ( - ({ - tabBarIcon: ({ focused }) => fnTabBarIcon(route, focused), - tabBarIconStyle: { - alignSelf: 'center', - marginBottom: 2, - }, - tabBarLabel: route.name === RouteEnum.History - ? (translate('loadedapp.history-menu') as string) - : route.name === RouteEnum.Send - ? (translate('loadedapp.send-menu') as string) - : route.name === RouteEnum.Receive - ? (translate('loadedapp.receive-menu') as string) - : route.name === RouteEnum.Messages - ? (translate('loadedapp.messages-menu') as string) - : '', - tabBarLabelPosition: 'below-icon', - tabBarLabelStyle: { - alignSelf: 'center', - fontSize: 14, - }, - tabBarItemStyle: { - height: 60, - }, - tabBarActiveTintColor: colors.background, - tabBarActiveBackgroundColor: colors.primaryDisabled, - tabBarInactiveTintColor: colors.money, - tabBarInactiveBackgroundColor: colors.sideMenuBackground, - tabBarStyle: { - borderTopWidth: 1, - height: 60, - }, - headerShown: false, - tabBarButton: renderTabPressable(colors), - })}> - - {propsTab => ( - props.navigation.toggleDrawer() /* header */} - setShieldingAmount={this.setShieldingAmount /* header */} - setScrollToTop={this.setScrollToTop /* header & history */} - scrollToTop={scrollToTop /* history */} - setScrollToBottom={this.setScrollToBottom /* header & messages */} + <> + {mode === ModeEnum.advanced || + (valueTransfersTotal !== null && + valueTransfersTotal > 0) || + (!readOnly && + !!totalBalance && + totalBalance.confirmedOrchardBalance + + totalBalance.confirmedSaplingBalance > + 0) ? ( + ({ + tabBarIcon: ({ focused }) => + fnTabBarIcon(route, focused), + tabBarIconStyle: { + alignSelf: 'center', + marginBottom: 2, + }, + tabBarLabel: + route.name === RouteEnum.History + ? (translate( + 'loadedapp.history-menu', + ) as string) + : route.name === RouteEnum.Send + ? (translate('loadedapp.send-menu') as string) + : route.name === RouteEnum.Receive + ? (translate( + 'loadedapp.receive-menu', + ) as string) + : route.name === RouteEnum.Messages + ? (translate( + 'loadedapp.messages-menu', + ) as string) + : '', + tabBarLabelPosition: 'below-icon', + tabBarLabelStyle: { + alignSelf: 'center', + fontSize: 14, + }, + tabBarItemStyle: { + height: 60, + }, + tabBarActiveTintColor: colors.background, + tabBarActiveBackgroundColor: colors.primaryDisabled, + tabBarInactiveTintColor: colors.money, + tabBarInactiveBackgroundColor: + colors.sideMenuBackground, + tabBarStyle: { + borderTopWidth: 1, + height: 60, + }, + headerShown: false, + tabBarButton: renderTabPressable(colors), + })} + > + + {propsTab => ( + + props.navigation.toggleDrawer() /* header */ + } + setShieldingAmount={ + this.setShieldingAmount /* header */ + } + setScrollToTop={ + this.setScrollToTop /* header & history */ + } + scrollToTop={scrollToTop /* history */} + setScrollToBottom={ + this.setScrollToBottom /* header & messages */ + } + /> + )} + + {!readOnly && + selectLightWalletServer !== + SelectServerEnum.offline && + (mode === ModeEnum.advanced || + (!!totalBalance && + totalBalance.confirmedOrchardBalance + + totalBalance.confirmedSaplingBalance > + 0) || + (!!totalBalance && + ((totalBalance.totalOrchardBalance > 0 && + totalBalance.confirmedOrchardBalance === 0) || + (totalBalance.totalSaplingBalance > 0 && + totalBalance.confirmedSaplingBalance === + 0)) && + somePending)) && ( + + {propsTab => ( + + props.navigation.toggleDrawer() /* header */ + } + setShieldingAmount={ + this.setShieldingAmount /* header */ + } + setScrollToTop={ + this.setScrollToTop /* header & send */ + } + setScrollToBottom={ + this.setScrollToBottom /* header & send */ + } + sendTransaction={ + this.sendTransaction /* send */ + } + setServerOption={ + this.setServerOption /* send */ + } + clearToAddr={this.clearToAddr /* send */} + setSecurityOption={ + this.setSecurityOption /* send */ + } + /> + )} + + )} + + {propsTab => ( + + props.navigation.toggleDrawer() /* header */ + } + alone={false /* receive */} + setSecurityOption={this.setSecurityOption} + /> + )} + + + ) : ( + <> + {addresses === null ? ( + + ) : ( + + + {propsTab => ( + + props.navigation.toggleDrawer() /* header */ + } + alone={true /* receive */} + setSecurityOption={this.setSecurityOption} + /> + )} + + )} - - {!readOnly && - selectLightWalletServer !== SelectServerEnum.offline && - (mode === ModeEnum.advanced || - (!!totalBalance && totalBalance.confirmedOrchardBalance + totalBalance.confirmedSaplingBalance > 0) || - (!!totalBalance && - ( - (totalBalance.totalOrchardBalance > 0 && totalBalance.confirmedOrchardBalance === 0) || - (totalBalance.totalSaplingBalance > 0 && totalBalance.confirmedSaplingBalance === 0) - ) && - somePending)) && ( - - {propsTab => ( - props.navigation.toggleDrawer() /* header */} - setShieldingAmount={this.setShieldingAmount /* header */} - setScrollToTop={this.setScrollToTop /* header & send */} - setScrollToBottom={this.setScrollToBottom /* header & send */} - sendTransaction={this.sendTransaction /* send */} - setServerOption={this.setServerOption /* send */} - clearToAddr={this.clearToAddr /* send */} - setSecurityOption={this.setSecurityOption /* send */} - /> - )} - - )} - - {propsTab => ( - props.navigation.toggleDrawer() /* header */} - alone={false /* receive */} - setSecurityOption={this.setSecurityOption} - /> - )} - - - ) : ( - <> - {addresses === null ? ( - - ) : ( - - - {propsTab => ( - props.navigation.toggleDrawer() /* header */} - alone={true /* receive */} - setSecurityOption={this.setSecurityOption} - /> - )} - - - )} - - )} - - );}} + + )} + + ); + }} - {props => - ( + props.navigation.toggleDrawer() /* header */} + toggleMenuDrawer={ + () => props.navigation.toggleDrawer() /* header */ + } /> - } + )} @@ -1911,41 +2285,66 @@ export class LoadedAppClass extends Component {() => { return ( - - - + + + ); }} {props => { - const action = !!props.route.params && props.route.params.action !== undefined ? props.route.params.action : UfvkActionEnum.view; - if (action === UfvkActionEnum.view ) { + const action = + !!props.route.params && + props.route.params.action !== undefined + ? props.route.params.action + : UfvkActionEnum.view; + if (action === UfvkActionEnum.view) { return ( - {}} onClickCancel={() => {}} /> ); } else if (action === UfvkActionEnum.change) { return ( - await this.onClickOKChangeWallet({ startingApp: false })} + + await this.onClickOKChangeWallet({ + startingApp: false, + }) + } onClickCancel={() => {}} /> ); } else if (action === UfvkActionEnum.backup) { return ( - await this.onClickOKRestoreBackup()} + + await this.onClickOKRestoreBackup() + } onClickCancel={() => {}} /> ); } else if (action === UfvkActionEnum.server) { return ( - await this.onClickOKServerWallet()} + + await this.onClickOKServerWallet() + } onClickCancel={async () => { // restart all the tasks again, nothing happen. await this.rpc.clearTimers(); @@ -1958,10 +2357,15 @@ export class LoadedAppClass extends Component {props => { - const action = !!props.route.params && props.route.params.action !== undefined ? props.route.params.action : SeedActionEnum.view; - if (action === SeedActionEnum.view ) { + const action = + !!props.route.params && + props.route.params.action !== undefined + ? props.route.params.action + : SeedActionEnum.view; + if (action === SeedActionEnum.view) { return ( - {}} onClickCancel={() => {}} keepAwake={this.keepAwake} @@ -1970,22 +2374,33 @@ export class LoadedAppClass extends Component await this.onClickOKChangeWallet({ startingApp: false })} + + await this.onClickOKChangeWallet({ + startingApp: false, + }) + } onClickCancel={() => {}} /> ); } else if (action === SeedActionEnum.backup) { return ( - await this.onClickOKRestoreBackup()} + + await this.onClickOKRestoreBackup() + } onClickCancel={() => {}} /> ); } else if (action === SeedActionEnum.server) { return ( - await this.onClickOKServerWallet()} + + await this.onClickOKServerWallet() + } onClickCancel={async () => { // restart all the tasks again, nothing happen. await this.rpc.clearTimers(); @@ -1996,32 +2411,62 @@ export class LoadedAppClass extends Component - + {() => { return ( - - - + + + ); }} - - + + {() => { return ( - - - + + + ); }} - + diff --git a/app/LoadingApp/LoadingApp.tsx b/app/LoadingApp/LoadingApp.tsx index 6553dd47d..6a6457cb1 100644 --- a/app/LoadingApp/LoadingApp.tsx +++ b/app/LoadingApp/LoadingApp.tsx @@ -14,9 +14,12 @@ import { useTheme } from '@react-navigation/native'; import { I18n } from 'i18n-js'; import * as RNLocalize from 'react-native-localize'; import { StackScreenProps } from '@react-navigation/stack'; -import NetInfo, { NetInfoSubscription, NetInfoState } from '@react-native-community/netinfo/src/index'; +import NetInfo, { + NetInfoSubscription, + NetInfoState, +} from '@react-native-community/netinfo/src/index'; -import RPCModule from '../RPCModule'; +import RPCModule, { WalletKind } from '../RPCModule'; import { AppStateLoading, BackgroundType, @@ -88,7 +91,10 @@ const tr = require('../translations/tr.json'); //const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); type LoadingAppProps = { - navigation: StackScreenProps['navigation']; + navigation: StackScreenProps< + AppStackParamList, + RouteEnum.LoadingApp + >['navigation']; route: StackScreenProps['route']; toggleTheme: (mode: ModeEnum) => void; }; @@ -103,17 +109,29 @@ export default function LoadingApp(props: LoadingAppProps) { const [loading, setLoading] = useState(true); const [language, setLanguage] = useState(LanguageEnum.en); - const [currency, setCurrency] = useState(CurrencyEnum.noCurrency); // by default none because of cTAZ - const [lightWalletServer, setLightWalletServer] = useState(SERVER_DEFAULT_0); - const [selectLightWalletServer, setSelectLightWalletServer] = useState(SelectServerEnum.custom); - const [validatorServer, setValidatorServer] = useState(SERVER_DEFAULT_0); - const [selectValidatorServer, setSelectValidatorServer] = useState(SelectServerEnum.custom); + const [currency, setCurrency] = useState( + CurrencyEnum.noCurrency, + ); // by default none because of cTAZ + const [lightWalletServer, setLightWalletServer] = + useState(SERVER_DEFAULT_0); + const [selectLightWalletServer, setSelectLightWalletServer] = + useState(SelectServerEnum.custom); + const [validatorServer, setValidatorServer] = + useState(SERVER_DEFAULT_0); + const [selectValidatorServer, setSelectValidatorServer] = + useState(SelectServerEnum.custom); const [sendAll, setSendAll] = useState(false); const [donation, setDonation] = useState(false); const [privacy, setPrivacy] = useState(false); const [mode, setMode] = useState(ModeEnum.advanced); // by default advanced - const [background, setBackground] = useState({ batches: 0, message: '', date: 0, dateEnd: 0 }); - const [firstLaunchingMessage, setFirstLaunchingMessage] = useState(LaunchingModeEnum.opening); + const [background, setBackground] = useState({ + batches: 0, + message: '', + date: 0, + dateEnd: 0, + }); + const [firstLaunchingMessage, setFirstLaunchingMessage] = + useState(LaunchingModeEnum.opening); const [security, setSecurity] = useState({ startApp: true, // activate only this foregroundApp: false, @@ -126,8 +144,10 @@ export default function LoadingApp(props: LoadingAppProps) { }); const [rescanMenu, setRescanMenu] = useState(false); // by default the App store the seed phrase & birthday on KeyChain/KeyStore (Device). - const [recoveryWalletInfoOnDevice, setRecoveryWalletInfoOnDevice] = useState(true); - const [performanceLevel, setPerformanceLevel] = useState(RPCPerformanceLevelEnum.Medium); + const [recoveryWalletInfoOnDevice, setRecoveryWalletInfoOnDevice] = + useState(true); + const [performanceLevel, setPerformanceLevel] = + useState(RPCPerformanceLevelEnum.Medium); const file = useMemo( () => ({ en: en, @@ -140,14 +160,16 @@ export default function LoadingApp(props: LoadingAppProps) { ); const i18n = useMemo(() => new I18n(file), [file]); - const translate: (key: string) => TranslateType = (key: string) => i18n.t(key); + const translate: (key: string) => TranslateType = (key: string) => + i18n.t(key); useEffect(() => { (async () => { // fallback if no available language fits const fallback = { languageTag: LanguageEnum.en, isRTL: false }; - const { languageTag, isRTL } = RNLocalize.findBestLanguageTag(Object.keys(file)) || fallback; + const { languageTag, isRTL } = + RNLocalize.findBestLanguageTag(Object.keys(file)) || fallback; // update layout direction I18nManager.forceRTL(isRTL); @@ -161,12 +183,18 @@ export default function LoadingApp(props: LoadingAppProps) { if (settings.version === null) { // this is a fresh install setFirstLaunchingMessage(LaunchingModeEnum.installing); - } else if (settings.version === '' || settings.version !== (translate('version') as string)) { + } else if ( + settings.version === '' || + settings.version !== (translate('version') as string) + ) { // this is an update setFirstLaunchingMessage(LaunchingModeEnum.updating); } - if (settings.mode === ModeEnum.basic || settings.mode === ModeEnum.advanced) { + if ( + settings.mode === ModeEnum.basic || + settings.mode === ModeEnum.advanced + ) { setMode(settings.mode); props.toggleTheme(settings.mode); } else { @@ -205,13 +233,19 @@ export default function LoadingApp(props: LoadingAppProps) { ) { setCurrency(settings.currency); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.currency, currency); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.currency, + currency, + ); } // lightwallet server if (settings.lightWalletserver) { setLightWalletServer(settings.lightWalletserver); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.lightWalletServer, lightWalletServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.lightWalletServer, + lightWalletServer, + ); } // using only custom & offline. if ( @@ -222,13 +256,19 @@ export default function LoadingApp(props: LoadingAppProps) { ) { setSelectLightWalletServer(settings.selectLightWalletServer); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.selectLightWalletServer, selectLightWalletServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.selectLightWalletServer, + selectLightWalletServer, + ); } // validator server if (settings.validatorServer) { setValidatorServer(settings.validatorServer); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.validatorServer, validatorServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.validatorServer, + validatorServer, + ); } if ( settings.selectValidatorServer === SelectServerEnum.auto || @@ -238,7 +278,10 @@ export default function LoadingApp(props: LoadingAppProps) { ) { setSelectValidatorServer(settings.selectValidatorServer); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.selectValidatorServer, selectValidatorServer); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.selectValidatorServer, + selectValidatorServer, + ); } if (settings.sendAll === true || settings.sendAll === false) { setSendAll(settings.sendAll); @@ -248,7 +291,10 @@ export default function LoadingApp(props: LoadingAppProps) { if (settings.donation === true || settings.donation === false) { setDonation(settings.donation); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.donation, donation); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.donation, + donation, + ); } if (settings.privacy === true || settings.privacy === false) { setPrivacy(settings.privacy); @@ -258,17 +304,29 @@ export default function LoadingApp(props: LoadingAppProps) { if (settings.security) { setSecurity(settings.security); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.security, security); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.security, + security, + ); } if (settings.rescanMenu === true || settings.rescanMenu === false) { setRescanMenu(settings.rescanMenu); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.rescanMenu, rescanMenu); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.rescanMenu, + rescanMenu, + ); } - if (settings.recoveryWalletInfoOnDevice === true || settings.recoveryWalletInfoOnDevice === false) { + if ( + settings.recoveryWalletInfoOnDevice === true || + settings.recoveryWalletInfoOnDevice === false + ) { setRecoveryWalletInfoOnDevice(settings.recoveryWalletInfoOnDevice); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.recoveryWalletInfoOnDevice, recoveryWalletInfoOnDevice); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.recoveryWalletInfoOnDevice, + recoveryWalletInfoOnDevice, + ); } if ( settings.performanceLevel === RPCPerformanceLevelEnum.High || @@ -278,7 +336,10 @@ export default function LoadingApp(props: LoadingAppProps) { ) { setPerformanceLevel(settings.performanceLevel); } else { - await SettingsFileImpl.writeSettings(SettingsNameEnum.performanceLevel, performanceLevel); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.performanceLevel, + performanceLevel, + ); } // for testing @@ -296,7 +357,13 @@ export default function LoadingApp(props: LoadingAppProps) { //console.log('render loadingApp - 2', translate('version')); if (loading) { - return ; + return ( + + ); } else { return ( ['navigation']; + navigationApp: StackScreenProps< + AppStackParamList, + RouteEnum.LoadingApp + >['navigation']; route: StackScreenProps['route']; toggleTheme: (mode: ModeEnum) => void; translate: (key: string) => TranslateType; @@ -351,7 +421,10 @@ type LoadingAppClassProps = { type LoadingAppClassState = AppStateLoading & AppContextLoading; -export class LoadingAppClass extends Component { +export class LoadingAppClass extends Component< + LoadingAppClassProps, + LoadingAppClassState +> { dim: EmitterSubscription; appstate: NativeEventSubscription; unsubscribeNetInfo: NetInfoSubscription; @@ -397,7 +470,10 @@ export class LoadingAppClass extends Component { this.addLastSnackbar({ - message: this.state.translate('loadedapp.selectingserver') as string, + message: this.state.translate( + 'loadedapp.selectingserver', + ) as string, duration: SnackbarDurationEnum.longer, screenName: [this.screenName], }); @@ -493,7 +577,10 @@ export class LoadingAppClass extends Component create a new wallet & go directly to history screen. // no seed screen. - if (!netInfoState.isConnected || this.state.selectLightWalletServer === SelectServerEnum.offline) { + if ( + !netInfoState.isConnected || + this.state.selectLightWalletServer === SelectServerEnum.offline + ) { this.setState({ screen: 1, walletExists: false, @@ -636,13 +741,22 @@ export class LoadingAppClass extends Component go to the initial menu. - await SettingsFileImpl.writeSettings(SettingsNameEnum.basicFirstViewSeed, true); + await SettingsFileImpl.writeSettings( + SettingsNameEnum.basicFirstViewSeed, + true, + ); this.setState(state => ({ screen: state.screen === 3 ? 3 : 1, walletExists: false, @@ -651,84 +765,107 @@ export class LoadingAppClass extends Component { - //console.log('LOADING', 'prior', this.state.appStateStatus, 'next', nextAppState); - // let's catch the prior value - const priorAppState = this.state.appStateStatus; - this.setState({ appStateStatus: nextAppState }); - if ( - (priorAppState === AppStateStatusEnum.inactive || priorAppState === AppStateStatusEnum.background) && - nextAppState === AppStateStatusEnum.active - ) { - //console.log('App LOADING has come to the foreground!'); - // reading background task info - this.fetchBackgroundSyncing(); - // setting value for background task Android - await AsyncStorage.setItem(GlobalConst.background, GlobalConst.no); - //console.log('&&&&& background no in storage &&&&&'); - if (this.state.backgroundError && (this.state.backgroundError.title || this.state.backgroundError.error)) { - Alert.alert(this.state.backgroundError.title, this.state.backgroundError.error); - this.setBackgroundError('', ''); + this.appstate = AppState.addEventListener( + EventListenerEnum.change, + async nextAppState => { + //console.log('LOADING', 'prior', this.state.appStateStatus, 'next', nextAppState); + // let's catch the prior value + const priorAppState = this.state.appStateStatus; + this.setState({ appStateStatus: nextAppState }); + if ( + (priorAppState === AppStateStatusEnum.inactive || + priorAppState === AppStateStatusEnum.background) && + nextAppState === AppStateStatusEnum.active + ) { + //console.log('App LOADING has come to the foreground!'); + // reading background task info + this.fetchBackgroundSyncing(); + // setting value for background task Android + await AsyncStorage.setItem(GlobalConst.background, GlobalConst.no); + //console.log('&&&&& background no in storage &&&&&'); + if ( + this.state.backgroundError && + (this.state.backgroundError.title || + this.state.backgroundError.error) + ) { + Alert.alert( + this.state.backgroundError.title, + this.state.backgroundError.error, + ); + this.setBackgroundError('', ''); + } } - } - if ( - (nextAppState === AppStateStatusEnum.inactive || nextAppState === AppStateStatusEnum.background) && - priorAppState === AppStateStatusEnum.active - ) { - console.log('App LOADING is gone to the background!'); - // setting value for background task Android - await AsyncStorage.setItem(GlobalConst.background, GlobalConst.yes); - //console.log('&&&&& background yes in storage &&&&&'); - } - }); + if ( + (nextAppState === AppStateStatusEnum.inactive || + nextAppState === AppStateStatusEnum.background) && + priorAppState === AppStateStatusEnum.active + ) { + console.log('App LOADING is gone to the background!'); + // setting value for background task Android + await AsyncStorage.setItem(GlobalConst.background, GlobalConst.yes); + //console.log('&&&&& background yes in storage &&&&&'); + } + }, + ); - this.unsubscribeNetInfo = NetInfo.addEventListener((state: NetInfoState) => { - const { screen } = this.state; - const { isConnected, type, isConnectionExpensive } = this.state.netInfo; - if ( - isConnected !== state.isConnected || - type !== state.type || - isConnectionExpensive !== state.details?.isConnectionExpensive - ) { - this.setState({ - netInfo: { - isConnected: state.isConnected, - type: state.type, - isConnectionExpensive: state.details && state.details.isConnectionExpensive, - }, - screen: screen === 3 ? 3 : screen !== 0 ? 1 : 0, - //actionButtonsDisabled: true, - }); - if (isConnected !== state.isConnected) { - if (!state.isConnected) { - //console.log('EVENT Loading: No internet connection.'); - this.setState({ - customServerShow: false, - }); - } else { - //console.log('EVENT Loading: YESSSSS internet connection.'); - // if it is offline & there is no wallet file - // the screen is going to be empty - // show the custom server component - if (this.state.selectLightWalletServer === SelectServerEnum.offline && !this.state.walletExists) { - this.setState({ - customServerShow: true, - }); - } - if (screen !== 0) { + this.unsubscribeNetInfo = NetInfo.addEventListener( + (state: NetInfoState) => { + const { screen } = this.state; + const { isConnected, type, isConnectionExpensive } = this.state.netInfo; + if ( + isConnected !== state.isConnected || + type !== state.type || + isConnectionExpensive !== state.details?.isConnectionExpensive + ) { + this.setState({ + netInfo: { + isConnected: state.isConnected, + type: state.type, + isConnectionExpensive: + state.details && state.details.isConnectionExpensive, + }, + screen: screen === 3 ? 3 : screen !== 0 ? 1 : 0, + //actionButtonsDisabled: true, + }); + if (isConnected !== state.isConnected) { + if (!state.isConnected) { + //console.log('EVENT Loading: No internet connection.'); this.setState({ - screen: screen === 3 ? 3 : screen !== 0 ? 1 : 0, + customServerShow: false, }); + } else { + //console.log('EVENT Loading: YESSSSS internet connection.'); + // if it is offline & there is no wallet file + // the screen is going to be empty + // show the custom server component + if ( + this.state.selectLightWalletServer === + SelectServerEnum.offline && + !this.state.walletExists + ) { + this.setState({ + customServerShow: true, + }); + } + if (screen !== 0) { + this.setState({ + screen: screen === 3 ? 3 : screen !== 0 ? 1 : 0, + }); + } } } } - } - }); + }, + ); // if it is offline & there is no wallet file // the screen is going to be empty // show the custom server component - if (netInfoState.isConnected && this.state.selectLightWalletServer === SelectServerEnum.offline && !this.state.walletExists) { + if ( + netInfoState.isConnected && + this.state.selectLightWalletServer === SelectServerEnum.offline && + !this.state.walletExists + ) { this.setState({ customServerShow: true, }); @@ -737,8 +874,12 @@ export class LoadingAppClass extends Component { this.dim && typeof this.dim.remove === 'function' && this.dim.remove(); - this.appstate && typeof this.appstate.remove === 'function' && this.appstate.remove(); - this.unsubscribeNetInfo && typeof this.unsubscribeNetInfo === 'function' && this.unsubscribeNetInfo(); + this.appstate && + typeof this.appstate.remove === 'function' && + this.appstate.remove(); + this.unsubscribeNetInfo && + typeof this.unsubscribeNetInfo === 'function' && + this.unsubscribeNetInfo(); }; selectTheBestServer = async (aDifferentOne: boolean): Promise => { @@ -747,7 +888,8 @@ export class LoadingAppClass extends Component !s.obsolete && s.uri !== (aDifferentOne ? actualServer.uri : ''), + (s: ServerUrisType) => + !s.obsolete && s.uri !== (aDifferentOne ? actualServer.uri : ''), ), ); let fasterServer: ServerType = {} as ServerType; @@ -766,19 +908,30 @@ export class LoadingAppClass extends Component Promise = async (server: ServerType) => { + checkServer: (s: ServerType) => Promise = async ( + server: ServerType, + ) => { const s = { uri: server.uri, chainName: server.chainName, @@ -804,10 +959,19 @@ export class LoadingAppClass extends Component { + walletErrorHandle = async ( + result: string, + title: string, + screen: number, + start: boolean, + ) => { // first check the actual server // if the server is not working properly sometimes can take more than one minute to fail. - if (start && this.state.netInfo.isConnected && this.state.selectLightWalletServer !== SelectServerEnum.offline) { + if ( + start && + this.state.netInfo.isConnected && + this.state.selectLightWalletServer !== SelectServerEnum.offline + ) { this.addLastSnackbar({ message: this.state.translate('restarting') as string, duration: SnackbarDurationEnum.long, @@ -816,7 +980,10 @@ export class LoadingAppClass extends Component show the error. // if Offline mode -> show the error. - if (!this.state.netInfo.isConnected || this.state.selectLightWalletServer === SelectServerEnum.offline) { + if ( + !this.state.netInfo.isConnected || + this.state.selectLightWalletServer === SelectServerEnum.offline + ) { createAlert( this.setBackgroundError, this.addLastSnackbar, @@ -828,9 +995,15 @@ export class LoadingAppClass extends Component this error is something not related with the server availability createAlert( @@ -844,14 +1017,20 @@ export class LoadingAppClass extends Component { - const backgroundJson: BackgroundType = await BackgroundFileImpl.readBackground(); + const backgroundJson: BackgroundType = + await BackgroundFileImpl.readBackground(); this.setState({ background: backgroundJson }); }; @@ -946,25 +1140,40 @@ export class LoadingAppClass extends Component { + navigateToLoadedApp = ( + readOnly: boolean, + orchardPool: boolean, + saplingPool: boolean, + transparentPool: boolean, + firstLaunchingMessage: LaunchingModeEnum, + ) => { this.props.navigationApp.reset({ index: 0, routes: [ { name: RouteEnum.LoadedApp, - params: { readOnly, orchardPool, saplingPool, transparentPool, firstLaunchingMessage }, + params: { + readOnly, + orchardPool, + saplingPool, + transparentPool, + firstLaunchingMessage, + }, }, ], }); }; createNewWallet = async (goSeedScreen: boolean = true): Promise => { - if (!this.state.netInfo.isConnected || this.state.selectLightWalletServer === SelectServerEnum.offline) { - this.addLastSnackbar({ message: this.state.translate('loadedapp.connection-error') as string, screenName: [this.screenName] }); + if ( + !this.state.netInfo.isConnected || + this.state.selectLightWalletServer === SelectServerEnum.offline + ) { + this.addLastSnackbar({ + message: this.state.translate('loadedapp.connection-error') as string, + screenName: [this.screenName], + }); return; } this.setState({ actionButtonsDisabled: true }); @@ -1055,7 +1291,10 @@ export class LoadingAppClass extends Component { const newSnackbars = this.state.snackbars; // if the last one is the same don't do anything. - if (newSnackbars.length > 0 && newSnackbars[newSnackbars.length - 1].message === snackbar.message) { + if ( + newSnackbars.length > 0 && + newSnackbars[newSnackbars.length - 1].message === snackbar.message + ) { return; } newSnackbars.push(snackbar); @@ -1324,7 +1603,10 @@ export class LoadingAppClass extends Component { Alert.alert( this.props.translate('loadedapp.walletseed-basic') as string, - (security ? '' : ((this.props.translate('loadingapp.recoverkeysinstall') + '\n\n') as string)) + txt, + (security + ? '' + : ((this.props.translate('loadingapp.recoverkeysinstall') + + '\n\n') as string)) + txt, [ { text: this.props.translate('copy') as string, @@ -1337,7 +1619,10 @@ export class LoadingAppClass extends Component zingolib version - ', Date.now() - start); + console.log( + '=========================================== > zingolib version - ', + Date.now() - start, + ); } if (zingolibStr) { if (zingolibStr.toLowerCase().startsWith(GlobalConst.error)) { @@ -1385,7 +1673,6 @@ export class LoadingAppClass extends Component { - this.setState({ biometricsFailed: false }, () => this.componentDidMount()); + this.setState({ biometricsFailed: false }, () => + this.componentDidMount(), + ); }} /> )} @@ -1491,9 +1780,26 @@ export class LoadingAppClass extends Component this.navigateToLoadedApp(readOnly, orchardPool, saplingPool, transparentPool, firstLaunchingMessage)}> + onRequestClose={() => + this.navigateToLoadedApp( + readOnly, + orchardPool, + saplingPool, + transparentPool, + firstLaunchingMessage, + ) + } + > this.navigateToLoadedApp(readOnly, orchardPool, saplingPool, transparentPool, firstLaunchingMessage)} + onClickOK={() => + this.navigateToLoadedApp( + readOnly, + orchardPool, + saplingPool, + transparentPool, + firstLaunchingMessage, + ) + } /> )} @@ -1502,7 +1808,8 @@ export class LoadingAppClass extends Component this.setState({ screen: 1 })}> + onRequestClose={() => this.setState({ screen: 1 })} + > this.doRestore(s, b)} onClickCancel={() => this.setState({ screen: 1 })} diff --git a/app/LoadingApp/components/ImportUfvk.tsx b/app/LoadingApp/components/ImportUfvk.tsx index 02815675c..69b2c372f 100644 --- a/app/LoadingApp/components/ImportUfvk.tsx +++ b/app/LoadingApp/components/ImportUfvk.tsx @@ -22,7 +22,12 @@ import { ThemeType } from '../../types'; import { ContextAppLoading } from '../../context'; import Header from '../../../components/Header'; import RPCModule from '../../RPCModule'; -import { ButtonTypeEnum, GlobalConst, ScreenEnum, SelectServerEnum } from '../../AppState'; +import { + ButtonTypeEnum, + GlobalConst, + ScreenEnum, + SelectServerEnum, +} from '../../AppState'; import Snackbars from '../../../components/Components/Snackbars'; import { ToastProvider } from 'react-native-toastier'; @@ -30,10 +35,22 @@ type ImportUfvkProps = { onClickCancel: () => void; onClickOK: (keyText: string, birthday: number) => void; }; -const ImportUfvk: React.FunctionComponent = ({ onClickCancel, onClickOK }) => { +const ImportUfvk: React.FunctionComponent = ({ + onClickCancel, + onClickOK, +}) => { const context = useContext(ContextAppLoading); - const { translate, netInfo, lightWalletserver, mode, addLastSnackbar, selectLightWalletServer, snackbars, removeFirstSnackbar } = context; - const { colors } = useTheme() as ThemeType; + const { + translate, + netInfo, + lightWalletserver, + mode, + addLastSnackbar, + selectLightWalletServer, + snackbars, + removeFirstSnackbar, + } = context; + const { colors } = useTheme() as ThemeType; const screenName = ScreenEnum.ImportUfvk; const [seedufvkText, setSeedufvkText] = useState(''); @@ -42,17 +59,36 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o const [latestBlock, setLatestBlock] = useState(0); useEffect(() => { - if (!netInfo.isConnected || selectLightWalletServer !== SelectServerEnum.offline) { - (async () => { - const resp: string = await RPCModule.getLatestBlockServerInfo(lightWalletserver.uri); - //console.log(resp); - if (resp && !resp.toLowerCase().startsWith(GlobalConst.error)) { - setLatestBlock(Number(resp)); - } else { - //console.log('error latest block', resp); - } - })(); + if ( + !netInfo.isConnected || + selectLightWalletServer === SelectServerEnum.offline + ) { + return; } + + let cancelled = false; + + const fetchLatestBlock = async () => { + try { + const height = await RPCModule.getLatestBlockServerInfo( + lightWalletserver.uri, + ); + + if (!cancelled) { + setLatestBlock(height); + } + } catch { + if (!cancelled) { + // TODO: What should we do here? + } + } + }; + + fetchLatestBlock(); + + return () => { + cancelled = true; + }; }, [lightWalletserver, selectLightWalletServer, netInfo.isConnected]); useEffect(() => { @@ -62,13 +98,20 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o seedufvkText.toLowerCase().startsWith(GlobalConst.utestview) ) { // if it is a ufvk - const seedufvkTextArray: string[] = seedufvkText.replaceAll('\n', ' ').trim().replaceAll(' ', ' ').split(' '); + const seedufvkTextArray: string[] = seedufvkText + .replaceAll('\n', ' ') + .trim() + .replaceAll(' ', ' ') + .split(' '); //console.log(seedufvkTextArray); // if the ufvk have 2 -> means it is a copy/paste from the stored ufvk in the device. if (seedufvkTextArray.length === 2) { // if the last word is a number -> move it to the birthday field - const lastWord: string = seedufvkTextArray[seedufvkTextArray.length - 1]; - const possibleBirthday: number | null = isNaN(Number(lastWord)) ? null : Number(lastWord); + const lastWord: string = + seedufvkTextArray[seedufvkTextArray.length - 1]; + const possibleBirthday: number | null = isNaN(Number(lastWord)) + ? null + : Number(lastWord); if (possibleBirthday && !birthday) { setBirthday(possibleBirthday.toString()); setSeedufvkText(seedufvkTextArray.slice(0, 1).join(' ')); @@ -76,13 +119,20 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o } } else { // if it is a seed - const seedufvkTextArray: string[] = seedufvkText.replaceAll('\n', ' ').trim().replaceAll(' ', ' ').split(' '); + const seedufvkTextArray: string[] = seedufvkText + .replaceAll('\n', ' ') + .trim() + .replaceAll(' ', ' ') + .split(' '); //console.log(seedufvkTextArray); // if the seed have 25 -> means it is a copy/paste from the stored seed in the device. if (seedufvkTextArray.length === 25) { // if the last word is a number -> move it to the birthday field - const lastWord: string = seedufvkTextArray[seedufvkTextArray.length - 1]; - const possibleBirthday: number | null = isNaN(Number(lastWord)) ? null : Number(lastWord); + const lastWord: string = + seedufvkTextArray[seedufvkTextArray.length - 1]; + const possibleBirthday: number | null = isNaN(Number(lastWord)) + ? null + : Number(lastWord); if (possibleBirthday && !birthday) { setBirthday(possibleBirthday.toString()); setSeedufvkText(seedufvkTextArray.slice(0, 24).join(' ')); @@ -95,8 +145,14 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o }, [seedufvkText]); const okButton = async () => { - if (!netInfo.isConnected || selectLightWalletServer === SelectServerEnum.offline) { - addLastSnackbar({ message: translate('loadedapp.connection-error') as string, screenName: [screenName] }); + if ( + !netInfo.isConnected || + selectLightWalletServer === SelectServerEnum.offline + ) { + addLastSnackbar({ + message: translate('loadedapp.connection-error') as string, + screenName: [screenName], + }); return; } onClickOK(seedufvkText.trimEnd().trimStart(), Number(birthday)); @@ -109,7 +165,7 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o // setSeedufvkText(a); // }); //} else { - setQrcodeModalVisible(true); + setQrcodeModalVisible(true); //} }; @@ -122,8 +178,12 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o /> = ({ onClickCancel, o style={{ flex: 1, backgroundColor: colors.background, - }}> + }} + > setQrcodeModalVisible(false)}> - setQrcodeModalVisible(false)} /> + onRequestClose={() => setQrcodeModalVisible(false)} + > + setQrcodeModalVisible(false)} + />
= ({ onClickCancel, o flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-start', - }}> - + }} + > + {translate('import.key-label') as string} = ({ onClickCancel, o maxHeight: '40%', flexDirection: 'row', justifyContent: 'space-between', - }}> + }} + > = ({ onClickCancel, o width: 'auto', flex: 1, justifyContent: 'center', - }}> + }} + > = ({ onClickCancel, o { setSeedufvkText(''); - }}> - + }} + > + )} { showQrcodeModalVisible(); - }}> - + }} + > + @@ -224,7 +305,10 @@ const ImportUfvk: React.FunctionComponent = ({ onClickCancel, o {translate('import.birthday') as string} {selectLightWalletServer !== SelectServerEnum.offline && ( - {translate('seed.birthday-no-readonly') + ' (1, ' + (latestBlock ? latestBlock.toString() : '--') + ')'} + {translate('seed.birthday-no-readonly') + + ' (1, ' + + (latestBlock ? latestBlock.toString() : '--') + + ')'} )} = ({ onClickCancel, o maxHeight: 48, minWidth: '20%', minHeight: 48, - }}> + }} + > = ({ onClickCancel, o setBirthday(''); } else if ( Number(text) <= 0 || - (Number(text) > latestBlock && selectLightWalletServer !== SelectServerEnum.offline) + (Number(text) > latestBlock && + selectLightWalletServer !== SelectServerEnum.offline) ) { setBirthday(''); } else { - setBirthday(Number(text.replace('.', '').replace(',', '')).toFixed(0)); + setBirthday( + Number(text.replace('.', '').replace(',', '')).toFixed( + 0, + ), + ); } }} - editable={latestBlock ? true : selectLightWalletServer !== SelectServerEnum.offline ? false : true} + editable={ + latestBlock + ? true + : selectLightWalletServer !== SelectServerEnum.offline + ? false + : true + } keyboardType="numeric" /> - {translate('import.text') as string} + + {translate('import.text') as string} + = ({ onClickCancel, o justifyContent: 'center', alignItems: 'center', marginVertical: 5, - }}> + }} + >