Merge branch 'master' into receietab
|
@ -1,8 +1,6 @@
|
|||
/* eslint react/prop-types: "off", react-native/no-inline-styles: "off" */
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, forwardRef } from 'react';
|
||||
import React, { forwardRef } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
|
@ -18,9 +16,8 @@ import {
|
|||
} from 'react-native';
|
||||
import { Icon, Text } from 'react-native-elements';
|
||||
|
||||
import { BlueCurrentTheme, useTheme } from './components/themes';
|
||||
import loc, { formatStringAddTwoWhiteSpaces } from './loc';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees';
|
||||
import { useTheme } from './components/themes';
|
||||
import loc from './loc';
|
||||
|
||||
const { height, width } = Dimensions.get('window');
|
||||
const aspectRatio = height / width;
|
||||
|
@ -203,165 +200,6 @@ export const BlueLoading = props => {
|
|||
);
|
||||
};
|
||||
|
||||
export class BlueReplaceFeeSuggestions extends Component {
|
||||
static propTypes = {
|
||||
onFeeSelected: PropTypes.func.isRequired,
|
||||
transactionMinimum: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
transactionMinimum: 1,
|
||||
};
|
||||
|
||||
state = {
|
||||
customFeeValue: '1',
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
try {
|
||||
const cachedNetworkTransactionFees = JSON.parse(await AsyncStorage.getItem(NetworkTransactionFee.StorageKey));
|
||||
|
||||
if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) {
|
||||
this.setState({ networkFees: cachedNetworkTransactionFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
|
||||
}
|
||||
} catch (_) {}
|
||||
const networkFees = await NetworkTransactionFees.recommendedFees();
|
||||
this.setState({ networkFees }, () => this.onFeeSelected(NetworkTransactionFeeType.FAST));
|
||||
}
|
||||
|
||||
onFeeSelected = selectedFeeType => {
|
||||
if (selectedFeeType !== NetworkTransactionFeeType.CUSTOM) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
if (selectedFeeType === NetworkTransactionFeeType.FAST) {
|
||||
this.props.onFeeSelected(this.state.networkFees.fastestFee);
|
||||
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.fastestFee));
|
||||
} else if (selectedFeeType === NetworkTransactionFeeType.MEDIUM) {
|
||||
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.mediumFee));
|
||||
} else if (selectedFeeType === NetworkTransactionFeeType.SLOW) {
|
||||
this.setState({ selectedFeeType }, () => this.props.onFeeSelected(this.state.networkFees.slowFee));
|
||||
} else if (selectedFeeType === NetworkTransactionFeeType.CUSTOM) {
|
||||
this.props.onFeeSelected(Number(this.state.customFeeValue));
|
||||
}
|
||||
};
|
||||
|
||||
onCustomFeeTextChange = customFee => {
|
||||
const customFeeValue = customFee.replace(/[^0-9]/g, '');
|
||||
this.setState({ customFeeValue, selectedFeeType: NetworkTransactionFeeType.CUSTOM }, () => {
|
||||
this.onFeeSelected(NetworkTransactionFeeType.CUSTOM);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { networkFees, selectedFeeType } = this.state;
|
||||
|
||||
return (
|
||||
<View>
|
||||
{networkFees &&
|
||||
[
|
||||
{
|
||||
label: loc.send.fee_fast,
|
||||
time: loc.send.fee_10m,
|
||||
type: NetworkTransactionFeeType.FAST,
|
||||
rate: networkFees.fastestFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.FAST,
|
||||
},
|
||||
{
|
||||
label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium),
|
||||
time: loc.send.fee_3h,
|
||||
type: NetworkTransactionFeeType.MEDIUM,
|
||||
rate: networkFees.mediumFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.MEDIUM,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_slow,
|
||||
time: loc.send.fee_1d,
|
||||
type: NetworkTransactionFeeType.SLOW,
|
||||
rate: networkFees.slowFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.SLOW,
|
||||
},
|
||||
].map(({ label, type, time, rate, active }, index) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
key={label}
|
||||
onPress={() => this.onFeeSelected(type)}
|
||||
style={[
|
||||
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
|
||||
active && { borderRadius: 8, backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor },
|
||||
]}
|
||||
>
|
||||
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 22, color: BlueCurrentTheme.colors.successColor, fontWeight: '600' }}>{label}</Text>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: BlueCurrentTheme.colors.successColor,
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: BlueCurrentTheme.colors.background }}>~{time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ justifyContent: 'flex-end', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ color: BlueCurrentTheme.colors.successColor }}>{rate} sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => this.customTextInput.focus()}
|
||||
style={[
|
||||
{ paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 },
|
||||
selectedFeeType === NetworkTransactionFeeType.CUSTOM && {
|
||||
borderRadius: 8,
|
||||
backgroundColor: BlueCurrentTheme.colors.incomingBackgroundColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ fontSize: 22, color: BlueCurrentTheme.colors.successColor, fontWeight: '600' }}>
|
||||
{formatStringAddTwoWhiteSpaces(loc.send.fee_custom)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ justifyContent: 'space-between', flexDirection: 'row', alignItems: 'center', marginTop: 5 }}>
|
||||
<TextInput
|
||||
onChangeText={this.onCustomFeeTextChange}
|
||||
keyboardType="numeric"
|
||||
value={this.state.customFeeValue}
|
||||
ref={ref => (this.customTextInput = ref)}
|
||||
maxLength={9}
|
||||
style={{
|
||||
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
|
||||
borderBottomColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderBottomWidth: 0.5,
|
||||
borderColor: BlueCurrentTheme.colors.formBorder,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1.0,
|
||||
color: '#81868e',
|
||||
flex: 1,
|
||||
marginRight: 10,
|
||||
minHeight: 33,
|
||||
paddingRight: 5,
|
||||
paddingLeft: 5,
|
||||
}}
|
||||
onFocus={() => this.onCustomFeeTextChange(this.state.customFeeValue)}
|
||||
defaultValue={this.props.transactionMinimum}
|
||||
placeholder={loc.send.fee_satvbyte}
|
||||
placeholderTextColor="#81868e"
|
||||
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
|
||||
/>
|
||||
<Text style={{ color: BlueCurrentTheme.colors.successColor }}>sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<BlueText style={{ color: BlueCurrentTheme.colors.alternativeTextColor }}>
|
||||
{loc.formatString(loc.send.fee_replace_minvb, { min: this.props.transactionMinimum })}
|
||||
</BlueText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function BlueBigCheckmark({ style = {} }) {
|
||||
const defaultStyles = {
|
||||
backgroundColor: '#ccddf9',
|
||||
|
|
|
@ -28,3 +28,9 @@ export function popToTop() {
|
|||
navigationRef.current?.dispatch(StackActions.popToTop());
|
||||
}
|
||||
}
|
||||
|
||||
export function pop() {
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.current?.dispatch(StackActions.pop());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
}
|
||||
apply plugin: 'com.google.gms.google-services' // Google Services plugin
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ import android.view.View;
|
|||
import android.widget.RemoteViews;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
@ -153,20 +151,6 @@ public class BitcoinPriceWidget extends AppWidgetProvider {
|
|||
String currentTime = new SimpleDateFormat("hh:mm a", Locale.getDefault()).format(new Date());
|
||||
views.setTextViewText(R.id.last_updated, "Last Updated");
|
||||
views.setTextViewText(R.id.last_updated_time, currentTime);
|
||||
|
||||
if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
|
||||
views.setTextColor(R.id.last_updated, context.getResources().getColor(android.R.color.white));
|
||||
views.setTextColor(R.id.last_updated_time, context.getResources().getColor(android.R.color.white));
|
||||
views.setTextColor(R.id.price_value, context.getResources().getColor(android.R.color.white));
|
||||
views.setTextColor(R.id.previous_price, context.getResources().getColor(android.R.color.white));
|
||||
views.setInt(R.id.widget_layout, "setBackgroundColor", context.getResources().getColor(android.R.color.black));
|
||||
} else {
|
||||
views.setTextColor(R.id.last_updated, context.getResources().getColor(android.R.color.black));
|
||||
views.setTextColor(R.id.last_updated_time, context.getResources().getColor(android.R.color.black));
|
||||
views.setTextColor(R.id.price_value, context.getResources().getColor(android.R.color.black));
|
||||
views.setTextColor(R.id.previous_price, context.getResources().getColor(android.R.color.black));
|
||||
views.setInt(R.id.widget_layout, "setBackgroundColor", context.getResources().getColor(android.R.color.white));
|
||||
}
|
||||
} else {
|
||||
String errorMessage = "Network Error";
|
||||
Log.e(TAG, errorMessage);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package io.bluewallet.bluewallet;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class CustomSegmentControlPackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
CustomSegmentedControlManager.registerIfNecessary();
|
||||
List<ViewManager> viewManagers = new ArrayList<>();
|
||||
viewManagers.add(new CustomSegmentedControlManager());
|
||||
return viewManagers;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package io.bluewallet.bluewallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
public class CustomSegmentedControlManager extends SimpleViewManager<CustomSegmentedControlManager.CustomSegmentedControlView> {
|
||||
public static final String REACT_CLASS = "CustomSegmentedControl";
|
||||
private static boolean isRegistered = false;
|
||||
|
||||
public static class CustomSegmentedControlView extends LinearLayout {
|
||||
private TabLayout tabLayout;
|
||||
private RCTEventEmitter eventEmitter;
|
||||
private int viewId;
|
||||
|
||||
public CustomSegmentedControlView(Context context) {
|
||||
super(context);
|
||||
setOrientation(LinearLayout.HORIZONTAL);
|
||||
tabLayout = new TabLayout(context);
|
||||
addView(tabLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putInt("selectedIndex", tab.getPosition());
|
||||
if (eventEmitter != null) {
|
||||
eventEmitter.receiveEvent(viewId, "topChange", event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {}
|
||||
});
|
||||
}
|
||||
|
||||
public void setValues(ReadableArray values) {
|
||||
tabLayout.removeAllTabs();
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
tabLayout.addTab(tabLayout.newTab().setText(values.getString(i)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelectedIndex(int selectedIndex) {
|
||||
if (selectedIndex >= 0 && selectedIndex < tabLayout.getTabCount()) {
|
||||
TabLayout.Tab tab = tabLayout.getTabAt(selectedIndex);
|
||||
if (tab != null) {
|
||||
tab.select();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEventEmitter(RCTEventEmitter eventEmitter, int viewId) {
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.viewId = viewId;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected CustomSegmentedControlView createViewInstance(@NonNull ThemedReactContext reactContext) {
|
||||
CustomSegmentedControlView view = new CustomSegmentedControlView(reactContext);
|
||||
view.setEventEmitter(reactContext.getJSModule(RCTEventEmitter.class), view.getId());
|
||||
return view;
|
||||
}
|
||||
|
||||
@ReactProp(name = "values")
|
||||
public void setValues(CustomSegmentedControlView view, ReadableArray values) {
|
||||
view.setValues(values);
|
||||
}
|
||||
|
||||
@ReactProp(name = "selectedIndex")
|
||||
public void setSelectedIndex(CustomSegmentedControlView view, int selectedIndex) {
|
||||
view.setSelectedIndex(selectedIndex);
|
||||
}
|
||||
|
||||
public static void registerIfNecessary() {
|
||||
if (!isRegistered) {
|
||||
isRegistered = true;
|
||||
// Registration logic if necessary
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,8 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
List<ReactPackage> 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 CustomSegmentControlPackage());
|
||||
CustomSegmentedControlManager.registerIfNecessary();
|
||||
return packages;
|
||||
}
|
||||
|
||||
|
@ -40,15 +41,15 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
return "index";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNewArchEnabled() {
|
||||
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean isHermesEnabled() {
|
||||
return BuildConfig.IS_HERMES_ENABLED;
|
||||
}
|
||||
@Override
|
||||
protected boolean isNewArchEnabled() {
|
||||
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean isHermesEnabled() {
|
||||
return BuildConfig.IS_HERMES_ENABLED;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
|
@ -62,19 +63,19 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
|
||||
sharedI18nUtilInstance.allowRTL(getApplicationContext(), true);
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
DefaultNewArchitectureEntryPoint.load();
|
||||
}
|
||||
SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE);
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
DefaultNewArchitectureEntryPoint.load();
|
||||
}
|
||||
SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE);
|
||||
|
||||
// Retrieve the "donottrack" value. Default to "0" if not found.
|
||||
String isDoNotTrackEnabled = sharedPref.getString("donottrack", "0");
|
||||
// Retrieve the "donottrack" value. Default to "0" if not found.
|
||||
String isDoNotTrackEnabled = sharedPref.getString("donottrack", "0");
|
||||
|
||||
// Check if do not track is not enabled and initialize Bugsnag if so
|
||||
if (!isDoNotTrackEnabled.equals("1")) {
|
||||
// Initialize Bugsnag or your error tracking here
|
||||
Bugsnag.start(this);
|
||||
}
|
||||
// Check if do not track is not enabled and initialize Bugsnag if so
|
||||
if (!isDoNotTrackEnabled.equals("1")) {
|
||||
// Initialize Bugsnag or your error tracking here
|
||||
Bugsnag.start(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:background="@android:color/white">
|
||||
android:background="@color/widget_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -15,21 +15,21 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/last_updated"
|
||||
style="@style/WidgetTextSecondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_updated_time"
|
||||
style="@style/WidgetTextPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="2dp"/>
|
||||
</LinearLayout>
|
||||
|
@ -42,12 +42,12 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/price_value"
|
||||
style="@style/WidgetTextPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
|
@ -67,11 +67,11 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/previous_price"
|
||||
style="@style/WidgetTextPrimary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=""
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<resources>
|
||||
<color name="background_color">#000000</color> <!-- Dark background -->
|
||||
<color name="background_color">#000000</color>
|
||||
<color name="widget_background">#1B1B1F</color>
|
||||
<color name="text_primary">#FFFFFF</color>
|
||||
<color name="text_secondary">#D3D3D3</color>
|
||||
</resources>
|
||||
|
||||
|
|
9
android/app/src/main/res/values-night/styles.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<resources>
|
||||
<style name="WidgetTextPrimary">
|
||||
<item name="android:textColor">@color/text_primary</item>
|
||||
</style>
|
||||
|
||||
<style name="WidgetTextSecondary">
|
||||
<item name="android:textColor">@color/text_secondary</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -1,4 +1,7 @@
|
|||
<resources>
|
||||
<color name="white">#FFF</color>
|
||||
<color name="background_color">#FFFFFF</color> <!-- Light background -->
|
||||
<color name="background_color">#FFFFFF</color>
|
||||
<color name="widget_background">#FFFFFF</color>
|
||||
<color name="text_primary">#0C234F</color>
|
||||
<color name="text_secondary">#D3D3D3</color>
|
||||
</resources>
|
|
@ -1,9 +1,14 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
</style>
|
||||
<style name="WidgetTextPrimary">
|
||||
<item name="android:textColor">@color/text_primary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
<style name="WidgetTextSecondary">
|
||||
<item name="android:textColor">@color/text_secondary</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -159,9 +159,10 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
}
|
||||
|
||||
async generateFromEntropy(user: Buffer) {
|
||||
const random = await randomBytes(user.length < 32 ? 32 - user.length : 0);
|
||||
const buf = Buffer.concat([user, random], 32);
|
||||
this.secret = bip39.entropyToMnemonic(buf.toString('hex'));
|
||||
if (user.length !== 32 && user.length !== 16) {
|
||||
throw new Error('Entropy has to be 16 or 32 bytes long');
|
||||
}
|
||||
this.secret = bip39.entropyToMnemonic(user.toString('hex'));
|
||||
}
|
||||
|
||||
_getExternalWIFByIndex(index: number): string | false {
|
||||
|
|
|
@ -63,18 +63,10 @@ export class LegacyWallet extends AbstractWallet {
|
|||
}
|
||||
|
||||
async generateFromEntropy(user: Buffer): Promise<void> {
|
||||
let i = 0;
|
||||
do {
|
||||
i += 1;
|
||||
const random = await randomBytes(user.length < 32 ? 32 - user.length : 0);
|
||||
const buf = Buffer.concat([user, random], 32);
|
||||
try {
|
||||
this.secret = ECPair.fromPrivateKey(buf).toWIF();
|
||||
return;
|
||||
} catch (e) {
|
||||
if (i === 5) throw e;
|
||||
}
|
||||
} while (true);
|
||||
if (user.length !== 32) {
|
||||
throw new Error('Entropy should be 32 bytes');
|
||||
}
|
||||
this.secret = ECPair.fromPrivateKey(user).toWIF();
|
||||
}
|
||||
|
||||
getAddress(): string | false {
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'react-native-gesture-handler'; // should be on top
|
|||
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
import React, { lazy, Suspense, useCallback, useEffect, useRef } from 'react';
|
||||
import { AppState, AppStateStatus, Linking, NativeEventEmitter, NativeModules, Platform, UIManager } from 'react-native';
|
||||
import { AppState, AppStateStatus, Linking, NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
||||
|
||||
import A from '../blue_modules/analytics';
|
||||
import BlueClipboard from '../blue_modules/clipboard';
|
||||
|
@ -31,12 +31,6 @@ const ClipboardContentType = Object.freeze({
|
|||
LIGHTNING: 'LIGHTNING',
|
||||
});
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}
|
||||
|
||||
const CompanionDelegates = () => {
|
||||
const { wallets, addWallet, saveToDisk, fetchAndSaveWalletTransactions, refreshAllWalletTransactions, setSharedCosigner } = useStorage();
|
||||
const appState = useRef<AppStateStatus>(AppState.currentState);
|
||||
|
|
|
@ -131,7 +131,7 @@ interface FButtonProps {
|
|||
last?: boolean;
|
||||
disabled?: boolean;
|
||||
onPress: () => void;
|
||||
onLongPress: () => void;
|
||||
onLongPress?: () => void;
|
||||
}
|
||||
|
||||
export const FButton = ({ text, icon, width, first, last, ...props }: FButtonProps) => {
|
||||
|
|
210
components/ReplaceFeeSuggestions.tsx
Normal file
|
@ -0,0 +1,210 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, Keyboard, StyleSheet } from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { BlueText } from '../BlueComponents';
|
||||
import loc, { formatStringAddTwoWhiteSpaces } from '../loc';
|
||||
import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from '../models/networkTransactionFees';
|
||||
import { useTheme } from './themes';
|
||||
|
||||
interface ReplaceFeeSuggestionsProps {
|
||||
onFeeSelected: (fee: number) => void;
|
||||
transactionMinimum?: number;
|
||||
}
|
||||
|
||||
const ReplaceFeeSuggestions: React.FC<ReplaceFeeSuggestionsProps> = ({ onFeeSelected, transactionMinimum = 1 }) => {
|
||||
const [networkFees, setNetworkFees] = useState<NetworkTransactionFee | null>(null);
|
||||
const [selectedFeeType, setSelectedFeeType] = useState<NetworkTransactionFeeType>(NetworkTransactionFeeType.FAST);
|
||||
const [customFeeValue, setCustomFeeValue] = useState<string>('1');
|
||||
const customTextInput = useRef<TextInput>(null);
|
||||
const { colors } = useTheme();
|
||||
const stylesHook = StyleSheet.create({
|
||||
activeButton: {
|
||||
backgroundColor: colors.incomingBackgroundColor,
|
||||
},
|
||||
buttonText: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
timeContainer: {
|
||||
backgroundColor: colors.successColor,
|
||||
},
|
||||
timeText: {
|
||||
color: colors.background,
|
||||
},
|
||||
rateText: {
|
||||
color: colors.successColor,
|
||||
},
|
||||
customFeeInput: {
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
borderBottomColor: colors.formBorder,
|
||||
borderColor: colors.formBorder,
|
||||
},
|
||||
alternativeText: {
|
||||
color: colors.alternativeTextColor,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchNetworkFees = useCallback(async () => {
|
||||
try {
|
||||
const cachedNetworkTransactionFees = JSON.parse((await AsyncStorage.getItem(NetworkTransactionFee.StorageKey)) || '{}');
|
||||
|
||||
if (cachedNetworkTransactionFees && 'fastestFee' in cachedNetworkTransactionFees) {
|
||||
setNetworkFees(cachedNetworkTransactionFees);
|
||||
onFeeSelected(cachedNetworkTransactionFees.fastestFee);
|
||||
setSelectedFeeType(NetworkTransactionFeeType.FAST);
|
||||
}
|
||||
} catch (_) {}
|
||||
const fees = await NetworkTransactionFees.recommendedFees();
|
||||
setNetworkFees(fees);
|
||||
onFeeSelected(fees.fastestFee);
|
||||
setSelectedFeeType(NetworkTransactionFeeType.FAST);
|
||||
}, [onFeeSelected]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchNetworkFees();
|
||||
}, [fetchNetworkFees]);
|
||||
|
||||
const handleFeeSelection = (feeType: NetworkTransactionFeeType) => {
|
||||
if (feeType !== NetworkTransactionFeeType.CUSTOM) {
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
if (networkFees) {
|
||||
let selectedFee: number;
|
||||
switch (feeType) {
|
||||
case NetworkTransactionFeeType.FAST:
|
||||
selectedFee = networkFees.fastestFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.MEDIUM:
|
||||
selectedFee = networkFees.mediumFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.SLOW:
|
||||
selectedFee = networkFees.slowFee;
|
||||
break;
|
||||
case NetworkTransactionFeeType.CUSTOM:
|
||||
selectedFee = Number(customFeeValue);
|
||||
break;
|
||||
}
|
||||
onFeeSelected(selectedFee);
|
||||
setSelectedFeeType(feeType);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCustomFeeChange = (customFee: string) => {
|
||||
const sanitizedFee = customFee.replace(/[^0-9]/g, '');
|
||||
setCustomFeeValue(sanitizedFee);
|
||||
handleFeeSelection(NetworkTransactionFeeType.CUSTOM);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
{networkFees &&
|
||||
[
|
||||
{
|
||||
label: loc.send.fee_fast,
|
||||
time: loc.send.fee_10m,
|
||||
type: NetworkTransactionFeeType.FAST,
|
||||
rate: networkFees.fastestFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.FAST,
|
||||
},
|
||||
{
|
||||
label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium),
|
||||
time: loc.send.fee_3h,
|
||||
type: NetworkTransactionFeeType.MEDIUM,
|
||||
rate: networkFees.mediumFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.MEDIUM,
|
||||
},
|
||||
{
|
||||
label: loc.send.fee_slow,
|
||||
time: loc.send.fee_1d,
|
||||
type: NetworkTransactionFeeType.SLOW,
|
||||
rate: networkFees.slowFee,
|
||||
active: selectedFeeType === NetworkTransactionFeeType.SLOW,
|
||||
},
|
||||
].map(({ label, type, time, rate, active }) => (
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
key={label}
|
||||
onPress={() => handleFeeSelection(type)}
|
||||
style={[styles.button, active && stylesHook.activeButton]}
|
||||
>
|
||||
<View style={styles.buttonContent}>
|
||||
<Text style={[styles.buttonText, stylesHook.buttonText]}>{label}</Text>
|
||||
<View style={[styles.timeContainer, stylesHook.timeContainer]}>
|
||||
<Text style={stylesHook.timeText}>~{time}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.rateContainer}>
|
||||
<Text style={stylesHook.rateText}>{rate} sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => customTextInput.current?.focus()}
|
||||
style={[styles.button, selectedFeeType === NetworkTransactionFeeType.CUSTOM && stylesHook.activeButton]}
|
||||
>
|
||||
<View style={styles.buttonContent}>
|
||||
<Text style={[styles.buttonText, stylesHook.buttonText]}>{formatStringAddTwoWhiteSpaces(loc.send.fee_custom)}</Text>
|
||||
</View>
|
||||
<View style={[styles.buttonContent, styles.customFeeInputContainer]}>
|
||||
<TextInput
|
||||
onChangeText={handleCustomFeeChange}
|
||||
keyboardType="numeric"
|
||||
value={customFeeValue}
|
||||
ref={customTextInput}
|
||||
maxLength={9}
|
||||
style={[styles.customFeeInput, stylesHook.customFeeInput]}
|
||||
onFocus={() => handleCustomFeeChange(customFeeValue)}
|
||||
placeholder={loc.send.fee_satvbyte}
|
||||
placeholderTextColor="#81868e"
|
||||
/>
|
||||
<Text style={stylesHook.rateText}>sat/byte</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<BlueText style={stylesHook.alternativeText}>{loc.formatString(loc.send.fee_replace_minvb, { min: transactionMinimum })}</BlueText>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
marginBottom: 10,
|
||||
borderRadius: 8,
|
||||
},
|
||||
buttonContent: {
|
||||
justifyContent: 'space-between',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
fontSize: 22,
|
||||
fontWeight: '600',
|
||||
},
|
||||
timeContainer: {
|
||||
borderRadius: 5,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 3,
|
||||
},
|
||||
rateContainer: {
|
||||
justifyContent: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
customFeeInputContainer: {
|
||||
marginTop: 5,
|
||||
},
|
||||
customFeeInput: {
|
||||
borderBottomWidth: 0.5,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1.0,
|
||||
color: '#81868e',
|
||||
flex: 1,
|
||||
marginRight: 10,
|
||||
minHeight: 33,
|
||||
paddingRight: 5,
|
||||
paddingLeft: 5,
|
||||
},
|
||||
});
|
||||
|
||||
export default ReplaceFeeSuggestions;
|
44
components/SegmentControl.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import { requireNativeComponent, View, StyleSheet, NativeSyntheticEvent } from 'react-native';
|
||||
|
||||
interface SegmentedControlProps {
|
||||
values: string[];
|
||||
selectedIndex: number;
|
||||
onChange: (index: number) => void;
|
||||
}
|
||||
|
||||
interface SegmentedControlEvent {
|
||||
selectedIndex: number;
|
||||
}
|
||||
|
||||
interface NativeSegmentedControlProps {
|
||||
values: string[];
|
||||
selectedIndex: number;
|
||||
onChangeEvent: (event: NativeSyntheticEvent<SegmentedControlEvent>) => void;
|
||||
style?: object;
|
||||
}
|
||||
|
||||
const NativeSegmentedControl = requireNativeComponent<NativeSegmentedControlProps>('CustomSegmentedControl');
|
||||
|
||||
const SegmentedControl: React.FC<SegmentedControlProps> = ({ values, selectedIndex, onChange }) => {
|
||||
const handleChange = (event: NativeSyntheticEvent<SegmentedControlEvent>) => {
|
||||
onChange(event.nativeEvent.selectedIndex);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<NativeSegmentedControl values={values} selectedIndex={selectedIndex} style={styles.segmentedControl} onChangeEvent={handleChange} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: '100%',
|
||||
},
|
||||
segmentedControl: {
|
||||
height: 40,
|
||||
},
|
||||
});
|
||||
|
||||
export default SegmentedControl;
|
|
@ -154,7 +154,7 @@ const iStyles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
export const WalletCarouselItem = React.memo(({ item, _, onPress, handleLongPress, isSelectedWallet, customStyle }) => {
|
||||
export const WalletCarouselItem = React.memo(({ item, _, onPress, handleLongPress, isSelectedWallet, customStyle, horizontal }) => {
|
||||
const scaleValue = new Animated.Value(1.0);
|
||||
const { colors } = useTheme();
|
||||
const { walletTransactionUpdateStatus } = useStorage();
|
||||
|
@ -197,7 +197,7 @@ export const WalletCarouselItem = React.memo(({ item, _, onPress, handleLongPres
|
|||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
isLargeScreen ? iStyles.rootLargeDevice : customStyle ?? { ...iStyles.root, width: itemWidth },
|
||||
isLargeScreen || !horizontal ? iStyles.rootLargeDevice : customStyle ?? { ...iStyles.root, width: itemWidth },
|
||||
{ opacity, transform: [{ scale: scaleValue }] },
|
||||
]}
|
||||
>
|
||||
|
@ -287,6 +287,7 @@ const WalletsCarousel = forwardRef((props, ref) => {
|
|||
index={index}
|
||||
handleLongPress={handleLongPress}
|
||||
onPress={onPress}
|
||||
horizontal={horizontal}
|
||||
/>
|
||||
) : (
|
||||
<NewWalletPanel onPress={onPress} />
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, Text, View, Pressable, LayoutAnimation, Platform, UIManager, ViewStyle, TextStyle } from 'react-native';
|
||||
import loc from '../../loc';
|
||||
import { useTheme } from '../themes';
|
||||
|
||||
export const TABS = {
|
||||
EXTERNAL: 'receive',
|
||||
INTERNAL: 'change',
|
||||
} as const;
|
||||
|
||||
type TabKey = keyof typeof TABS;
|
||||
type TABS_VALUES = (typeof TABS)[keyof typeof TABS];
|
||||
|
||||
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
|
||||
interface AddressTypeTabsProps {
|
||||
currentTab: TABS_VALUES;
|
||||
setCurrentTab: (tab: TABS_VALUES) => void;
|
||||
customTabText?: { [key in TabKey]?: string };
|
||||
}
|
||||
|
||||
const AddressTypeTabs: React.FC<AddressTypeTabsProps> = ({ currentTab, setCurrentTab, customTabText }) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
activeTab: {
|
||||
backgroundColor: colors.modal,
|
||||
} as ViewStyle,
|
||||
activeText: {
|
||||
fontWeight: 'bold',
|
||||
color: colors.foregroundColor,
|
||||
} as TextStyle,
|
||||
inactiveTab: {
|
||||
fontWeight: 'normal',
|
||||
color: colors.foregroundColor,
|
||||
} as TextStyle,
|
||||
backTabs: {
|
||||
backgroundColor: colors.buttonDisabledBackgroundColor,
|
||||
} as ViewStyle,
|
||||
});
|
||||
|
||||
const tabs = Object.entries(TABS).map(([key, value]) => {
|
||||
return {
|
||||
key: key as TabKey,
|
||||
value,
|
||||
name: customTabText?.[key as TabKey] || loc.addresses[`type_${value}`],
|
||||
};
|
||||
});
|
||||
|
||||
const changeToTab = (tabKey: TabKey) => {
|
||||
if (tabKey in TABS) {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
||||
setCurrentTab(TABS[tabKey]);
|
||||
}
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const tabsButtons = tabs.map(tab => {
|
||||
const isActive = tab.value === currentTab;
|
||||
|
||||
const tabStyle = isActive ? stylesHook.activeTab : undefined;
|
||||
const textStyle = isActive ? stylesHook.activeText : stylesHook.inactiveTab;
|
||||
|
||||
return (
|
||||
<Pressable key={tab.key} onPress={() => changeToTab(tab.key)} style={[styles.tab, tabStyle]}>
|
||||
<Text style={textStyle}>{tab.name}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[stylesHook.backTabs, styles.backTabs]}>
|
||||
<View style={styles.tabs}>{tabsButtons}</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return render();
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
} as ViewStyle,
|
||||
backTabs: {
|
||||
padding: 4,
|
||||
marginVertical: 8,
|
||||
borderRadius: 8,
|
||||
} as ViewStyle,
|
||||
tabs: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
} as ViewStyle,
|
||||
tab: {
|
||||
borderRadius: 6,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 16,
|
||||
justifyContent: 'center',
|
||||
} as ViewStyle,
|
||||
});
|
||||
|
||||
export { AddressTypeTabs };
|
Before Width: | Height: | Size: 851 B After Width: | Height: | Size: 539 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 931 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 479 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 798 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 548 B After Width: | Height: | Size: 131 B |
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 189 B |
Before Width: | Height: | Size: 818 B After Width: | Height: | Size: 257 B |
BIN
img/close.png
Executable file → Normal file
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 166 B |
BIN
img/close@2x.png
Executable file → Normal file
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 220 B |
BIN
img/close@3x.png
Executable file → Normal file
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 17 KiB |
BIN
img/round-compare-arrows-24-px.png
Executable file → Normal file
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 199 B |
BIN
img/round-compare-arrows-24-px@2x.png
Executable file → Normal file
Before Width: | Height: | Size: 636 B After Width: | Height: | Size: 315 B |
BIN
img/round-compare-arrows-24-px@3x.png
Executable file → Normal file
Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 394 B After Width: | Height: | Size: 183 B |
Before Width: | Height: | Size: 623 B After Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 347 B |
BIN
img/scan.png
Executable file → Normal file
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 282 B |
BIN
img/scan@2x.png
Executable file → Normal file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 441 B |
BIN
img/scan@3x.png
Executable file → Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 538 B |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 3.5 KiB |
8
index.js
|
@ -1,7 +1,7 @@
|
|||
import './shim.js';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { AppRegistry, LogBox, Platform, UIManager } from 'react-native';
|
||||
import { AppRegistry, LogBox } from 'react-native';
|
||||
|
||||
import App from './App';
|
||||
import A from './blue_modules/analytics';
|
||||
|
@ -14,12 +14,6 @@ if (!Error.captureStackTrace) {
|
|||
|
||||
LogBox.ignoreLogs(['Require cycle:', 'Battery state `unknown` and monitoring disabled, this is normal for simulators and tvOS.']);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
if (UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||
}
|
||||
}
|
||||
|
||||
const BlueAppComponent = () => {
|
||||
useEffect(() => {
|
||||
restoreSavedPreferredFiatCurrencyAndExchangeFromStorage();
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
#import "AppDelegate.h"
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import "React/RCTViewManager.h"
|
||||
|
|
|
@ -141,8 +141,10 @@
|
|||
B450109D2C0FCD9F00619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
|
||||
B450109E2C0FCDA000619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
|
||||
B450109F2C0FCDA500619044 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B450109B2C0FCD8A00619044 /* Utilities.swift */; };
|
||||
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */; };
|
||||
B4549F362B82B10D002E3153 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = B4549F352B82B10D002E3153 /* ci_post_clone.sh */; };
|
||||
B461B852299599F800E431AA /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = B461B851299599F800E431AA /* AppDelegate.mm */; };
|
||||
B47462D02C1538D800100825 /* CustomSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */; };
|
||||
B47B21EC2B2128B8001F6690 /* BlueWalletUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B47B21EB2B2128B8001F6690 /* BlueWalletUITests.swift */; };
|
||||
B49038D92B8FBAD300A8164A /* BlueWalletUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */; };
|
||||
B4A29A2C2B55C990002A67DF /* EventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D32C5C52596CE3A008C077C /* EventEmitter.m */; };
|
||||
|
@ -444,6 +446,8 @@
|
|||
B44033FF2BCC37F800162242 /* Bundle+decode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+decode.swift"; sourceTree = "<group>"; };
|
||||
B440340E2BCC40A400162242 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../models/fiatUnits.json; sourceTree = "<group>"; };
|
||||
B450109B2C0FCD8A00619044 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
|
||||
B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CustomSegmentedControlManager.m; sourceTree = "<group>"; };
|
||||
B45010A92C15080500619044 /* CustomSegmentedControlManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CustomSegmentedControlManager.h; sourceTree = "<group>"; };
|
||||
B4549F352B82B10D002E3153 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
|
||||
B461B850299599F800E431AA /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = BlueWallet/AppDelegate.h; sourceTree = "<group>"; };
|
||||
B461B851299599F800E431AA /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = BlueWallet/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
|
@ -715,7 +719,7 @@
|
|||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */,
|
||||
B45010A12C1504E900619044 /* Components */,
|
||||
B44033C82BCC34AC00162242 /* Shared */,
|
||||
B41C2E552BB3DCB8000FE097 /* PrivacyInfo.xcprivacy */,
|
||||
B4549F2E2B80FEA1002E3153 /* ci_scripts */,
|
||||
|
@ -862,6 +866,24 @@
|
|||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B45010A12C1504E900619044 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B4B1A4612BFA73110072E3BB /* WidgetHelper.swift */,
|
||||
B45010A82C1507F000619044 /* SegmentedControl */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B45010A82C1507F000619044 /* SegmentedControl */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B45010A52C1507DE00619044 /* CustomSegmentedControlManager.m */,
|
||||
B45010A92C15080500619044 /* CustomSegmentedControlManager.h */,
|
||||
);
|
||||
path = SegmentedControl;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B4549F2E2B80FEA1002E3153 /* ci_scripts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1521,6 +1543,7 @@
|
|||
6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */,
|
||||
B44033FE2BCC37D700162242 /* MarketAPI.swift in Sources */,
|
||||
B450109C2C0FCD8A00619044 /* Utilities.swift in Sources */,
|
||||
B45010A62C1507DE00619044 /* CustomSegmentedControlManager.m in Sources */,
|
||||
B44033CE2BCC352900162242 /* UserDefaultsGroup.swift in Sources */,
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||
B461B852299599F800E431AA /* AppDelegate.mm in Sources */,
|
||||
|
@ -1638,6 +1661,7 @@
|
|||
B44033D72BCC369400162242 /* UserDefaultsExtension.swift in Sources */,
|
||||
B44033F72BCC377F00162242 /* WidgetData.swift in Sources */,
|
||||
B44033E12BCC36CA00162242 /* Placeholders.swift in Sources */,
|
||||
B47462D02C1538D800100825 /* CustomSegmentedControlManager.m in Sources */,
|
||||
B44033E72BCC36FF00162242 /* WalletData.swift in Sources */,
|
||||
B44033E02BCC36C300162242 /* LatestTransaction.swift in Sources */,
|
||||
B44033D92BCC369900162242 /* Colors.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"originHash" : "89509f555bc90a15b96ca0a326a69850770bdaac04a46f9cf482d81533702e3c",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "bugsnag-cocoa",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/bugsnag/bugsnag-cocoa",
|
||||
"state" : {
|
||||
"revision" : "16b9145fc66e5296f16e733f6feb5d0e450574e8",
|
||||
"version" : "6.28.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "efqrcode",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/EFPrefix/EFQRCode.git",
|
||||
"state" : {
|
||||
"revision" : "2991c2f318ad9529d93b2a73a382a3f9c72c64ce",
|
||||
"version" : "6.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift_qrcodejs",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ApolloZhu/swift_qrcodejs.git",
|
||||
"state" : {
|
||||
"revision" : "374dc7f7b9e76c6aeb393f6a84590c6d387e1ecb",
|
||||
"version" : "2.2.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
#import <React/RCTRootView.h>
|
||||
#import <Bugsnag/Bugsnag.h>
|
||||
#import "BlueWallet-Swift.h"
|
||||
#import "CustomSegmentedControlManager.h"
|
||||
|
||||
@interface AppDelegate() <UNUserNotificationCenterDelegate>
|
||||
|
||||
|
@ -21,6 +22,7 @@
|
|||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[CustomSegmentedControlManager registerIfNecessary];
|
||||
[self clearFilesIfNeeded];
|
||||
self.userDefaultsGroup = [[NSUserDefaults alloc] initWithSuiteName:@"group.io.bluewallet.bluewallet"];
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
//
|
||||
// SegmentedControlManager.h
|
||||
// BlueWallet
|
||||
//
|
||||
// Created by Marcos Rodriguez on 6/8/24.
|
||||
// Copyright © 2024 BlueWallet. All rights reserved.
|
||||
//
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface CustomSegmentedControlManager : RCTViewManager
|
||||
|
||||
+ (void)registerIfNecessary;
|
||||
|
||||
@end
|
|
@ -0,0 +1,62 @@
|
|||
#import "CustomSegmentedControlManager.h"
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTEventDispatcher.h>
|
||||
#import <React/UIView+React.h>
|
||||
|
||||
@interface CustomSegmentedControl : UISegmentedControl
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onChangeEvent;
|
||||
- (void)setValues:(NSArray<NSString *> *)values;
|
||||
- (void)setSelectedIndex:(NSNumber *)selectedIndex;
|
||||
@end
|
||||
|
||||
@implementation CustomSegmentedControl
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setValues:(NSArray<NSString *> *)values {
|
||||
[self removeAllSegments];
|
||||
for (NSUInteger i = 0; i < values.count; i++) {
|
||||
[self insertSegmentWithTitle:values[i] atIndex:i animated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSelectedIndex:(NSNumber *)selectedIndex {
|
||||
self.selectedSegmentIndex = selectedIndex.integerValue;
|
||||
}
|
||||
|
||||
- (void)onChange:(UISegmentedControl *)sender {
|
||||
if (self.onChangeEvent) {
|
||||
self.onChangeEvent(@{@"selectedIndex": @(self.selectedSegmentIndex)});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CustomSegmentedControlManager
|
||||
|
||||
static BOOL isRegistered = NO;
|
||||
|
||||
RCT_EXPORT_MODULE(CustomSegmentedControl)
|
||||
|
||||
- (UIView *)view {
|
||||
return [CustomSegmentedControl new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(values, NSArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onChangeEvent, RCTDirectEventBlock)
|
||||
|
||||
+ (void)registerIfNecessary {
|
||||
if (!isRegistered) {
|
||||
isRegistered = YES;
|
||||
// Registration logic if necessary
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -474,7 +474,7 @@ PODS:
|
|||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (10.14.0):
|
||||
- RNDeviceInfo (11.1.0):
|
||||
- React-Core
|
||||
- RNFS (2.20.0):
|
||||
- React-Core
|
||||
|
@ -485,7 +485,7 @@ PODS:
|
|||
- React
|
||||
- RNKeychain (8.2.0):
|
||||
- React-Core
|
||||
- RNLocalize (3.1.0):
|
||||
- RNLocalize (3.2.0):
|
||||
- React-Core
|
||||
- RNPermissions (4.1.5):
|
||||
- React-Core
|
||||
|
@ -830,12 +830,12 @@ SPEC CHECKSUMS:
|
|||
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
|
||||
RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 59344c19152c4b2b32283005f9737c5c64b42fba
|
||||
RNDeviceInfo: b899ce37a403a4dea52b7cb85e16e49c04a5b88e
|
||||
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
|
||||
RNGestureHandler: 982741f345785f2927e7b28f67dc83679cf3bfc8
|
||||
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
|
||||
RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678
|
||||
RNLocalize: e8694475db034bf601e17bd3dfa8986565e769eb
|
||||
RNLocalize: b77875884750cb6a58cd6865863fe2ba2729b72b
|
||||
RNPermissions: 9fa74223844f437bc309e112994859dc47194829
|
||||
RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
|
|
|
@ -43,7 +43,8 @@
|
|||
"entropy": {
|
||||
"save": "Save",
|
||||
"title": "Entropy",
|
||||
"undo": "Undo"
|
||||
"undo": "Undo",
|
||||
"amountOfEntropy": "{bits} of {limit} bits"
|
||||
},
|
||||
"errors": {
|
||||
"broadcast": "Broadcast failed.",
|
||||
|
@ -387,6 +388,8 @@
|
|||
"add_bitcoin": "Bitcoin",
|
||||
"add_bitcoin_explain": "Simple and powerful Bitcoin wallet",
|
||||
"add_create": "Create",
|
||||
"add_entropy": "Entropy",
|
||||
"add_entropy_bytes": "{bytes} bytes of entropy",
|
||||
"add_entropy_generated": "{gen} bytes of generated entropy",
|
||||
"add_entropy_provide": "Provide entropy via dice rolls",
|
||||
"add_entropy_remain": "{gen} bytes of generated entropy. Remaining {rem} bytes will be obtained from the System random number generator.",
|
||||
|
@ -400,6 +403,10 @@
|
|||
"add_title": "Add Wallet",
|
||||
"add_wallet_name": "Name",
|
||||
"add_wallet_type": "Type",
|
||||
"add_wallet_seed_length": "Seed Length",
|
||||
"add_wallet_seed_length_message": "Choose the length of the seed phrase you wish to use for this wallet.",
|
||||
"add_wallet_seed_length_12": "12 words",
|
||||
"add_wallet_seed_length_24": "24 words",
|
||||
"clipboard_bitcoin": "You have a Bitcoin address on your clipboard. Would you like to use it for a transaction?",
|
||||
"clipboard_lightning": "You have a Lightning invoice on your clipboard. Would you like to use it for a transaction?",
|
||||
"details_address": "Address",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as BlueElectrum from '../blue_modules/BlueElectrum';
|
||||
|
||||
export const NetworkTransactionFeeType = Object.freeze({
|
||||
FAST: 'Fast',
|
||||
MEDIUM: 'MEDIUM',
|
||||
SLOW: 'SLOW',
|
||||
CUSTOM: 'CUSTOM',
|
||||
});
|
||||
export enum NetworkTransactionFeeType {
|
||||
FAST = 'Fast',
|
||||
MEDIUM = 'MEDIUM',
|
||||
SLOW = 'SLOW',
|
||||
CUSTOM = 'CUSTOM',
|
||||
}
|
||||
|
||||
export class NetworkTransactionFee {
|
||||
static StorageKey = 'NetworkTransactionFee';
|
||||
|
|
|
@ -28,7 +28,7 @@ import WalletAddresses from '../screen/wallets/WalletAddresses';
|
|||
import WalletDetails from '../screen/wallets/details';
|
||||
import GenerateWord from '../screen/wallets/generateWord';
|
||||
import LdkViewLogs from '../screen/wallets/ldkViewLogs';
|
||||
import SelectWallet from '../screen/wallets/selectWallet';
|
||||
import SelectWallet from '../screen/wallets/SelectWallet';
|
||||
import WalletTransactions from '../screen/wallets/transactions';
|
||||
import WalletsList from '../screen/wallets/WalletsList';
|
||||
import { NavigationDefaultOptions, NavigationFormModalOptions, StatusBarLightOptions, DetailViewStack } from './index'; // Importing the navigator
|
||||
|
|
|
@ -11,7 +11,7 @@ const ImportWallet = lazy(() => import('../screen/wallets/import'));
|
|||
const PleaseBackup = lazy(() => import('../screen/wallets/PleaseBackup'));
|
||||
const PleaseBackupLNDHub = lazy(() => import('../screen/wallets/pleaseBackupLNDHub'));
|
||||
const PleaseBackupLdk = lazy(() => import('../screen/wallets/pleaseBackupLdk'));
|
||||
const ProvideEntropy = lazy(() => import('../screen/wallets/provideEntropy'));
|
||||
const ProvideEntropy = lazy(() => import('../screen/wallets/ProvideEntropy'));
|
||||
const WalletsAddMultisig = lazy(() => import('../screen/wallets/addMultisig'));
|
||||
const WalletsAddMultisigStep2 = lazy(() => import('../screen/wallets/addMultisigStep2'));
|
||||
const WalletsAddMultisigHelp = lazy(() => import('../screen/wallets/addMultisigHelp'));
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { lazy, Suspense } from 'react';
|
|||
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||
|
||||
const AztecoRedeem = lazy(() => import('../screen/receive/aztecoRedeem'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/selectWallet')); // Assuming the path is correct
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
|
||||
|
||||
export const AztecoRedeemComponent = () => (
|
||||
<Suspense fallback={<LazyLoadingIndicator />}>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { lazy, Suspense } from 'react';
|
|||
|
||||
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/selectWallet'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
|
||||
const LdkOpenChannel = lazy(() => import('../screen/lnd/ldkOpenChannel'));
|
||||
const Success = lazy(() => import('../screen/send/success'));
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { lazy, Suspense } from 'react';
|
|||
import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
||||
|
||||
const LNDCreateInvoice = lazy(() => import('../screen/lnd/lndCreateInvoice'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/selectWallet'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
|
||||
const LNDViewInvoice = lazy(() => import('../screen/lnd/lndViewInvoice'));
|
||||
const LNDViewAdditionalInvoiceInformation = lazy(() => import('../screen/lnd/lndViewAdditionalInvoiceInformation'));
|
||||
const LNDViewAdditionalInvoicePreImage = lazy(() => import('../screen/lnd/lndViewAdditionalInvoicePreImage'));
|
||||
|
|
|
@ -4,7 +4,7 @@ import { LazyLoadingIndicator } from './LazyLoadingIndicator';
|
|||
|
||||
// Lazy loading components for the navigation stack
|
||||
const ScanLndInvoice = lazy(() => import('../screen/lnd/scanLndInvoice'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/selectWallet'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
|
||||
const Success = lazy(() => import('../screen/send/success'));
|
||||
const LnurlPay = lazy(() => import('../screen/lnd/lnurlPay'));
|
||||
const LnurlPaySuccess = lazy(() => import('../screen/lnd/lnurlPaySuccess'));
|
||||
|
|
|
@ -9,7 +9,7 @@ const CreateTransaction = lazy(() => import('../screen/send/create'));
|
|||
const PsbtMultisig = lazy(() => import('../screen/send/psbtMultisig'));
|
||||
const PsbtMultisigQRCode = lazy(() => import('../screen/send/psbtMultisigQRCode'));
|
||||
const Success = lazy(() => import('../screen/send/success'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/selectWallet'));
|
||||
const SelectWallet = lazy(() => import('../screen/wallets/SelectWallet'));
|
||||
const CoinControl = lazy(() => import('../screen/send/coinControl'));
|
||||
const PaymentCodesList = lazy(() => import('../screen/wallets/PaymentCodesList'));
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ const Settings = lazy(() => import('../screen/settings/Settings'));
|
|||
const GeneralSettings = lazy(() => import('../screen/settings/GeneralSettings'));
|
||||
const Licensing = lazy(() => import('../screen/settings/Licensing'));
|
||||
const NetworkSettings = lazy(() => import('../screen/settings/NetworkSettings'));
|
||||
const About = lazy(() => import('../screen/settings/about'));
|
||||
const About = lazy(() => import('../screen/settings/About'));
|
||||
const DefaultView = lazy(() => import('../screen/settings/DefaultView'));
|
||||
const ElectrumSettings = lazy(() => import('../screen/settings/electrumSettings'));
|
||||
const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage'));
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||
import navigationStyle from '../components/navigationStyle';
|
||||
import { useTheme } from '../components/themes';
|
||||
import loc from '../loc';
|
||||
import ReorderWallets from '../screen/wallets/reorderWallets';
|
||||
import ReorderWallets from '../screen/wallets/ReorderWallets';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
|
|
|
@ -68,7 +68,6 @@ export type SendDetailsStackParamList = {
|
|||
txid?: string;
|
||||
};
|
||||
SelectWallet: {
|
||||
onWalletSelect: (wallet: TWallet) => void;
|
||||
chainType: Chain;
|
||||
};
|
||||
CoinControl: {
|
||||
|
|
47
package-lock.json
generated
|
@ -34,7 +34,7 @@
|
|||
"bip32": "3.0.1",
|
||||
"bip38": "github:BlueWallet/bip38",
|
||||
"bip39": "3.1.0",
|
||||
"bitcoinjs-lib": "6.1.5",
|
||||
"bitcoinjs-lib": "6.1.6",
|
||||
"bitcoinjs-message": "2.2.0",
|
||||
"bolt11": "1.4.1",
|
||||
"buffer": "6.0.3",
|
||||
|
@ -63,7 +63,7 @@
|
|||
"react-native-camera-kit": "13.0.0",
|
||||
"react-native-crypto": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.14.0",
|
||||
"react-native-device-info": "11.1.0",
|
||||
"react-native-document-picker": "https://github.com/BlueWallet/react-native-document-picker#6033c4e1b0dd0a6760b5f5a5a2c3b2e5d07f2ae4",
|
||||
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#ebfddc4",
|
||||
"react-native-elements": "3.4.3",
|
||||
|
@ -76,7 +76,7 @@
|
|||
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#v1.15.3",
|
||||
"react-native-keychain": "8.2.0",
|
||||
"react-native-linear-gradient": "2.8.3",
|
||||
"react-native-localize": "3.1.0",
|
||||
"react-native-localize": "3.2.0",
|
||||
"react-native-modal": "13.0.1",
|
||||
"react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb",
|
||||
"react-native-permissions": "4.1.5",
|
||||
|
@ -7858,9 +7858,10 @@
|
|||
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
|
||||
},
|
||||
"node_modules/bitcoinjs-lib": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz",
|
||||
"integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==",
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz",
|
||||
"integrity": "sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bech32": "^2.0.0",
|
||||
|
@ -7877,6 +7878,7 @@
|
|||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base-x": "^4.0.0"
|
||||
}
|
||||
|
@ -7885,6 +7887,7 @@
|
|||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz",
|
||||
"integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bs58": "^5.0.0"
|
||||
|
@ -19574,10 +19577,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-device-info": {
|
||||
"version": "10.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.14.0.tgz",
|
||||
"integrity": "sha512-9NnTGfhEU4UgQtz4p6COk2Gbqly0dpSWrJtp+dw5rNAi96KtYbaNnO5yoOHDlJ1SVIzh8+hFu3WxVbnWkFU9gA==",
|
||||
"license": "MIT",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-11.1.0.tgz",
|
||||
"integrity": "sha512-hzXJSObJdezEz0hF7MAJ3tGeoesuQWenXXt9mrQR9Mjb8kXpZ09rqSsZ/quNpJdZpQ3rYiFa3/0GFG5KNn9PBg==",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
|
@ -19737,9 +19739,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-localize": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.1.0.tgz",
|
||||
"integrity": "sha512-A7Rrxl8vuAr5FAqtMFrM5ELLdmszohK6FoHL6qlgxx4HScyOnadoZksbvKHbn+zV5nE8kud2Z4kJM10cN120Zg==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.2.0.tgz",
|
||||
"integrity": "sha512-Wi486WdDoBcDKUNxzr2/8f64ZwkWExlIvn0lcfFhsRNP2CVXGkjLyXhr3h6jf3Utkv5EmoFKiNqcmZn9kjYDxQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=18.1.0",
|
||||
"react-native": ">=0.70.0",
|
||||
|
@ -28563,9 +28566,9 @@
|
|||
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
|
||||
},
|
||||
"bitcoinjs-lib": {
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz",
|
||||
"integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==",
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.6.tgz",
|
||||
"integrity": "sha512-Fk8+Vc+e2rMoDU5gXkW9tD+313rhkm5h6N9HfZxXvYU9LedttVvmXKTgd9k5rsQJjkSfsv6XRM8uhJv94SrvcA==",
|
||||
"requires": {
|
||||
"@noble/hashes": "^1.2.0",
|
||||
"bech32": "^2.0.0",
|
||||
|
@ -37399,9 +37402,9 @@
|
|||
"integrity": "sha512-h0vtgiSKws3UmMRJykXAVM4ne1SgfoocUcoBD19ewRpQd6wqurE0HJRQGrSxcHK5LdKE7QPSIB1VX3YGIVS8Jg=="
|
||||
},
|
||||
"react-native-device-info": {
|
||||
"version": "10.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-10.14.0.tgz",
|
||||
"integrity": "sha512-9NnTGfhEU4UgQtz4p6COk2Gbqly0dpSWrJtp+dw5rNAi96KtYbaNnO5yoOHDlJ1SVIzh8+hFu3WxVbnWkFU9gA=="
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-11.1.0.tgz",
|
||||
"integrity": "sha512-hzXJSObJdezEz0hF7MAJ3tGeoesuQWenXXt9mrQR9Mjb8kXpZ09rqSsZ/quNpJdZpQ3rYiFa3/0GFG5KNn9PBg=="
|
||||
},
|
||||
"react-native-document-picker": {
|
||||
"version": "git+ssh://git@github.com/BlueWallet/react-native-document-picker.git#6033c4e1b0dd0a6760b5f5a5a2c3b2e5d07f2ae4",
|
||||
|
@ -37503,9 +37506,9 @@
|
|||
"integrity": "sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA=="
|
||||
},
|
||||
"react-native-localize": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.1.0.tgz",
|
||||
"integrity": "sha512-A7Rrxl8vuAr5FAqtMFrM5ELLdmszohK6FoHL6qlgxx4HScyOnadoZksbvKHbn+zV5nE8kud2Z4kJM10cN120Zg=="
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-3.2.0.tgz",
|
||||
"integrity": "sha512-Wi486WdDoBcDKUNxzr2/8f64ZwkWExlIvn0lcfFhsRNP2CVXGkjLyXhr3h6jf3Utkv5EmoFKiNqcmZn9kjYDxQ=="
|
||||
},
|
||||
"react-native-modal": {
|
||||
"version": "13.0.1",
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
"bip32": "3.0.1",
|
||||
"bip38": "github:BlueWallet/bip38",
|
||||
"bip39": "3.1.0",
|
||||
"bitcoinjs-lib": "6.1.5",
|
||||
"bitcoinjs-lib": "6.1.6",
|
||||
"bitcoinjs-message": "2.2.0",
|
||||
"bolt11": "1.4.1",
|
||||
"buffer": "6.0.3",
|
||||
|
@ -148,7 +148,7 @@
|
|||
"react-native-camera-kit": "13.0.0",
|
||||
"react-native-crypto": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.14.0",
|
||||
"react-native-device-info": "11.1.0",
|
||||
"react-native-document-picker": "https://github.com/BlueWallet/react-native-document-picker#6033c4e1b0dd0a6760b5f5a5a2c3b2e5d07f2ae4",
|
||||
"react-native-draggable-flatlist": "github:BlueWallet/react-native-draggable-flatlist#ebfddc4",
|
||||
"react-native-elements": "3.4.3",
|
||||
|
@ -161,7 +161,7 @@
|
|||
"react-native-ios-context-menu": "github:BlueWallet/react-native-ios-context-menu#v1.15.3",
|
||||
"react-native-keychain": "8.2.0",
|
||||
"react-native-linear-gradient": "2.8.3",
|
||||
"react-native-localize": "3.1.0",
|
||||
"react-native-localize": "3.2.0",
|
||||
"react-native-modal": "13.0.1",
|
||||
"react-native-obscure": "https://github.com/BlueWallet/react-native-obscure.git#f4b83b4a261e39b1f5ed4a45ac5bcabc8a59eadb",
|
||||
"react-native-permissions": "4.1.5",
|
||||
|
|
|
@ -1,343 +0,0 @@
|
|||
import BIP32Factory from 'bip32';
|
||||
import bip38 from 'bip38';
|
||||
import * as bip39 from 'bip39';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Linking, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import BlueCrypto from 'react-native-blue-crypto';
|
||||
import wif from 'wif';
|
||||
|
||||
import * as BlueElectrum from '../blue_modules/BlueElectrum';
|
||||
import * as encryption from '../blue_modules/encryption';
|
||||
import * as fs from '../blue_modules/fs';
|
||||
import ecc from '../blue_modules/noble_ecc';
|
||||
import { BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../BlueComponents';
|
||||
import {
|
||||
HDAezeedWallet,
|
||||
HDSegwitBech32Wallet,
|
||||
HDSegwitP2SHWallet,
|
||||
LegacyWallet,
|
||||
SegwitP2SHWallet,
|
||||
SLIP39LegacyP2PKHWallet,
|
||||
} from '../class';
|
||||
import presentAlert from '../components/Alert';
|
||||
import Button from '../components/Button';
|
||||
import SafeArea from '../components/SafeArea';
|
||||
import SaveFileButton from '../components/SaveFileButton';
|
||||
import loc from '../loc';
|
||||
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
center: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default class SelfTest extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
onPressImportDocument = async () => {
|
||||
try {
|
||||
fs.showFilePickerAndReadFile().then(file => {
|
||||
if (file && file.data && file.data.length > 0) {
|
||||
presentAlert({ message: file.data });
|
||||
} else {
|
||||
presentAlert({ message: 'Error reading file' });
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
let errorMessage = '';
|
||||
let isOk = true;
|
||||
|
||||
try {
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const uniqs = {};
|
||||
const w = new SegwitP2SHWallet();
|
||||
for (let c = 0; c < 1000; c++) {
|
||||
await w.generate();
|
||||
if (uniqs[w.getSecret()]) {
|
||||
throw new Error('failed to generate unique private key');
|
||||
}
|
||||
uniqs[w.getSecret()] = 1;
|
||||
}
|
||||
} else {
|
||||
// skipping RN-specific test
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
await BlueElectrum.ping();
|
||||
await BlueElectrum.waitTillConnected();
|
||||
const addr4elect = '3GCvDBAktgQQtsbN6x5DYiQCMmgZ9Yk8BK';
|
||||
const electrumBalance = await BlueElectrum.getBalanceByAddress(addr4elect);
|
||||
if (electrumBalance.confirmed !== 51432)
|
||||
throw new Error('BlueElectrum getBalanceByAddress failure, got ' + JSON.stringify(electrumBalance));
|
||||
|
||||
const electrumTxs = await BlueElectrum.getTransactionsByAddress(addr4elect);
|
||||
if (electrumTxs.length !== 1) throw new Error('BlueElectrum getTransactionsByAddress failure, got ' + JSON.stringify(electrumTxs));
|
||||
} else {
|
||||
// skipping RN-specific test'
|
||||
}
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const aezeed = new HDAezeedWallet();
|
||||
aezeed.setSecret(
|
||||
'abstract rhythm weird food attract treat mosquito sight royal actor surround ride strike remove guilt catch filter summer mushroom protect poverty cruel chaos pattern',
|
||||
);
|
||||
assertStrictEqual(await aezeed.validateMnemonicAsync(), true, 'Aezeed failed');
|
||||
assertStrictEqual(aezeed._getExternalAddressByIndex(0), 'bc1qdjj7lhj9lnjye7xq3dzv3r4z0cta294xy78txn', 'Aezeed failed');
|
||||
} else {
|
||||
// skipping RN-specific test
|
||||
}
|
||||
|
||||
let l = new LegacyWallet();
|
||||
l.setSecret('L4ccWrPMmFDZw4kzAKFqJNxgHANjdy6b7YKNXMwB4xac4FLF3Tov');
|
||||
assertStrictEqual(l.getAddress(), '14YZ6iymQtBVQJk6gKnLCk49UScJK7SH4M');
|
||||
let utxos = [
|
||||
{
|
||||
txid: 'cc44e933a094296d9fe424ad7306f16916253a3d154d52e4f1a757c18242cec4',
|
||||
vout: 0,
|
||||
value: 100000,
|
||||
txhex:
|
||||
'0200000000010161890cd52770c150da4d7d190920f43b9f88e7660c565a5a5ad141abb6de09de00000000000000008002a0860100000000001976a91426e01119d265aa980390c49eece923976c218f1588ac3e17000000000000160014c1af8c9dd85e0e55a532a952282604f820746fcd02473044022072b3f28808943c6aa588dd7a4e8f29fad7357a2814e05d6c5d767eb6b307b4e6022067bc6a8df2dbee43c87b8ce9ddd9fe678e00e0f7ae6690d5cb81eca6170c47e8012102e8fba5643e15ab70ec79528833a2c51338c1114c4eebc348a235b1a3e13ab07100000000',
|
||||
},
|
||||
];
|
||||
|
||||
let txNew = l.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, l.getAddress());
|
||||
const txBitcoin = bitcoin.Transaction.fromHex(txNew.tx.toHex());
|
||||
assertStrictEqual(
|
||||
txNew.tx.toHex(),
|
||||
'0200000001c4ce4282c157a7f1e4524d153d3a251669f10673ad24e49f6d2994a033e944cc000000006b48304502210091e58bd2021f2eeea8d39d7f7b053c9ccc52a747b60f1c3584ba33285e2d150602205b2d35a2536cbe157015e8c54a26f5fc350cc7c72b5ca80b9e548917993f652201210337c09b3cb889801638078fd4e6998218b28c92d338ea2602720a88847aedceb3ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88ac2e260000000000001976a91426e01119d265aa980390c49eece923976c218f1588ac00000000',
|
||||
);
|
||||
assertStrictEqual(txBitcoin.ins.length, 1);
|
||||
assertStrictEqual(txBitcoin.outs.length, 2);
|
||||
assertStrictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(txBitcoin.outs[0].script)); // to address
|
||||
assertStrictEqual(l.getAddress(), bitcoin.address.fromOutputScript(txBitcoin.outs[1].script)); // change address
|
||||
|
||||
//
|
||||
|
||||
l = new SegwitP2SHWallet();
|
||||
l.setSecret('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct');
|
||||
if (l.getAddress() !== '34AgLJhwXrvmkZS1o5TrcdeevMt22Nar53') {
|
||||
throw new Error('failed to generate segwit P2SH address from WIF');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const wallet = new SegwitP2SHWallet();
|
||||
wallet.setSecret('Ky1vhqYGCiCbPd8nmbUeGfwLdXB1h5aGwxHwpXrzYRfY5cTZPDo4');
|
||||
assertStrictEqual(wallet.getAddress(), '3CKN8HTCews4rYJYsyub5hjAVm5g5VFdQJ');
|
||||
|
||||
utxos = [
|
||||
{
|
||||
txid: 'a56b44080cb606c0bd90e77fcd4fb34c863e68e5562e75b4386e611390eb860c',
|
||||
vout: 0,
|
||||
value: 300000,
|
||||
},
|
||||
];
|
||||
|
||||
txNew = wallet.createTransaction(utxos, [{ value: 90000, address: '1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB' }], 1, wallet.getAddress());
|
||||
const tx = bitcoin.Transaction.fromHex(txNew.tx.toHex());
|
||||
assertStrictEqual(
|
||||
txNew.tx.toHex(),
|
||||
'020000000001010c86eb9013616e38b4752e56e5683e864cb34fcd7fe790bdc006b60c08446ba50000000017160014139dc70d73097f9d775f8a3280ba3e3435515641ffffffff02905f0100000000001976a914aa381cd428a4e91327fd4434aa0a08ff131f1a5a88aca73303000000000017a914749118baa93fb4b88c28909c8bf0a8202a0484f4870248304502210080545d30e3d30dff272ab11c91fd6150170b603239b48c3d56a3fa66bf240085022003762404e1b45975adc89f61ec1569fa19d6d4a8d405e060897754c489ebeade012103a5de146762f84055db3202c1316cd9008f16047f4f408c1482fdb108217eda0800000000',
|
||||
);
|
||||
assertStrictEqual(tx.ins.length, 1);
|
||||
assertStrictEqual(tx.outs.length, 2);
|
||||
assertStrictEqual('1GX36PGBUrF8XahZEGQqHqnJGW2vCZteoB', bitcoin.address.fromOutputScript(tx.outs[0].script)); // to address
|
||||
assertStrictEqual(bitcoin.address.fromOutputScript(tx.outs[1].script), wallet.getAddress()); // change address
|
||||
|
||||
//
|
||||
|
||||
const data2encrypt = 'really long data string';
|
||||
const crypted = encryption.encrypt(data2encrypt, 'password');
|
||||
const decrypted = encryption.decrypt(crypted, 'password');
|
||||
|
||||
if (decrypted !== data2encrypt) {
|
||||
throw new Error('encryption lib is not ok');
|
||||
}
|
||||
|
||||
//
|
||||
const mnemonic =
|
||||
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode';
|
||||
const seed = bip39.mnemonicToSeedSync(mnemonic);
|
||||
const root = bip32.fromSeed(seed);
|
||||
|
||||
const path = "m/49'/0'/0'/0/0";
|
||||
const child = root.derivePath(path);
|
||||
const address = bitcoin.payments.p2sh({
|
||||
redeem: bitcoin.payments.p2wpkh({
|
||||
pubkey: child.publicKey,
|
||||
network: bitcoin.networks.bitcoin,
|
||||
}),
|
||||
network: bitcoin.networks.bitcoin,
|
||||
}).address;
|
||||
|
||||
if (address !== '3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK') {
|
||||
throw new Error('bip49 is not ok');
|
||||
}
|
||||
|
||||
//
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const hd = new HDSegwitP2SHWallet();
|
||||
const hashmap = {};
|
||||
for (let c = 0; c < 1000; c++) {
|
||||
await hd.generate();
|
||||
const secret = hd.getSecret();
|
||||
if (hashmap[secret]) {
|
||||
throw new Error('Duplicate secret generated!');
|
||||
}
|
||||
hashmap[secret] = 1;
|
||||
if (secret.split(' ').length !== 12 && secret.split(' ').length !== 24) {
|
||||
throw new Error('mnemonic phrase not ok');
|
||||
}
|
||||
}
|
||||
|
||||
const hd2 = new HDSegwitP2SHWallet();
|
||||
hd2.setSecret(hd.getSecret());
|
||||
if (!hd2.validateMnemonic()) {
|
||||
throw new Error('mnemonic phrase validation not ok');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const hd4 = new HDSegwitBech32Wallet();
|
||||
hd4._xpub = 'zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP';
|
||||
await hd4.fetchBalance();
|
||||
if (hd4.getBalance() !== 200000) throw new Error('Could not fetch HD Bech32 balance');
|
||||
await hd4.fetchTransactions();
|
||||
if (hd4.getTransactions().length !== 4) throw new Error('Could not fetch HD Bech32 transactions');
|
||||
} else {
|
||||
// skipping RN-specific test
|
||||
}
|
||||
|
||||
// BlueCrypto test
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const hex = await BlueCrypto.scrypt('717765727479', '4749345a22b23cf3', 64, 8, 8, 32); // using non-default parameters to speed it up (not-bip38 compliant)
|
||||
if (hex.toUpperCase() !== 'F36AB2DC12377C788D61E6770126D8A01028C8F6D8FE01871CE0489A1F696A90')
|
||||
throw new Error('react-native-blue-crypto is not ok');
|
||||
}
|
||||
|
||||
// bip38 test
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
let callbackWasCalled = false;
|
||||
const decryptedKey = await bip38.decryptAsync(
|
||||
'6PnU5voARjBBykwSddwCdcn6Eu9EcsK24Gs5zWxbJbPZYW7eiYQP8XgKbN',
|
||||
'qwerty',
|
||||
() => (callbackWasCalled = true),
|
||||
);
|
||||
assertStrictEqual(
|
||||
wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed),
|
||||
'KxqRtpd9vFju297ACPKHrGkgXuberTveZPXbRDiQ3MXZycSQYtjc',
|
||||
'bip38 failed',
|
||||
);
|
||||
// bip38 with BlueCrypto doesn't support progress callback
|
||||
assertStrictEqual(callbackWasCalled, false, "bip38 doesn't use BlueCrypto");
|
||||
}
|
||||
|
||||
// slip39 test
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
const w = new SLIP39LegacyP2PKHWallet();
|
||||
w.setSecret(
|
||||
'shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed\n' +
|
||||
'shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking',
|
||||
);
|
||||
assertStrictEqual(w._getExternalAddressByIndex(0), '18pvMjy7AJbCDtv4TLYbGPbR7SzGzjqUpj', 'SLIP39 failed');
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') {
|
||||
assertStrictEqual(await Linking.canOpenURL('https://github.com/BlueWallet/BlueWallet/'), true, 'Linking can not open https url');
|
||||
} else {
|
||||
// skipping RN-specific test'
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
assertStrictEqual(Buffer.from('00ff0f', 'hex').reverse().toString('hex'), '0fff00');
|
||||
|
||||
//
|
||||
} catch (Err) {
|
||||
errorMessage += Err;
|
||||
isOk = false;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
isOk,
|
||||
errorMessage,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.isLoading) {
|
||||
return <BlueLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeArea>
|
||||
<BlueCard>
|
||||
<ScrollView>
|
||||
<BlueSpacing20 />
|
||||
|
||||
{(() => {
|
||||
if (this.state.isOk) {
|
||||
return (
|
||||
<View style={styles.center}>
|
||||
<BlueText testID="SelfTestOk" h4>
|
||||
OK
|
||||
</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueText>{loc.settings.about_selftest_ok}</BlueText>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.center}>
|
||||
<BlueText h4 numberOfLines={0}>
|
||||
{this.state.errorMessage}
|
||||
</BlueText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
<BlueSpacing20 />
|
||||
<SaveFileButton fileName="bluewallet-selftest.txt" fileContent={'Success on ' + new Date().toUTCString()}>
|
||||
<Button title="Test Save to Storage" />
|
||||
</SaveFileButton>
|
||||
<BlueSpacing20 />
|
||||
<Button title="Test File Import" onPress={this.onPressImportDocument} />
|
||||
</ScrollView>
|
||||
</BlueCard>
|
||||
</SafeArea>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertStrictEqual(actual, expected, message) {
|
||||
if (expected !== actual) {
|
||||
if (message) throw new Error(message);
|
||||
throw new Error('Assertion failed that ' + JSON.stringify(expected) + ' equals ' + JSON.stringify(actual));
|
||||
}
|
||||
}
|
||||
|
||||
SelfTest.propTypes = {
|
||||
navigation: PropTypes.shape({
|
||||
navigate: PropTypes.func,
|
||||
goBack: PropTypes.func,
|
||||
}),
|
||||
};
|
|
@ -664,10 +664,13 @@ const SendDetails = () => {
|
|||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const onWalletSelect = (w: TWallet) => {
|
||||
setWallet(w);
|
||||
navigation.dispatch(popAction);
|
||||
};
|
||||
useEffect(() => {
|
||||
const newWallet = wallets.find(w => w.getID() === routeParams.walletID);
|
||||
if (newWallet) {
|
||||
setWallet(newWallet);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [routeParams.walletID]);
|
||||
|
||||
/**
|
||||
* same as `importTransaction`, but opens camera instead.
|
||||
|
@ -1196,6 +1199,12 @@ const SendDetails = () => {
|
|||
},
|
||||
});
|
||||
|
||||
const calculateTotalAmount = () => {
|
||||
const totalAmount = addresses.reduce((total, item) => total + Number(item.amountSats || 0), 0);
|
||||
const totalWithFee = totalAmount + (feePrecalc.current || 0);
|
||||
return totalWithFee;
|
||||
};
|
||||
|
||||
const renderFeeSelectionModal = () => {
|
||||
const nf = networkTransactionFees;
|
||||
const options = [
|
||||
|
@ -1355,12 +1364,15 @@ const SendDetails = () => {
|
|||
};
|
||||
|
||||
const renderCreateButton = () => {
|
||||
const totalWithFee = calculateTotalAmount();
|
||||
const isDisabled = totalWithFee === 0 || totalWithFee > balance || balance === 0 || isLoading || addresses.length === 0;
|
||||
|
||||
return (
|
||||
<View style={styles.createButton}>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<Button onPress={createTransaction} title={loc.send.details_next} testID="CreateTransactionButton" />
|
||||
<Button onPress={createTransaction} disabled={isDisabled} title={loc.send.details_next} testID="CreateTransactionButton" />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
|
@ -1389,7 +1401,7 @@ const SendDetails = () => {
|
|||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
style={styles.selectTouch}
|
||||
onPress={() => navigation.navigate('SelectWallet', { onWalletSelect, chainType: Chain.ONCHAIN })}
|
||||
onPress={() => navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN })}
|
||||
>
|
||||
<Text style={styles.selectText}>{loc.wallets.select_wallet.toLowerCase()}</Text>
|
||||
<Icon name={I18nManager.isRTL ? 'angle-left' : 'angle-right'} size={18} type="font-awesome" color="#9aa0aa" />
|
||||
|
@ -1399,7 +1411,7 @@ const SendDetails = () => {
|
|||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
style={styles.selectTouch}
|
||||
onPress={() => navigation.navigate('SelectWallet', { onWalletSelect, chainType: Chain.ONCHAIN })}
|
||||
onPress={() => navigation.navigate('SelectWallet', { chainType: Chain.ONCHAIN })}
|
||||
disabled={!isEditable || isLoading}
|
||||
>
|
||||
<Text style={[styles.selectLabel, stylesHook.selectLabel]}>{wallet?.getLabel()}</Text>
|
||||
|
|
|
@ -17,63 +17,24 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
|
|||
|
||||
const branch = require('../../current-branch.json');
|
||||
|
||||
const About = () => {
|
||||
const About: React.FC = () => {
|
||||
const { navigate } = useExtendedNavigation();
|
||||
const { colors } = useTheme();
|
||||
const { width, height } = useWindowDimensions();
|
||||
const { isElectrumDisabled } = useStorage();
|
||||
const styles = StyleSheet.create({
|
||||
copyToClipboard: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
copyToClipboardText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '400',
|
||||
color: '#68bbe1',
|
||||
},
|
||||
center: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 54,
|
||||
},
|
||||
logo: {
|
||||
width: 102,
|
||||
height: 124,
|
||||
},
|
||||
textFree: {
|
||||
maxWidth: 260,
|
||||
marginVertical: 24,
|
||||
color: '#9AA0AA',
|
||||
fontSize: 15,
|
||||
textAlign: 'center',
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
||||
const stylesHook = StyleSheet.create({
|
||||
textBackup: {
|
||||
maxWidth: 260,
|
||||
marginBottom: 40,
|
||||
color: colors.foregroundColor,
|
||||
fontSize: 15,
|
||||
textAlign: 'center',
|
||||
fontWeight: '500',
|
||||
},
|
||||
buildWith: {
|
||||
backgroundColor: colors.inputBackgroundColor,
|
||||
padding: 16,
|
||||
paddingTop: 0,
|
||||
borderRadius: 8,
|
||||
},
|
||||
buttonLink: {
|
||||
backgroundColor: colors.lightButton,
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
padding: 8,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
textLink: {
|
||||
color: colors.foregroundColor,
|
||||
marginLeft: 8,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -104,9 +65,11 @@ const About = () => {
|
|||
const handleOnTelegramPress = () => {
|
||||
Linking.openURL('https://t.me/bluewallethat');
|
||||
};
|
||||
|
||||
const handleOnGithubPress = () => {
|
||||
Linking.openURL('https://github.com/BlueWallet/BlueWallet');
|
||||
};
|
||||
|
||||
const handleOnRatePress = () => {
|
||||
const options = {
|
||||
AppleAppID: '1376878040',
|
||||
|
@ -129,7 +92,7 @@ const About = () => {
|
|||
<View style={styles.center}>
|
||||
<Image style={styles.logo} source={require('../../img/bluebeast.png')} />
|
||||
<Text style={styles.textFree}>{loc.settings.about_free}</Text>
|
||||
<Text style={styles.textBackup}>{formatStringAddTwoWhiteSpaces(loc.settings.about_backup)}</Text>
|
||||
<Text style={[styles.textBackup, stylesHook.textBackup]}>{formatStringAddTwoWhiteSpaces(loc.settings.about_backup)}</Text>
|
||||
{((Platform.OS === 'android' && hasGmsSync()) || Platform.OS !== 'android') && (
|
||||
<Button onPress={handleOnRatePress} title={loc.settings.about_review + ' ⭐🙏'} />
|
||||
)}
|
||||
|
@ -163,9 +126,8 @@ const About = () => {
|
|||
title={loc.settings.about_sm_discord}
|
||||
/>
|
||||
<BlueCard>
|
||||
<View style={styles.buildWith}>
|
||||
<View style={[styles.buildWith, stylesHook.buildWith]}>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<BlueTextCentered>{loc.settings.about_awesome} 👍</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
<BlueTextCentered>React Native</BlueTextCentered>
|
||||
|
@ -173,10 +135,9 @@ const About = () => {
|
|||
<BlueTextCentered>Nodejs</BlueTextCentered>
|
||||
<BlueTextCentered>Electrum server</BlueTextCentered>
|
||||
<BlueSpacing20 />
|
||||
|
||||
<TouchableOpacity accessibilityRole="button" onPress={handleOnGithubPress} style={styles.buttonLink}>
|
||||
<TouchableOpacity accessibilityRole="button" onPress={handleOnGithubPress} style={[styles.buttonLink, stylesHook.buttonLink]}>
|
||||
<Icon size={22} name="github" type="font-awesome-5" color={colors.foregroundColor} />
|
||||
<Text style={styles.textLink}>{formatStringAddTwoWhiteSpaces(loc.settings.about_sm_github)}</Text>
|
||||
<Text style={[styles.textLink, stylesHook.textLink]}>{formatStringAddTwoWhiteSpaces(loc.settings.about_sm_github)}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</BlueCard>
|
||||
|
@ -241,24 +202,28 @@ const About = () => {
|
|||
<BlueTextCentered>
|
||||
{getApplicationName()} ver {getVersion()} (build {getBuildNumber() + ' ' + branch})
|
||||
</BlueTextCentered>
|
||||
<BlueTextCentered>{new Date(getBuildNumber() * 1000).toGMTString()}</BlueTextCentered>
|
||||
<BlueTextCentered>{new Date(Number(getBuildNumber()) * 1000).toUTCString()}</BlueTextCentered>
|
||||
<BlueTextCentered>{getBundleId()}</BlueTextCentered>
|
||||
<BlueTextCentered>
|
||||
w, h = {width}, {height}
|
||||
</BlueTextCentered>
|
||||
<BlueTextCentered>Unique ID: {getUniqueIdSync()}</BlueTextCentered>
|
||||
<View style={styles.copyToClipboard}>
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => {
|
||||
const stringToCopy = 'userId:' + getUniqueIdSync();
|
||||
A.logError('copied unique id');
|
||||
Clipboard.setString(stringToCopy);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.copyToClipboardText}>{loc.transactions.details_copy}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{process.env.NODE_ENV !== 'development' && (
|
||||
<>
|
||||
<BlueTextCentered>Unique ID: {getUniqueIdSync()}</BlueTextCentered>
|
||||
<View style={styles.copyToClipboard}>
|
||||
<TouchableOpacity
|
||||
accessibilityRole="button"
|
||||
onPress={() => {
|
||||
const stringToCopy = 'userId:' + getUniqueIdSync();
|
||||
A.logError('copied unique id');
|
||||
Clipboard.setString(stringToCopy);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.copyToClipboardText}>{loc.transactions.details_copy}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
<BlueSpacing20 />
|
||||
<BlueSpacing20 />
|
||||
</ScrollView>
|
||||
|
@ -266,3 +231,54 @@ const About = () => {
|
|||
};
|
||||
|
||||
export default About;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
copyToClipboard: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
copyToClipboardText: {
|
||||
fontSize: 13,
|
||||
fontWeight: '400',
|
||||
color: '#68bbe1',
|
||||
},
|
||||
center: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 54,
|
||||
},
|
||||
logo: {
|
||||
width: 102,
|
||||
height: 124,
|
||||
},
|
||||
textFree: {
|
||||
maxWidth: 260,
|
||||
marginVertical: 24,
|
||||
color: '#9AA0AA',
|
||||
fontSize: 15,
|
||||
textAlign: 'center',
|
||||
fontWeight: '500',
|
||||
},
|
||||
textBackup: {
|
||||
maxWidth: 260,
|
||||
marginBottom: 40,
|
||||
fontSize: 15,
|
||||
textAlign: 'center',
|
||||
fontWeight: '500',
|
||||
},
|
||||
buildWith: {
|
||||
padding: 16,
|
||||
paddingTop: 0,
|
||||
borderRadius: 8,
|
||||
},
|
||||
buttonLink: {
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
padding: 8,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
textLink: {
|
||||
marginLeft: 8,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
|
@ -16,7 +16,7 @@ import { Text } from 'react-native-elements';
|
|||
import * as BlueElectrum from '../../blue_modules/BlueElectrum';
|
||||
import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback';
|
||||
import Notifications from '../../blue_modules/notifications';
|
||||
import { BlueCard, BlueReplaceFeeSuggestions, BlueSpacing, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { BlueCard, BlueSpacing, BlueSpacing20, BlueText } from '../../BlueComponents';
|
||||
import { HDSegwitBech32Transaction, HDSegwitBech32Wallet } from '../../class';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import Button from '../../components/Button';
|
||||
|
@ -26,6 +26,7 @@ import { BlueCurrentTheme } from '../../components/themes';
|
|||
import loc from '../../loc';
|
||||
import { StorageContext } from '../../components/Context/StorageProvider';
|
||||
import { popToTop } from '../../NavigationService';
|
||||
import ReplaceFeeSuggestions from '../../components/ReplaceFeeSuggestions';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
|
@ -167,7 +168,7 @@ export default class CPFP extends Component {
|
|||
<BlueCard style={styles.center}>
|
||||
<BlueText>{text}</BlueText>
|
||||
<BlueSpacing20 />
|
||||
<BlueReplaceFeeSuggestions onFeeSelected={fee => this.setState({ newFeeRate: fee })} transactionMinimum={this.state.feeRate} />
|
||||
<ReplaceFeeSuggestions onFeeSelected={fee => this.setState({ newFeeRate: fee })} transactionMinimum={this.state.feeRate} />
|
||||
<BlueSpacing />
|
||||
<Button
|
||||
disabled={this.state.newFeeRate <= this.state.feeRate}
|
||||
|
|