ci(release): publish latest release

parent c55953b6
diff --git a/dist/cjs/createAnimations.native.js b/dist/cjs/createAnimations.native.js
index 8ef9a4b03e660025bd16f4d15ec9e82ead963be0..023de8bf1955d83f0483917f4d6e0a5205d6fb17 100644
--- a/dist/cjs/createAnimations.native.js
+++ b/dist/cjs/createAnimations.native.js
@@ -163,7 +163,7 @@ function createAnimations(animations) {
else {
var animateOnly = props.animateOnly;
for (var key in style)
- !import_web.stylePropsAll[key] || neverAnimate[key] || animateOnly && !animateOnly.includes(key) ? dontAnimate[key] = style[key] : animate[key] = style[key];
+ !import_web.stylePropsAll[key] || neverAnimate[key] || style[key] === 'auto' || animateOnly && !animateOnly.includes(key) ? dontAnimate[key] = style[key] : animate[key] = style[key];
}
var animateStr = JSON.stringify(animate), styles = (0, import_react.useMemo)(function() {
return JSON.parse(animateStr);
diff --git a/lib/browser/eip1193.js b/lib/browser/eip1193.js
index ce028c25a164d8af8d513bc0eae4cf104234f6e8..81ba2f29d379f18c04c57b5a560cc6c4f4b57e0d 100644
--- a/lib/browser/eip1193.js
+++ b/lib/browser/eip1193.js
@@ -70,6 +70,7 @@ class Eip1193 extends eip1193_bridge_1.Eip1193Bridge {
yield _super.send.call(this, method, params);
// Providers will not "rewind" to an older block number nor notice chain changes, so they must be reset.
this.utils.providers.forEach((provider) => provider.reset());
+ this.emit('chainChanged', params[0].chainId);
break;
default:
result = yield _super.send.call(this, method, params);
diff --git a/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt b/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt
index 9980dfabbd5179753ba99f50c6ecfadd732cfdb9..589c58e427989181dddaa5ccd4ccf31fcc0e53c7 100644
index 996ee2ec5b77386b360fed3118fd6d6d4244d3c4..4aa70a89a7fb5d01b5b700d55c9817c213322e4d 100644
--- a/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt
+++ b/android/src/main/java/expo/modules/localauthentication/LocalAuthenticationModule.kt
@@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent
@@ -4,6 +4,8 @@ package expo.modules.localauthentication
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
+import android.content.Intent
+import android.provider.Settings
import android.os.Build
import android.os.Bundle
+import android.provider.Settings
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
@@ -89,7 +90,7 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
import androidx.annotation.UiThread
@@ -51,7 +53,7 @@ class LocalAuthenticationModule : Module() {
@ExpoMethod
fun supportedAuthenticationTypesAsync(promise: Promise) {
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
val results: MutableList<Int> = ArrayList()
if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
promise.resolve(results)
@@ -122,13 +123,13 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
AsyncFunction("supportedAuthenticationTypesAsync") {
val results = mutableSetOf<Int>()
- if (canAuthenticateUsingWeakBiometrics() == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
+ if (canAuthenticateUsingStrongBiometrics() == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
return@AsyncFunction results
}
@ExpoMethod
fun hasHardwareAsync(promise: Promise) {
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
promise.resolve(result != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE)
}
@@ -67,12 +69,29 @@ class LocalAuthenticationModule : Module() {
return@AsyncFunction results
}
@ExpoMethod
fun isEnrolledAsync(promise: Promise) {
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
promise.resolve(result == BiometricManager.BIOMETRIC_SUCCESS)
}
+ AsyncFunction("enrollForAuthentication") {
+ if (Build.VERSION.SDK_INT >= 30) {
+ val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL)
+ intent.putExtra(
+ Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ )
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ currentActivity?.startActivity(intent)
+ return@AsyncFunction true
+ } else {
+ val intent = Intent(Settings.ACTION_FINGERPRINT_ENROLL)
+ currentActivity?.startActivity(intent)
+ return@AsyncFunction true
+ }
+ }
+
AsyncFunction("hasHardwareAsync") {
- canAuthenticateUsingWeakBiometrics() != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
+ canAuthenticateUsingStrongBiometrics() != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
}
@@ -138,13 +139,31 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
if (isDeviceSecure) {
level = SECURITY_LEVEL_SECRET
AsyncFunction("isEnrolledAsync") {
- canAuthenticateUsingWeakBiometrics() == BiometricManager.BIOMETRIC_SUCCESS
+ canAuthenticateUsingStrongBiometrics() == BiometricManager.BIOMETRIC_SUCCESS
}
- val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ val result = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
if (result == BiometricManager.BIOMETRIC_SUCCESS) {
level = SECURITY_LEVEL_BIOMETRIC
AsyncFunction("getEnrolledLevelAsync") {
@@ -80,7 +99,7 @@ class LocalAuthenticationModule : Module() {
if (isDeviceSecure) {
level = SECURITY_LEVEL_SECRET
}
- if (canAuthenticateUsingWeakBiometrics() == BiometricManager.BIOMETRIC_SUCCESS) {
+ if (canAuthenticateUsingStrongBiometrics() == BiometricManager.BIOMETRIC_SUCCESS) {
level = SECURITY_LEVEL_BIOMETRIC
}
return@AsyncFunction level
@@ -235,10 +254,19 @@ class LocalAuthenticationModule : Module() {
if (disableDeviceFallback) {
setNegativeButtonText(cancelLabel)
} else {
- setAllowedAuthenticators(
- BiometricManager.Authenticators.BIOMETRIC_WEAK
- or BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
+ if (Build.VERSION.SDK_INT >= 30) {
+ setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ or BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ } else {
+ setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ )
+ cancelLabel?.let {
+ setNegativeButtonText(it)
+ }
+ }
}
setConfirmationRequired(requireConfirmation)
}
promise.resolve(level)
}
@@ -336,6 +364,9 @@ class LocalAuthenticationModule : Module() {
private fun canAuthenticateUsingWeakBiometrics(): Int =
biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+ @ExpoMethod
+ fun enrollForAuthentication(promise: Promise) {
+ if (Build.VERSION.SDK_INT >= 30) {
+ val intent = Intent(Settings.ACTION_BIOMETRIC_ENROLL)
+ intent.putExtra(
+ Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ )
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ currentActivity!!.startActivity(intent)
+ promise.resolve(true)
+ } else {
+ val intent = Intent(Settings.ACTION_FINGERPRINT_ENROLL)
+ currentActivity!!.startActivity(intent)
+ promise.resolve(true)
+ }
+ }
+ private fun canAuthenticateUsingStrongBiometrics(): Int =
+ biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+
@ExpoMethod
fun authenticateAsync(options: Map<String?, Any?>, promise: Promise) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
@@ -220,10 +239,19 @@ class LocalAuthenticationModule(context: Context) : ExportedModule(context), Act
promptInfoBuilder.setNegativeButtonText(it)
}
} else {
- promptInfoBuilder.setAllowedAuthenticators(
- BiometricManager.Authenticators.BIOMETRIC_WEAK
- or BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
+ if (Build.VERSION.SDK_INT >= 30) {
+ promptInfoBuilder.setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ or BiometricManager.Authenticators.DEVICE_CREDENTIAL
+ )
+ } else {
+ promptInfoBuilder.setAllowedAuthenticators(
+ BiometricManager.Authenticators.BIOMETRIC_STRONG
+ )
+ cancelLabel?.let {
+ promptInfoBuilder.setNegativeButtonText(it)
+ }
+ }
}
promptInfoBuilder.setConfirmationRequired(requireConfirmation)
val promptInfo = promptInfoBuilder.build()
private fun createResponse(
error: String? = null,
warning: String? = null
diff --git a/src/LocalAuthentication.ts b/src/LocalAuthentication.ts
index b9f3f0732b61138f790e7c3a33a92d8d726a71f1..b754d8588701c7d74a86a07b8e6c09c2434b9d43 100644
index b9f3f0732b61138f790e7c3a33a92d8d726a71f1..b847e90d87e91e4a5be83d1b9c90dbc6fe6188bb 100644
--- a/src/LocalAuthentication.ts
+++ b/src/LocalAuthentication.ts
@@ -78,11 +78,16 @@ export async function getEnrolledLevelAsync(): Promise<SecurityLevel> {
@@ -78,12 +78,17 @@ export async function getEnrolledLevelAsync(): Promise<SecurityLevel> {
* @return Returns a promise which fulfils with [`LocalAuthenticationResult`](#localauthenticationresult).
*/
export async function authenticateAsync(
......@@ -106,14 +109,15 @@ index b9f3f0732b61138f790e7c3a33a92d8d726a71f1..b754d8588701c7d74a86a07b8e6c09c2
if (!ExpoLocalAuthentication.authenticateAsync) {
throw new UnavailabilityError('expo-local-authentication', 'authenticateAsync');
}
+
+ invariant(
+ typeof options.cancelLabel === 'string' && options.cancelLabel.length,
+ 'LocalAuthentication.authenticateAsync : `options.cancelLabel` must be a non-empty string.'
+ );
+
if (options.hasOwnProperty('promptMessage')) {
invariant(
typeof options.promptMessage === 'string' && options.promptMessage.length,
diff --git a/src/LocalAuthentication.types.ts b/src/LocalAuthentication.types.ts
index a65b16d5bc5d18c03cee3ca8e4d223c9b736659b..d1ae18df61714b2bd44f3b9867718568707c1dc6 100644
--- a/src/LocalAuthentication.types.ts
......
diff --git a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
index 9fc8eeb9b6b66061fca818a17127ff6f21df3126..6a3c9f1addd4bd8bcea3afc2e60aca52cd4c8248 100644
--- a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
+++ b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
@@ -67,6 +67,14 @@ public class ContextMenuView extends ReactViewGroup implements View.OnCreateCont
}
}
}
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (dropdownMenuMode && !disabled) {
+ showContextMenu(e.getX(), e.getY());
+ }
+ return super.onSingleTapConfirmed(e);
+ }
});
}
diff --git a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
index 4b5b90b7b478668fdff3fd12d5e028d423ada057..af30dc6f700b3b3cfde5c149bf1f865786df3e27 100644
--- a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
+++ b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
@@ -67,6 +67,14 @@ public class ContextMenuView extends ReactViewGroup implements PopupMenu.OnMenuI
contextMenu.show();
}
}
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (dropdownMenuMode) {
+ contextMenu.show();
+ }
+ return super.onSingleTapConfirmed(e);
+ }
});
}
diff --git a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuManager.java b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuManager.java
index 81f7b4b58c946d1b2e14301f9b52ecffa1cd0643..403dac6450be24a8c4d26ffb8293b51a1485f6a8 100644
--- a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuManager.java
+++ b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuManager.java
@@ -45,6 +45,11 @@ public class ContextMenuManager extends ViewGroupManager<ContextMenuView> {
view.setDropdownMenuMode(enabled);
}
+ @ReactProp(name = "disabled")
+ public void setDisabled(ContextMenuView view, @Nullable boolean disabled) {
+ view.setDisabled(disabled);
+ }
+
@androidx.annotation.Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
diff --git a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
index af30dc6f700b3b3cfde5c149bf1f865786df3e27..aa04fe6d9458601fdcb9bb44f89e16bbc1ad9d39 100644
--- a/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
+++ b/android/src/main/java/com/mpiannucci/reactnativecontextmenu/ContextMenuView.java
@@ -43,6 +43,8 @@ public class ContextMenuView extends ReactViewGroup implements PopupMenu.OnMenuI
boolean cancelled = true;
+ private boolean disabled = false;
+
protected boolean dropdownMenuMode = false;
public ContextMenuView(final Context context) {
@@ -87,13 +89,18 @@ public class ContextMenuView extends ReactViewGroup implements PopupMenu.OnMenuI
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
- return true;
+ return disabled ? false : true;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
- gestureDetector.onTouchEvent(ev);
- return true;
+ if (disabled) return false;
+ gestureDetector.onTouchEvent(ev);
+ return true;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
}
public void setActions(@Nullable ReadableArray actions) {
diff --git a/jest/setup.js b/jest/setup.js
index 3738bd2c61e516fa431f61fda47f2474f72dba42..2b3266007b3c9412d99e7ceee205ee52e3008077 100644
--- a/jest/setup.js
+++ b/jest/setup.js
@@ -17,12 +17,12 @@ jest.requireActual('@react-native/polyfills/error-guard');
global.__DEV__ = true;
-global.performance = {
+if (!global.performance) global.performance = {
now: jest.fn(Date.now),
};
global.regeneratorRuntime = jest.requireActual('regenerator-runtime/runtime');
-global.window = global;
+if (!global.window) global.window = global;
global.requestAnimationFrame = function (callback) {
return setTimeout(callback, 0);
diff --git a/third-party-podspecs/boost.podspec b/third-party-podspecs/boost.podspec
index 3d9331c95d1217682a0b820a0d9440fdff074ae0..8276eb1a5854f945462363fe8db917e8270b3b6a 100644
--- a/third-party-podspecs/boost.podspec
+++ b/third-party-podspecs/boost.podspec
@@ -10,8 +10,8 @@ Pod::Spec.new do |spec|
spec.homepage = 'http://www.boost.org'
spec.summary = 'Boost provides free peer-reviewed portable C++ source libraries.'
spec.authors = 'Rene Rivera'
- spec.source = { :http => 'https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2',
- :sha256 => 'f0397ba6e982c4450f27bf32a2a83292aba035b827a5623a14636ea583318c41' }
+ spec.source = { :http => 'https://sourceforge.net/projects/boost/files/boost/1.83.0/boost_1_83_0.tar.bz2',
+ :sha256 => '6478edfe2f3305127cffe8caf73ea0176c53769f4bf1585be237eb30798c3b8e' }
# Pinning to the same version as React.podspec.
spec.platforms = { :ios => '11.0' }
diff --git a/src/charts/line/ChartPath.tsx b/src/charts/line/ChartPath.tsx
index 3807c185c9456d2976c305df94574ff7d948b32a..5cf985422cf49120f943c98084b08c16faf452d9 100644
--- a/src/charts/line/ChartPath.tsx
+++ b/src/charts/line/ChartPath.tsx
@@ -18,7 +18,6 @@ const BACKGROUND_COMPONENTS = [
'LineChartHighlight',
'LineChartHorizontalLine',
'LineChartGradient',
- 'LineChartDot',
'LineChartTooltip',
];
const FOREGROUND_COMPONENTS = ['LineChartHighlight', 'LineChartDot'];
@@ -166,10 +165,25 @@ export function LineChartPathWrapper({
<View style={StyleSheet.absoluteFill}>
<AnimatedSVG animatedProps={svgProps} height={height}>
<LineChartPath color={color} width={strokeWidth} {...pathProps} />
+ </AnimatedSVG>
+ </View>
+
+ </LineChartPathContext.Provider>
+ <LineChartPathContext.Provider
+ value={{
+ color,
+ isInactive: false,
+ isTransitionEnabled: pathProps.isTransitionEnabled ?? true,
+ }}
+ >
+ <View style={StyleSheet.absoluteFill}>
+ <AnimatedSVG animatedProps={svgProps} height={height}>
{foregroundChildren}
</AnimatedSVG>
</View>
+
</LineChartPathContext.Provider>
diff --git a/lib/typescript/src/charts/line/useDatetime.d.ts b/lib/typescript/src/charts/line/useDatetime.d.ts
index c6f73dd8b31294d0e4c0597519dd998ccd84ad30..9f9eb03e25d1e020de37c706e8f8803d0b9dcefa 100644
--- a/lib/typescript/src/charts/line/useDatetime.d.ts
+++ b/lib/typescript/src/charts/line/useDatetime.d.ts
@@ -1,13 +1,10 @@
import type { TFormatterFn } from '../candle/types';
+import { SharedValue } from 'react-native-reanimated';
export declare function useLineChartDatetime({ format, locale, options, }?: {
format?: TFormatterFn<number>;
locale?: string;
options?: Intl.DateTimeFormatOptions;
}): {
- value: Readonly<{
- value: string;
- }>;
- formatted: Readonly<{
- value: string;
- }>;
+ value: SharedValue<string>;
+ formatted: SharedValue<string>;
};
diff --git a/src/charts/line/Dot.tsx b/src/charts/line/Dot.tsx
index dd49d3e49231a5e4f56138bbf3ec51013515f7b0..2c240689e2b51c265dc5deab885124d3fa216424 100644
--- a/src/charts/line/Dot.tsx
+++ b/src/charts/line/Dot.tsx
@@ -1,18 +1,18 @@
-import * as React from 'react';
+import * as React from "react";
import Animated, {
Easing,
useAnimatedProps,
- useDerivedValue,
+ useSharedValue,
withRepeat,
withSequence,
withTiming,
-} from 'react-native-reanimated';
-import { Circle, CircleProps } from 'react-native-svg';
-import { getYForX } from 'react-native-redash';
+} from "react-native-reanimated";
+import { getYForX } from "react-native-redash";
+import { Circle, CircleProps } from "react-native-svg";
-import { LineChartDimensionsContext } from './Chart';
-import { LineChartPathContext } from './LineChartPathContext';
-import { useLineChart } from './useLineChart';
+import { LineChartDimensionsContext } from "./Chart";
+import { LineChartPathContext } from "./LineChartPathContext";
+import { useLineChart } from "./useLineChart";
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
@@ -33,7 +33,7 @@ export type LineChartDotProps = {
*
* Default: `while-inactive`
*/
- pulseBehaviour?: 'always' | 'while-inactive';
+ pulseBehaviour?: "always" | "while-inactive";
/**
* Defaults to `size * 4`
*/
@@ -41,17 +41,19 @@ export type LineChartDotProps = {
pulseDurationMs?: number;
};
-LineChartDot.displayName = 'LineChartDot';
+LineChartDot.displayName = "LineChartDot";
+
</>
+const easing = Easing.out(Easing.sin);
export function LineChartDot({
at,
- color: defaultColor = 'black',
+ color: defaultColor = "black",
dotProps,
hasOuterDot: defaultHasOuterDot = false,
hasPulse = false,
inactiveColor,
outerDotProps,
- pulseBehaviour = 'while-inactive',
+ pulseBehaviour = "while-inactive",
pulseDurationMs = 800,
showInactiveColor = true,
size = 4,
@@ -72,29 +74,44 @@ export function LineChartDot({
////////////////////////////////////////////////////////////
- const x = useDerivedValue(
- () => withTiming(pointWidth * at),
- [at, pointWidth]
- );
- const y = useDerivedValue(
- () => withTiming(getYForX(parsedPath!, x.value) || 0),
- [parsedPath, x]
- );
+ const x = pointWidth * at;
+ const y = getYForX(parsedPath!, x) ?? 0;
////////////////////////////////////////////////////////////
- const animatedDotProps = useAnimatedProps(
- () => ({
- cx: x.value,
- cy: y.value,
- }),
- [x, y]
- );
+ const animatedOpacity = useSharedValue(0.1);
+ const animatedScale = useSharedValue(0);
+
+ React.useEffect(() => {
+ animatedOpacity.value = withRepeat(
+ withSequence(
+ withTiming(0.8, {
+ duration: 0,
+ }),
+ withTiming(0, {
+ duration: pulseDurationMs,
+ easing,
+ })
+ ),
+ -1
+ );
+
+ animatedScale.value = withRepeat(
+ withSequence(
+ withTiming(0, {
+ duration: 0,
+ }),
+ withTiming(1, {
+ duration: pulseDurationMs,
+ easing,
+ })
+ ),
+ -1
+ );
+ }, []);
const animatedOuterDotProps = useAnimatedProps(() => {
let defaultProps = {
- cx: x.value,
- cy: y.value,
opacity: 0.1,
r: outerSize,
};
@@ -103,58 +120,35 @@ export function LineChartDot({
return defaultProps;
}
- if (isActive.value && pulseBehaviour === 'while-inactive') {
+ if (isActive.value && pulseBehaviour === "while-inactive") {
return {
...defaultProps,
r: 0,
};
}
- const easing = Easing.out(Easing.sin);
- const animatedOpacity = withRepeat(
- withSequence(
- withTiming(0.8),
- withTiming(0, {
- duration: pulseDurationMs,
- easing,
- })
- ),
- -1,
- false
- );
- const scale = withRepeat(
- withSequence(
- withTiming(0),
- withTiming(outerSize, {
- duration: pulseDurationMs,
- easing,
- })
- ),
- -1,
- false
- );
-
- if (pulseBehaviour === 'while-inactive') {
+ if (pulseBehaviour === "while-inactive") {
return {
...defaultProps,
- opacity: isActive.value ? withTiming(0) : animatedOpacity,
- r: isActive.value ? withTiming(0) : scale,
+ opacity: isActive.value ? withTiming(0) : animatedOpacity.value,
+ r: isActive.value ? withTiming(0) : outerSize * animatedScale.value,
};
}
return {
...defaultProps,
- opacity: animatedOpacity,
- r: scale,
+ opacity: animatedOpacity.value,
+ r: outerSize * animatedScale.value,
};
- }, [hasPulse, isActive, outerSize, pulseBehaviour, pulseDurationMs, x, y]);
+ }, []);
////////////////////////////////////////////////////////////
return (
<>
<AnimatedCircle
- animatedProps={animatedDotProps}
r={size}
+ cx={x}
+ cy={y}
fill={color}
opacity={opacity}
{...dotProps}
@@ -163,6 +157,8 @@ export function LineChartDot({
<AnimatedCircle
animatedProps={animatedOuterDotProps}
fill={color}
+ cx={x}
+ cy={y}
{...outerDotProps}
/>
)}
diff --git a/src/charts/line/Path.tsx b/src/charts/line/Path.tsx
index 5c5830792b0ac9ace3316d869b24f98400694f63..ca8a083a740f70ebe673e22820ae77db48dbe9c0 100644
--- a/src/charts/line/Path.tsx
+++ b/src/charts/line/Path.tsx
@@ -1,10 +1,10 @@
-import * as React from 'react';
-import Animated from 'react-native-reanimated';
-import { Path, PathProps } from 'react-native-svg';
+import * as React from "react";
+import Animated from "react-native-reanimated";
+import { Path, PathProps } from "react-native-svg";
-import { LineChartDimensionsContext } from './Chart';
-import { LineChartPathContext } from './LineChartPathContext';
-import useAnimatedPath from './useAnimatedPath';
+import { LineChartDimensionsContext } from "./Chart";
+import { LineChartPathContext } from "./LineChartPathContext";
+import useAnimatedPath from "./useAnimatedPath";
const AnimatedPath = Animated.createAnimatedComponent(Path);
@@ -31,10 +31,10 @@ export type LineChartPathProps = Animated.AnimateProps<PathProps> & {
isTransitionEnabled?: boolean;
};
-LineChartPath.displayName = 'LineChartPath';
+LineChartPath.displayName = "LineChartPath";
export function LineChartPath({
- color = 'black',
+ color = "black",
inactiveColor,
width: strokeWidth = 3,
...props
@@ -53,15 +53,14 @@ export function LineChartPath({
////////////////////////////////////////////////
return (
- <>
- <AnimatedPath
- animatedProps={animatedProps}
- fill="transparent"
- stroke={isInactive ? inactiveColor || color : color}
- strokeOpacity={isInactive && !inactiveColor ? 0.2 : 1}
- strokeWidth={strokeWidth}
- {...props}
- />
- </>
+ <AnimatedPath
+ animatedProps={animatedProps}
+ d={isTransitionEnabled ? undefined : path}
+ fill="transparent"
+ stroke={isInactive ? inactiveColor || color : color}
+ strokeOpacity={isInactive && !inactiveColor ? 0.2 : 1}
+ strokeWidth={strokeWidth}
+ {...props}
+ />
);
}
diff --git a/android/build.gradle b/android/build.gradle
index 83a10e5c57ba7c8d82db28f8e868a93327790457..dbda4fa4b0391a210f902c8ab17375a29594d22d 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -50,6 +50,13 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
+ // Resolves library merge issues during detox e2e build
+ packagingOptions {
+ pickFirst 'lib/x86/libc++_shared.so'
+ pickFirst 'lib/x86_64/libc++_shared.so'
+ pickFirst 'lib/armeabi-v7a/libc++_shared.so'
+ pickFirst 'lib/arm64-v8a/libc++_shared.so'
+ }
}
repositories {
IPFS hash of the deployment:
- CIDv0: `QmTqFxX9jtCD9To3o7DprzKi8GX2Zcfj78Q1bes3iSu7Pk`
- CIDv1: `bafybeicrt2gyxhucirummgef7lv7pudqbamzmjvycfm2my6fkdybdix3z4`
- CIDv0: `QmZirSDi4Fvdxc9L8veeJwpU38XLuBHQYFKR2avBuyXUqA`
- CIDv1: `bafybeifjecl3zl5tsaxhooqwfibjzteb3w4ckaag5oos2oqnkadkpovumu`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
......@@ -10,15 +10,59 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeicrt2gyxhucirummgef7lv7pudqbamzmjvycfm2my6fkdybdix3z4.ipfs.dweb.link/
- https://bafybeicrt2gyxhucirummgef7lv7pudqbamzmjvycfm2my6fkdybdix3z4.ipfs.cf-ipfs.com/
- [ipfs://QmTqFxX9jtCD9To3o7DprzKi8GX2Zcfj78Q1bes3iSu7Pk/](ipfs://QmTqFxX9jtCD9To3o7DprzKi8GX2Zcfj78Q1bes3iSu7Pk/)
- https://bafybeifjecl3zl5tsaxhooqwfibjzteb3w4ckaag5oos2oqnkadkpovumu.ipfs.dweb.link/
- https://bafybeifjecl3zl5tsaxhooqwfibjzteb3w4ckaag5oos2oqnkadkpovumu.ipfs.cf-ipfs.com/
- [ipfs://QmZirSDi4Fvdxc9L8veeJwpU38XLuBHQYFKR2avBuyXUqA/](ipfs://QmZirSDi4Fvdxc9L8veeJwpU38XLuBHQYFKR2avBuyXUqA/)
### 5.26.1 (2024-05-10)
## 5.27.0 (2024-05-14)
### Features
* **web:** add "name='description'" tags for SEO (#7782) 75b57c7
* **web:** add retry with classic in xv2 GPA error (#7375) 92ccc68
* **web:** add uniswapX v2 (#7072) 25c0827
* **web:** fix token explore filter test (#8103) 348cd70
* **web:** refactor Send flow to use wagmi signer (#7596) baa4e5e
* **web:** send connection meta to Sentry (#7650) a576fc1
* **web:** update/add SEO descriptions to all pages (#7984) f0a5c71
* **web:** wagmi connection (#7558) 80dd479
### Bug Fixes
* **web:** using web accessible resources to block extension (#8097) 3a5dadd
* **web:** add build = true to craco fork ts checker to avoid output file ts errors (#7980) c513191
* **web:** add info to swap modified event (#8044) 31d52d1
* **web:** adding vercels unique inline script (#7918) 9622186
* **web:** block v3 pool creation for FOT tokens (#7735) f4f22e9
* **web:** hotfix warning modified extensions (#8071) 065cd16
* **web:** hover animation behind Hero title (#8055) e740bb8
* **web:** landing page gap staging (#8177) e3870f3
* **web:** limits insufficient-balance behaviors (#7977) b18f808
* **web:** multi routing copy is wrong (#7923) ccaecac
* **web:** set locale in state when clicking SwitchLocaleLink (#7968) 931c7cb
* **web:** try hardcoded block number from 24hrs ago (#8119) 7b1e92b
* **web:** update favicon (#7976) 4396120
### Build Systems
* **web:** static ip runner (#8109) 42159c2
### Tests
* **web:** annotate proxy fetch flakiness workaround (#7998) 1589c7e
* **web:** reset between suites, revert within suites (#8012) 746b2cb
* **web:** revert chain after liquidity test (#8065) 6adbb1a
* **web:** skip duplicate static analysis in CI (#7996) 9e07b63
* **web:** use stable CSP for cloud-tests (#7997) e885436
### Code Refactoring
* **web:** block infringing collection (#7915) 7aa7d29
* **web:** update transactions hooks to use wagmi directly (#7584) 4ec79a3
* **web:** use wagmi for chainId (#7953) a43c588
web/5.26.1
\ No newline at end of file
web/5.27.0
\ No newline at end of file
......@@ -131,17 +131,17 @@ android {
dev {
isDefault(true)
applicationIdSuffix ".dev"
versionName "1.27"
versionName "1.28"
dimension "variant"
}
beta {
applicationIdSuffix ".beta"
versionName "1.27"
versionName "1.28"
dimension "variant"
}
prod {
dimension "variant"
versionName "1.27"
versionName "1.28"
}
}
......@@ -183,7 +183,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.6"
kotlinCompilerExtensionVersion = "1.4.6"
}
sourceSets {
......@@ -242,18 +242,11 @@ dependencies {
implementation "com.google.accompanist:accompanist-flowlayout:$flowlayout"
implementation project(':@sentry_react-native')
// Used for device-reported performance class.
implementation("androidx.core:core-performance:$corePerf")
implementation("androidx.core:core-performance-play-services:$corePerf")
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
implementation("com.facebook.react:flipper-integration")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
......
......@@ -8,6 +8,5 @@
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="true"/>
</application>
</manifest>
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.uniswap;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}
......@@ -2,22 +2,22 @@ package com.uniswap
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.flipper.ReactNativeFlipper
import com.facebook.soloader.SoLoader
import androidx.multidex.MultiDexApplication;
import androidx.multidex.MultiDexApplication
import com.shopify.reactnativeperformance.ReactNativePerformance
import com.uniswap.onboarding.scantastic.ScantasticEncryptionModule
class MainApplication : MultiDexApplication(), ReactApplication {
private val mReactNativeHost: ReactNativeHost =
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
......@@ -26,11 +26,14 @@ class MainApplication : MultiDexApplication(), ReactApplication {
add(RNCloudStorageBackupsManagerModule())
add(ScantasticEncryptionModule())
}
override fun getJSMainModuleName(): String {
return "index"
}
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override val isNewArchEnabled: Boolean
get() = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
......@@ -38,9 +41,8 @@ class MainApplication : MultiDexApplication(), ReactApplication {
get() = BuildConfig.IS_HERMES_ENABLED
}
override fun getReactNativeHost(): ReactNativeHost {
return mReactNativeHost
}
override val reactHost: ReactHost
get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
override fun onCreate() {
ReactNativePerformance.onAppStarted()
......@@ -48,7 +50,7 @@ class MainApplication : MultiDexApplication(), ReactApplication {
SoLoader.init(this,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();
load()
}
ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager);
}
......
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.uniswap;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
/**
* Class responsible of loading Flipper inside your React Native application. This is the release
* flavor of it so it's empty as we don't want to load Flipper.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
// Do nothing as we don't want to initialize Flipper on Release.
}
}
......@@ -2,7 +2,7 @@
buildscript {
ext {
buildToolsVersion = "33.0.0"
buildToolsVersion = "34.0.0"
minSdkVersion = 28
compileSdkVersion = 34
targetSdkVersion = 34
......@@ -22,13 +22,13 @@ buildscript {
mavenCentral()
}
dependencies {
classpath('com.android.tools.build:gradle:7.4.2')
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath('com.google.gms:google-services:4.3.15')
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("io.sentry:sentry-android-gradle-plugin:3.13.0")
classpath("io.sentry:sentry-android-gradle-plugin:4.4.0")
}
}
......@@ -56,3 +56,5 @@ allprojects {
}
}
}
apply plugin: "com.facebook.react.rootproject"
......@@ -27,7 +27,7 @@ android.useAndroidX=true
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.162.0
FLIPPER_VERSION=0.212.0
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
......
#Wed Aug 16 13:06:20 EDT 2023
#Wed April 10 16:30:26 EDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
......@@ -23,41 +23,41 @@
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$(ls -ld "$app_path")
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$(cd "${APP_HOME:-./}" >/dev/null && pwd -P) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
warn() {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
die() {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
......@@ -65,119 +65,120 @@ cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$(uname)" in
CYGWIN*)
cygwin=true
;;
Darwin*)
darwin=true
;;
MINGW*)
msys=true
;;
NONSTOP*)
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ]; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD="java"
if ! command -v java >/dev/null 2>&1; then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then
MAX_FD_LIMIT=$(ulimit -H -n)
if [ $? -eq 0 ]; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ]; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
if [ "$cygwin" = "true" -o "$msys" = "true" ]; then
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
JAVACMD=$(cygpath --unix "$JAVACMD")
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
SEP=""
for dir in $ROOTDIRSRAW; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ]; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@"; do
CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -)
CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
else
eval $(echo args$i)="\"$arg\""
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
i=$(expr $i + 1)
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
save() {
for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done
echo " "
}
APP_ARGS=`save "$@"`
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
......
......@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
......@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
......@@ -75,13 +76,16 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not "" == "%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
......
rootProject.name = 'Uniswap'
apply from: file("../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
applyNativeModulesSettingsGradle(settings)
includeBuild('../../../node_modules/react-native-gradle-plugin')
includeBuild('../../../node_modules/@react-native/gradle-plugin')
include ':app'
apply from: new File(["node", "--print", "require.resolve('../../../node_modules/expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
......
......@@ -2,6 +2,7 @@
"name": "Uniswap",
"displayName": "Uniswap",
"appStoreUrl": "https://apps.apple.com/app/apple-store/id6443944476",
"playStoreUrl": "https://play.google.com/store/apps/details?id=com.uniswap.mobile",
"plugins": [
"expo-localization"
],
......
......@@ -15,6 +15,14 @@ module.exports = function (api) {
// config: '../../packages/ui/src/tamagui.config.ts',
// },
// ],
[
'module-resolver',
{
alias: {
'src': './src',
}
}
],
[
'module:react-native-dotenv',
{
......@@ -38,8 +46,6 @@ module.exports = function (api) {
'@babel/plugin-proposal-logical-assignment-operators',
// metro doesn't like these
'@babel/plugin-proposal-numeric-separator',
// automatically require React when using JSX
'react-require',
].filter(Boolean)
if (inProduction) {
......@@ -52,7 +58,7 @@ module.exports = function (api) {
// speeds up compile
'**/@tamagui/**/dist/**',
],
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
plugins,
}
}
......@@ -46,14 +46,13 @@ target 'Uniswap' do
post_install do |installer|
react_native_post_install(installer, "../../../node_modules/react-native")
__apply_Xcode_12_5_M1_post_install_workaround(installer)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'No'
end
end
end
end
end
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -7,7 +7,7 @@
#import <React/RCTBundleURLProvider.h>
#import <ReactNativePerformance/ReactNativePerformance.h>
#import <React/RCTAppSetupUtils.h>
#import <RCTAppSetupUtils.h>
#import "RNSplashScreen.h"
@implementation AppDelegate
......
......@@ -31,6 +31,8 @@ jest.mock('@sentry/react-native', () => ({
// Disables animated driver warning
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper')
jest.mock('@walletconnect/react-native-compat', () => ({}))
jest.mock('src/lib/RNEthersRs')
// Mock OneSignal package
......
......@@ -8,7 +8,7 @@ const { getMetroAndroidAssetsResolutionFix } = require('react-native-monorepo-to
const androidAssetsResolutionFix = getMetroAndroidAssetsResolutionFix()
const path = require('path')
const { getDefaultConfig } = require('metro-config')
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const mobileRoot = path.resolve(__dirname)
const workspaceRoot = path.resolve(mobileRoot, '../..')
......@@ -17,32 +17,35 @@ const watchFolders = [mobileRoot, `${workspaceRoot}/node_modules`, `${workspaceR
const detoxExtensions = process.env.DETOX_MODE === 'mocked' ? ['mock.tsx', 'mock.ts'] : []
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig()
return {
resolver: {
nodeModulesPaths: [`${workspaceRoot}/node_modules`],
assetExts: assetExts.filter((ext) => ext !== 'svg'),
// detox mocking works properly only being spreaded at the beginning of sourceExts array
sourceExts: [...detoxExtensions, ...sourceExts, 'svg', 'cjs']
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
babelTransformerPath: require.resolve('react-native-svg-transformer'),
publicPath: androidAssetsResolutionFix.publicPath,
},
server: {
enhanceMiddleware: (middleware) => {
return androidAssetsResolutionFix.applyMiddleware(middleware)
const defaultConfig = getDefaultConfig(__dirname);
const {
resolver: { sourceExts, assetExts },
} = defaultConfig;
const config = {
resolver: {
nodeModulesPaths: [`${workspaceRoot}/node_modules`],
assetExts: assetExts.filter((ext) => ext !== 'svg'),
// detox mocking works properly only being spreaded at the beginning of sourceExts array
sourceExts: [...detoxExtensions, ...sourceExts, 'svg', 'cjs']
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
babelTransformerPath: require.resolve('react-native-svg-transformer'),
publicPath: androidAssetsResolutionFix.publicPath,
},
server: {
enhanceMiddleware: (middleware) => {
return androidAssetsResolutionFix.applyMiddleware(middleware)
},
watchFolders,
}
})()
},
watchFolders,
}
module.exports = mergeConfig(defaultConfig, config);
......@@ -4,12 +4,12 @@
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {
"android": "react-native run-android --variant=devDebug --appIdSuffix=dev",
"android:release": "react-native run-android --variant=devRelease --appIdSuffix=dev",
"android:beta": "react-native run-android --variant=betaDebug --appIdSuffix=beta",
"android:beta:release": "react-native run-android --variant=betaRelease --appIdSuffix=beta",
"android:prod": "react-native run-android --variant=prodDebug",
"android:prod:release": "react-native run-android --variant=prodRelease",
"android": "react-native run-android --mode=devDebug --appIdSuffix=dev",
"android:release": "react-native run-android --mode=devRelease --appIdSuffix=dev",
"android:beta": "react-native run-android --mode=betaDebug --appIdSuffix=beta",
"android:beta:release": "react-native run-android --mode=betaRelease --appIdSuffix=beta",
"android:prod": "react-native run-android --mode=prodDebug",
"android:prod:release": "react-native run-android --mode=prodRelease",
"check:deps:usage": "./scripts/checkDepsUsage.sh",
"clean": "react-native-clean-project",
"debug": "react-devtools",
......@@ -69,6 +69,7 @@
"@react-native-firebase/auth": "18.4.0",
"@react-native-firebase/firestore": "18.4.0",
"@react-native-masked-view/masked-view": "0.2.9",
"@react-native/metro-config": "0.75.0-main",
"@react-navigation/core": "6.2.2",
"@react-navigation/native": "6.0.11",
"@react-navigation/native-stack": "6.7.0",
......@@ -79,7 +80,7 @@
"@shopify/flash-list": "1.6.3",
"@shopify/react-native-performance": "4.1.2",
"@shopify/react-native-performance-navigation": "3.0.0",
"@shopify/react-native-skia": "0.1.187",
"@shopify/react-native-skia": "1.2.0",
"@uniswap/analytics": "1.7.0",
"@uniswap/analytics-events": "2.32.0",
"@uniswap/ethers-rs-mobile": "0.0.5",
......@@ -95,19 +96,18 @@
"cross-fetch": "3.1.5",
"dayjs": "1.11.7",
"ethers": "5.7.2",
"expo": "48.0.19",
"expo-av": "13.4.1",
"expo-barcode-scanner": "12.7.0",
"expo-blur": "12.6.0",
"expo-camera": "13.4.4",
"expo-clipboard": "4.1.2",
"expo-linear-gradient": "12.3.0",
"expo-linking": "4.0.1",
"expo-local-authentication": "13.0.2",
"expo-localization": "14.1.1",
"expo-modules-core": "1.5.8",
"expo-screen-capture": "4.2.0",
"expo-store-review": "~6.2.1",
"expo": "50.0.15",
"expo-av": "13.10.5",
"expo-barcode-scanner": "12.9.3",
"expo-blur": "12.9.2",
"expo-camera": "14.1.2",
"expo-clipboard": "5.0.1",
"expo-linear-gradient": "12.7.2",
"expo-linking": "6.2.2",
"expo-local-authentication": "13.8.0",
"expo-localization": "14.8.3",
"expo-screen-capture": "5.8.1",
"expo-store-review": "6.8.3",
"fuse.js": "6.5.3",
"i18next": "23.10.0",
"lodash": "4.17.21",
......@@ -115,9 +115,9 @@
"react": "18.2.0",
"react-freeze": "1.0.3",
"react-i18next": "14.1.0",
"react-native": "0.71.13",
"react-native-appsflyer": "6.10.3",
"react-native-context-menu-view": "1.6.0",
"react-native": "0.73.6",
"react-native-appsflyer": "6.13.1",
"react-native-context-menu-view": "1.15.0",
"react-native-device-info": "10.0.2",
"react-native-fast-image": "8.6.3",
"react-native-gesture-handler": "2.15.0",
......@@ -130,12 +130,12 @@
"react-native-onesignal": "4.5.2",
"react-native-pager-view": "6.0.1",
"react-native-permissions": "3.6.0",
"react-native-reanimated": "3.3.0",
"react-native-reanimated": "3.8.1",
"react-native-restart": "0.0.27",
"react-native-safe-area-context": "4.9.0",
"react-native-screens": "3.24.0",
"react-native-screens": "3.30.1",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "13.9.0",
"react-native-svg": "15.1.0",
"react-native-tab-view": "3.5.2",
"react-native-url-polyfill": "1.3.0",
"react-native-wagmi-charts": "2.3.0",
......@@ -146,8 +146,7 @@
"redux-mock-store": "1.5.4",
"redux-persist": "6.0.0",
"redux-saga": "1.2.2",
"rive-react-native": "6.1.1",
"statsig-react-native": "4.11.0",
"rive-react-native": "7.0.0",
"typed-redux-saga": "1.5.0",
"uniswap": "workspace:^",
"utilities": "workspace:^",
......@@ -157,29 +156,27 @@
"@babel/core": "^7.20.5",
"@babel/plugin-proposal-export-namespace-from": "7.18.9",
"@babel/plugin-proposal-logical-assignment-operators": "7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-numeric-separator": "7.16.7",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/runtime": "7.18.9",
"@faker-js/faker": "7.6.0",
"@storybook/react": "7.0.2",
"@tamagui/babel-plugin": "1.94.5",
"@tamagui/babel-plugin": "1.95.1",
"@testing-library/react-hooks": "7.0.2",
"@testing-library/react-native": "11.5.0",
"@types/react-native": "0.71.3",
"@types/redux-mock-store": "1.0.6",
"@uniswap/eslint-config": "workspace:^",
"@walletconnect/types": "2.11.2",
"@welldone-software/why-did-you-render": "8.0.1",
"babel-loader": "8.2.3",
"babel-plugin-module-resolver": "5.0.0",
"babel-plugin-react-native-web": "0.17.5",
"babel-plugin-react-require": "4.0.0",
"core-js": "2.6.12",
"detox": "20.18.1",
"eslint": "8.44.0",
"expo-modules-core": "1.11.13",
"hardhat": "2.14.0",
"jest": "29.7.0",
"jest-expo": "49.0.0",
"jest-expo": "50.0.4",
"jest-extended": "4.0.1",
"jest-transformer-svg": "2.0.0",
"madge": "6.1.0",
......@@ -191,10 +188,10 @@
"react-native-asset": "2.1.1",
"react-native-clean-project": "4.0.1",
"react-native-dotenv": "3.2.0",
"react-native-flipper": "0.187.1",
"react-native-flipper": "0.212.0",
"react-native-mmkv-flipper-plugin": "1.0.0",
"react-native-monorepo-tools": "1.2.1",
"react-native-svg-transformer": "1.0.0",
"react-native-svg-transformer": "1.3.0",
"react-test-renderer": "18.2.0",
"redux-flipper": "2.0.2",
"redux-saga-test-plan": "4.0.4",
......
......@@ -45,14 +45,14 @@ import {
getSentryTracesSamplingRate,
getStatsigEnvironmentTier,
} from 'src/utils/version'
import { StatsigProvider } from 'statsig-react-native'
import { flexStyles, useIsDarkMode } from 'ui/src'
import { config } from 'uniswap/src/config'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { DUMMY_STATSIG_SDK_KEY } from 'uniswap/src/features/gating/constants'
import { WALLET_EXPERIMENTS } from 'uniswap/src/features/gating/experiments'
import { WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/gating/flags'
import { Statsig } from 'uniswap/src/features/gating/sdk/statsig'
import { loadStatsigOverrides } from 'uniswap/src/features/gating/overrides/customPersistedOverrides'
import { Statsig, StatsigProvider } from 'uniswap/src/features/gating/sdk/statsig'
import { UnitagUpdaterContextProvider } from 'uniswap/src/features/unitags/context'
import i18n from 'uniswap/src/i18n/i18n'
import { CurrencyId } from 'uniswap/src/types/currency'
......@@ -138,6 +138,7 @@ function App(): JSX.Element | null {
api: uniswapUrls.statsigProxyUrl,
disableAutoMetricsLogging: true,
disableErrorLogging: true,
initCompletionCallback: loadStatsigOverrides,
},
sdkKey: DUMMY_STATSIG_SDK_KEY,
user: deviceId ? { userID: deviceId } : {},
......
......@@ -70,7 +70,7 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element {
px="$spacing16"
py="$spacing48">
<Flex centered grow gap="$spacing36">
<Image source={DEAD_LUNI} style={styles.errorImage} />
<Image resizeMode="contain" source={DEAD_LUNI} style={styles.errorImage} />
<Flex centered gap="$spacing8">
<Text variant="subheading1">{t('errors.crash.title')}</Text>
<Text variant="body2">{t('errors.crash.message')}</Text>
......@@ -92,7 +92,6 @@ function ErrorScreen({ error }: { error: Error }): JSX.Element {
const styles = StyleSheet.create({
errorImage: {
height: 150,
resizeMode: 'contain',
width: 150,
},
})
......@@ -76,8 +76,8 @@ import { initialModalsState } from 'src/features/modals/modalSlice'
import { initialTelemetryState } from 'src/features/telemetry/slice'
import { initialTweaksState } from 'src/features/tweaks/slice'
import { initialWalletConnectState } from 'src/features/walletConnect/walletConnectSlice'
import { ChainId } from 'uniswap/src/types/chains'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { ChainId } from 'wallet/src/constants/chains'
import {
ExtensionOnboardingState,
initialBehaviorHistoryState,
......
......@@ -4,7 +4,7 @@
/* eslint-disable max-lines */
import dayjs from 'dayjs'
import { ChainId } from 'wallet/src/constants/chains'
import { ChainId } from 'uniswap/src/types/chains'
import { ExtensionOnboardingState } from 'wallet/src/features/behaviorHistory/slice'
import { toSupportedChainId } from 'wallet/src/features/chains/utils'
import { initialFiatCurrencyState } from 'wallet/src/features/fiatCurrency/slice'
......
......@@ -22,11 +22,11 @@ import {
} from 'ui/src'
import { Plus } from 'ui/src/components/icons'
import { spacing } from 'ui/src/theme'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { isAndroid } from 'uniswap/src/utils/platform'
import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { ActionSheetModal, MenuItemProp } from 'wallet/src/components/modals/ActionSheetModal'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { AccountType } from 'wallet/src/features/wallet/accounts/types'
import {
createAccountActions,
......
......@@ -53,7 +53,8 @@ export function ExperimentsModal(): JSX.Element {
paddingRight: spacing.spacing24,
paddingLeft: spacing.spacing24,
}}>
<Accordion type="single">
<Text variant="heading3">Server</Text>
<Accordion collapsible type="single">
<Accordion.Item value="graphql-endpoint">
<AccordionHeader title="⚙️ Custom GraphQL Endpoint" />
......
......@@ -5,8 +5,8 @@ import { OnboardingScreens, Screens } from 'src/screens/Screens'
import { Button, Flex, Text, useIsDarkMode } from 'ui/src'
import ViewOnlyWalletDark from 'ui/src/assets/graphics/view-only-wallet-dark.svg'
import ViewOnlyWalletLight from 'ui/src/assets/graphics/view-only-wallet-light.svg'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { useActiveAccountAddress, useNativeAccountExists } from 'wallet/src/features/wallet/hooks'
import { useAppDispatch } from 'wallet/src/state'
import { ModalName } from 'wallet/src/telemetry/constants'
......
......@@ -475,16 +475,16 @@ exports[`AccountSwitcher renders correctly 1`] = `
<RCTScrollView
CellRendererComponent={[Function]}
ListHeaderComponent={<React.Fragment />}
animatedStyle={
{
"value": {},
}
}
bounces={false}
collapsable={false}
data={[]}
getItem={[Function]}
getItemCount={[Function]}
jestAnimatedStyle={
{
"value": {},
}
}
keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
onContentSizeChange={[Function]}
......@@ -506,6 +506,7 @@ exports[`AccountSwitcher renders correctly 1`] = `
>
<View>
<View
collapsable={false}
onLayout={[Function]}
/>
</View>
......
......@@ -66,7 +66,7 @@ import { RotatableChevron } from 'ui/src/components/icons'
import { spacing } from 'ui/src/theme'
import { FeatureFlags } from 'uniswap/src/features/gating/flags'
import { useFeatureFlag } from 'uniswap/src/features/gating/hooks'
import { OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { useActiveAccountWithThrow } from 'wallet/src/features/wallet/hooks'
import { selectFinishedOnboarding } from 'wallet/src/features/wallet/selectors'
......
......@@ -9,8 +9,8 @@ import { EducationContentType } from 'src/components/education'
import { HomeScreenTabIndex } from 'src/screens/HomeScreenTabIndex'
import { FiatOnRampScreens, OnboardingScreens, Screens, UnitagScreens } from 'src/screens/Screens'
import { UnitagClaim } from 'uniswap/src/features/unitags/types'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { NFTItem } from 'wallet/src/features/nfts/types'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
type NFTItemScreenParams = {
owner?: Address
......
......@@ -5,13 +5,13 @@ exports[`DatetimeText renders loading state 1`] = `null`;
exports[`DatetimeText renders without error 1`] = `
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.4}
style={
{
......@@ -39,15 +39,15 @@ exports[`PriceText renders loading state 1`] = `
>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {
"fontSize": 106,
},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.2}
style={
{
......@@ -77,15 +77,15 @@ exports[`PriceText renders without error 1`] = `
>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {
"fontSize": 106,
},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.2}
style={
{
......@@ -103,7 +103,9 @@ exports[`PriceText renders without error 1`] = `
/>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {
"color": "#CECECE",
......@@ -111,8 +113,6 @@ exports[`PriceText renders without error 1`] = `
},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.2}
style={
{
......@@ -142,15 +142,15 @@ exports[`PriceText renders without error less than a dollar 1`] = `
>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {
"fontSize": 106,
},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.2}
style={
{
......@@ -168,7 +168,9 @@ exports[`PriceText renders without error less than a dollar 1`] = `
/>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {
"color": "#222222",
......@@ -176,8 +178,6 @@ exports[`PriceText renders without error less than a dollar 1`] = `
},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.2}
style={
{
......@@ -246,13 +246,13 @@ exports[`RelativeChangeText renders loading state 1`] = `
>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.4}
style={
{
......@@ -269,12 +269,12 @@ exports[`RelativeChangeText renders loading state 1`] = `
underlineColorAndroid="transparent"
/>
<Text
animatedStyle={
collapsable={false}
jestAnimatedStyle={
{
"value": {},
}
}
collapsable={false}
style={
{
"fontFamily": "Basel-Book",
......@@ -325,7 +325,12 @@ exports[`RelativeChangeText renders without error 1`] = `
>
<RNSVGSvgView
align="xMidYMid"
animatedStyle={
bbHeight="16"
bbWidth="16"
collapsable={false}
fill="none"
focusable={false}
jestAnimatedStyle={
{
"value": {
"color": "#FF5F52",
......@@ -337,11 +342,6 @@ exports[`RelativeChangeText renders without error 1`] = `
},
}
}
bbHeight="16"
bbWidth="16"
collapsable={false}
fill="none"
focusable={false}
meetOrSlice={0}
minX={0}
minY={0}
......@@ -411,15 +411,15 @@ exports[`RelativeChangeText renders without error 1`] = `
</RNSVGSvgView>
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {
"color": "#FF5F52",
},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.4}
style={
{
......
......@@ -74,9 +74,11 @@ describe(useTokenPriceHistory, () => {
expect(result.current.error).toBe(false)
})
expect(result.current.data?.spot).toEqual({
value: { value: market.price?.value },
relativeChange: { value: market.pricePercentChange?.value },
await waitFor(() => {
expect(result.current.data?.spot).toEqual({
value: expect.objectContaining({ value: market.price?.value }),
relativeChange: expect.objectContaining({ value: market.pricePercentChange?.value }),
})
})
})
......@@ -246,8 +248,10 @@ describe(useTokenPriceHistory, () => {
await waitFor(() => {
expect(result.current.data?.spot).toEqual({
value: { value: dayTokenProject.markets[0]?.price.value },
relativeChange: { value: dayTokenProject.markets[0]?.pricePercentChange24h.value },
value: expect.objectContaining({ value: dayTokenProject.markets[0]?.price.value }),
relativeChange: expect.objectContaining({
value: dayTokenProject.markets[0]?.pricePercentChange24h.value,
}),
})
})
})
......@@ -280,8 +284,10 @@ describe(useTokenPriceHistory, () => {
)
await waitFor(() => {
expect(result.current.data?.spot).toEqual({
value: { value: yearTokenProject.markets[0]?.price?.value },
relativeChange: { value: yearTokenProject.markets[0]?.pricePercentChange24h?.value },
value: expect.objectContaining({ value: yearTokenProject.markets[0]?.price?.value }),
relativeChange: expect.objectContaining({
value: yearTokenProject.markets[0]?.pricePercentChange24h?.value,
}),
})
})
})
......@@ -335,8 +341,10 @@ describe(useTokenPriceHistory, () => {
expect(result.current.data).toEqual({
priceHistory: formatPriceHistory(dayPriceHistory),
spot: {
value: { value: dayTokenProject.markets[0]?.price.value },
relativeChange: { value: dayTokenProject.markets[0]?.pricePercentChange24h.value },
value: expect.objectContaining({ value: dayTokenProject.markets[0]?.price.value }),
relativeChange: expect.objectContaining({
value: dayTokenProject.markets[0]?.pricePercentChange24h.value,
}),
},
})
})
......@@ -350,10 +358,10 @@ describe(useTokenPriceHistory, () => {
expect(result.current.data).toEqual({
priceHistory: formatPriceHistory(weekPriceHistory),
spot: {
value: { value: weekTokenProject.markets[0]?.price?.value },
relativeChange: {
value: expect.objectContaining({ value: weekTokenProject.markets[0]?.price?.value }),
relativeChange: expect.objectContaining({
value: weekTokenProject.markets[0]?.pricePercentChange24h?.value,
},
}),
},
})
})
......
import { maxBy } from 'lodash'
import { Dispatch, SetStateAction, useCallback, useMemo, useRef, useState } from 'react'
import { SharedValue } from 'react-native-reanimated'
import { SharedValue, useDerivedValue } from 'react-native-reanimated'
import { TLineChartData } from 'react-native-wagmi-charts'
import {
HistoryDuration,
......@@ -75,15 +75,18 @@ export function useTokenPriceHistory(
const pricePercentChange24h =
offChainData?.pricePercentChange24h?.value ?? onChainData?.pricePercentChange24h?.value ?? 0
const spotValue = useDerivedValue(() => price ?? 0)
const spotRelativeChange = useDerivedValue(() => pricePercentChange24h)
const spot = useMemo(
() =>
price !== undefined
? {
value: { value: price },
relativeChange: { value: pricePercentChange24h },
value: spotValue,
relativeChange: spotRelativeChange,
}
: undefined,
[price, pricePercentChange24h]
[price, spotValue, spotRelativeChange]
)
const formattedPriceHistory = useMemo(() => {
......@@ -94,6 +97,14 @@ export function useTokenPriceHistory(
return formatted
}, [priceHistory])
const data = useMemo(
() => ({
priceHistory: formattedPriceHistory,
spot,
}),
[formattedPriceHistory, spot]
)
const numberOfDigits = useMemo(() => {
const maxPriceInHistory = maxBy(priceHistory, 'value')?.value
// If there is neither max price in history nor current price, return last number of digits
......@@ -119,10 +130,7 @@ export function useTokenPriceHistory(
return useMemo(
() => ({
data: {
priceHistory: formattedPriceHistory,
spot,
},
data,
loading: isNonPollingRequestInFlight(networkStatus),
error: isError(networkStatus, !!priceData),
refetch: retry,
......@@ -131,15 +139,6 @@ export function useTokenPriceHistory(
numberOfDigits,
onCompleted,
}),
[
duration,
formattedPriceHistory,
networkStatus,
priceData,
retry,
spot,
onCompleted,
numberOfDigits,
]
[data, duration, networkStatus, priceData, retry, onCompleted, numberOfDigits]
)
}
......@@ -4,8 +4,8 @@ import { toIncludeSameMembers } from 'jest-extended'
import { act } from 'react-test-renderer'
import { MobileState } from 'src/app/reducer'
import { renderHookWithProviders } from 'src/test/render'
import { ChainId } from 'uniswap/src/types/chains'
import { useRecipients } from 'wallet/src/components/RecipientSearch/hooks'
import { ChainId } from 'wallet/src/constants/chains'
import { SearchableRecipient } from 'wallet/src/features/address/types'
import { TransactionStateMap } from 'wallet/src/features/transactions/slice'
import { TransactionStatus } from 'wallet/src/features/transactions/types'
......
......@@ -3,20 +3,20 @@ import { useTranslation } from 'react-i18next'
import { useAnimatedStyle, withTiming } from 'react-native-reanimated'
import { useAppDispatch, useAppSelector } from 'src/app/hooks'
import { navigate } from 'src/app/navigation/rootNavigation'
import { Delay } from 'src/components/layout/Delayed'
import { AssociatedAccountsList } from 'src/components/RemoveWallet/AssociatedAccountsList'
import { RemoveLastMnemonicWalletFooter } from 'src/components/RemoveWallet/RemoveLastMnemonicWalletFooter'
import { RemoveWalletStep, useModalContent } from 'src/components/RemoveWallet/useModalContent'
import { navigateToOnboardingImportMethod } from 'src/components/RemoveWallet/utils'
import { Delay } from 'src/components/layout/Delayed'
import { useBiometricAppSettings, useBiometricPrompt } from 'src/features/biometrics/hooks'
import { closeModal } from 'src/features/modals/modalSlice'
import { selectModalState } from 'src/features/modals/selectModalState'
import { OnboardingScreens, Screens } from 'src/screens/Screens'
import { AnimatedFlex, Button, ColorTokens, Flex, Text, ThemeKeys, useSporeColors } from 'ui/src'
import { iconSizes, opacify } from 'ui/src/theme'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { SpinningLoader } from 'wallet/src/components/loading/SpinningLoader'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import {
EditAccountAction,
editAccountActions,
......
import { CommonActions } from '@react-navigation/core'
import { dispatchNavigationAction } from 'src/app/navigation/rootNavigation'
import { OnboardingScreens, Screens } from 'src/screens/Screens'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
// This fast-forwards user to the same app state as if
// they have pressed "Get Started" on Landing and should now see import method view
......
......@@ -7,8 +7,8 @@ import { OnboardingScreens, Screens } from 'src/screens/Screens'
import { Button, Flex, Text, useSporeColors } from 'ui/src'
import LockIcon from 'ui/src/assets/icons/lock.svg'
import { iconSizes, opacify } from 'ui/src/theme'
import { ImportType, OnboardingEntryPoint } from 'uniswap/src/types/onboarding'
import { BottomSheetModal } from 'wallet/src/components/modals/BottomSheetModal'
import { ImportType, OnboardingEntryPoint } from 'wallet/src/features/onboarding/types'
import { ElementName, ModalName } from 'wallet/src/telemetry/constants'
export function RestoreWalletModal(): JSX.Element | null {
......
......@@ -178,7 +178,7 @@ export const TokenBalanceListInner = forwardRef<
const getItemLayout = useCallback(
(
_: Maybe<TokenBalanceListRow[]>,
_: Maybe<ArrayLike<string>>,
index: number
): { length: number; offset: number; index: number } => ({
length: ESTIMATED_TOKEN_ITEM_HEIGHT,
......
......@@ -6,7 +6,8 @@ import { Flex, Text } from 'ui/src'
import GlobeIcon from 'ui/src/assets/icons/globe-filled.svg'
import TwitterIcon from 'ui/src/assets/icons/x-twitter.svg'
import { TokenDetailsScreenQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { ChainId } from 'uniswap/src/types/chains'
import { CHAIN_INFO } from 'wallet/src/constants/chains'
import { ElementName } from 'wallet/src/telemetry/constants'
import {
currencyIdToAddress,
......
......@@ -4,11 +4,11 @@ import { useTranslation } from 'react-i18next'
import { ListRenderItemInfo } from 'react-native'
import { FiatOnRampCurrency } from 'src/features/fiatOnRamp/types'
import { Flex, Inset, Loader } from 'ui/src'
import { ChainId } from 'uniswap/src/types/chains'
import { CurrencyId } from 'uniswap/src/types/currency'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { TokenOptionItem } from 'wallet/src/components/TokenSelector/TokenOptionItem'
import { useBottomSheetFocusHook } from 'wallet/src/components/modals/hooks'
import { ChainId } from 'wallet/src/constants/chains'
interface Props {
onSelectCurrency: (currency: FiatOnRampCurrency) => void
......
......@@ -22,6 +22,7 @@ function mockFn(module: any, func: string, returnValue: any): jest.SpyInstance<a
}
jest.mock('react-native/Libraries/Utilities/useColorScheme')
jest.mock('wallet/src/features/gating/userPropertyHooks')
const address1 = '0x168fA52Da8A45cEb01318E72B299b2d6A17167BF'
const address2 = '0x168fA52Da8A45cEb01318E72B299b2d6A17167BD'
......
......@@ -13,6 +13,7 @@ import { useIsDarkMode } from 'ui/src'
import { isAndroid } from 'uniswap/src/utils/platform'
import { analytics } from 'utilities/src/telemetry/analytics/analytics'
import { useAppFiatCurrency } from 'wallet/src/features/fiatCurrency/hooks'
import { useGatingUserPropertyUsernames } from 'wallet/src/features/gating/userPropertyHooks'
import { useCurrentLanguageInfo } from 'wallet/src/features/language/hooks'
import { BackupType } from 'wallet/src/features/wallet/accounts/types'
import {
......@@ -41,6 +42,8 @@ export function TraceUserProperties(): null {
// Effects must check this and ensure they are setting properties for when analytics is reenabled
const allowAnalytics = useAppSelector(selectAllowAnalytics)
useGatingUserPropertyUsernames()
useEffect(() => {
setUserProperty(UserPropertyName.AppVersion, getFullAppVersion())
if (isAndroid) {
......
......@@ -3,18 +3,18 @@ import { Transaction, TransactionDescription } from 'no-yolo-signatures'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView } from 'react-native-gesture-handler'
import { LinkButton } from 'src/components/buttons/LinkButton'
import { SpendingDetails } from 'src/components/WalletConnect/RequestModal/SpendingDetails'
import { LinkButton } from 'src/components/buttons/LinkButton'
import {
isTransactionRequest,
SignRequest,
WalletConnectRequest,
isTransactionRequest,
} from 'src/features/walletConnect/walletConnectSlice'
import { useNoYoloParser } from 'src/utils/useNoYoloParser'
import { Flex, Text, useSporeColors } from 'ui/src'
import { iconSizes, TextVariantTokens } from 'ui/src/theme'
import { TextVariantTokens, iconSizes } from 'ui/src/theme'
import { ChainId } from 'uniswap/src/types/chains'
import { logger } from 'utilities/src/logger/logger'
import { ChainId } from 'wallet/src/constants/chains'
import { toSupportedChainId } from 'wallet/src/features/chains/utils'
import { useENS } from 'wallet/src/features/ens/useENS'
import { EthMethod, EthTransaction } from 'wallet/src/features/walletConnect/types'
......
......@@ -2,9 +2,9 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { Flex, Text } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { ChainId } from 'uniswap/src/types/chains'
import { NumberType } from 'utilities/src/format/types'
import { CurrencyLogo } from 'wallet/src/components/CurrencyLogo/CurrencyLogo'
import { ChainId } from 'wallet/src/constants/chains'
import { useUSDValue } from 'wallet/src/features/gas/hooks'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { useNativeCurrencyInfo } from 'wallet/src/features/tokens/useCurrencyInfo'
......
import { useMemo } from 'react'
import { ChainId } from 'wallet/src/constants/chains'
import { ChainId } from 'uniswap/src/types/chains'
import { GasFeeResult } from 'wallet/src/features/gas/types'
import { useOnChainNativeCurrencyBalance } from 'wallet/src/features/portfolio/api'
import { NativeCurrency } from 'wallet/src/features/tokens/NativeCurrency'
......
......@@ -23,10 +23,10 @@ import {
import { Flex, Text, TouchableArea, useSporeColors } from 'ui/src'
import { Check, X } from 'ui/src/components/icons'
import { iconSizes } from 'ui/src/theme'
import { ChainId } from 'uniswap/src/types/chains'
import { ONE_SECOND_MS } from 'utilities/src/time/time'
import { AccountDetails } from 'wallet/src/components/accounts/AccountDetails'
import { NetworkLogos } from 'wallet/src/components/network/NetworkLogos'
import { ChainId } from 'wallet/src/constants/chains'
import { pushNotification } from 'wallet/src/features/notifications/slice'
import { AppNotificationType } from 'wallet/src/features/notifications/types'
import {
......
......@@ -3,9 +3,10 @@ import { useTranslation } from 'react-i18next'
import { Flex, Separator, Text, useSporeColors } from 'ui/src'
import Check from 'ui/src/assets/icons/check.svg'
import { iconSizes } from 'ui/src/theme'
import { ChainId } from 'uniswap/src/types/chains'
import { NetworkLogo } from 'wallet/src/components/CurrencyLogo/NetworkLogo'
import { ActionSheetModal } from 'wallet/src/components/modals/ActionSheetModal'
import { ALL_SUPPORTED_CHAIN_IDS, CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { ALL_SUPPORTED_CHAIN_IDS, CHAIN_INFO } from 'wallet/src/constants/chains'
import { ElementName, ModalName } from 'wallet/src/telemetry/constants'
type Props = {
......
......@@ -19,8 +19,6 @@ const { resolvers } = queryResolvers({
})
describe(AccountList, () => {
afterEach(cleanup)
it('renders without error', async () => {
const tree = render(<AccountList accounts={[ACCOUNT]} onPress={jest.fn()} />, { resolvers })
......@@ -103,7 +101,7 @@ describe(AccountList, () => {
})
expect(screen.queryByText('View only wallets')).toBeFalsy()
// cleanup()
cleanup()
})
})
})
......@@ -477,7 +477,9 @@ exports[`AccountHeader renders correctly 1`] = `
Test Account
</Text>
<View
animatedStyle={
collapsable={false}
forwardedRef={[Function]}
jestAnimatedStyle={
{
"value": {
"transform": [
......@@ -488,8 +490,6 @@ exports[`AccountHeader renders correctly 1`] = `
},
}
}
collapsable={false}
forwardedRef={[Function]}
style={
{
"flexDirection": "row",
......@@ -512,7 +512,9 @@ exports[`AccountHeader renders correctly 1`] = `
>
<Text
allowFontScaling={true}
animatedStyle={
collapsable={false}
forwardedRef={[Function]}
jestAnimatedStyle={
{
"value": {
"color": "#CECECE",
......@@ -521,8 +523,6 @@ exports[`AccountHeader renders correctly 1`] = `
},
}
}
collapsable={false}
forwardedRef={[Function]}
maxFontSizeMultiplier={1.4}
style={
{
......
......@@ -26,16 +26,16 @@ exports[`AccountList renders without error 1`] = `
</React.Fragment>
</React.Fragment>
}
animatedStyle={
{
"value": {},
}
}
bounces={false}
collapsable={false}
data={[]}
getItem={[Function]}
getItemCount={[Function]}
jestAnimatedStyle={
{
"value": {},
}
}
keyExtractor={[Function]}
keyboardShouldPersistTaps="always"
onContentSizeChange={[Function]}
......@@ -57,6 +57,7 @@ exports[`AccountList renders without error 1`] = `
>
<View>
<View
collapsable={false}
onLayout={[Function]}
>
<View
......
......@@ -22,9 +22,9 @@ import {
ExploreTokensTabQuery,
useExploreTokensTabQuery,
} from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { ChainId } from 'uniswap/src/types/chains'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { getWrappedNativeAddress } from 'wallet/src/constants/addresses'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
import { usePersistedError } from 'wallet/src/features/dataApi/utils'
......
......@@ -45,7 +45,7 @@ const touchableId = `token-box-${favoriteToken.symbol}`
const defaultProps: FavoriteTokenCardProps = {
currencyId: SAMPLE_CURRENCY_ID_1,
isTouched: makeMutable(false),
pressProgress: makeMutable(0),
dragActivationProgress: makeMutable(0),
setIsEditing: jest.fn(),
isEditing: false,
......@@ -113,7 +113,9 @@ describe('FavoriteTokenCard', () => {
const removeButton = await findByTestId('explore/remove-button')
expect(removeButton).toHaveAnimatedStyle({ opacity: 0 })
await waitFor(() => {
expect(removeButton).toHaveAnimatedStyle({ opacity: 0 })
})
})
})
......@@ -125,7 +127,9 @@ describe('FavoriteTokenCard', () => {
const removeButton = await findByTestId('explore/remove-button')
expect(removeButton).toHaveAnimatedStyle({ opacity: 1 })
await waitFor(() => {
expect(removeButton).toHaveAnimatedStyle({ opacity: 1 })
})
})
it('dispatches removeFavoriteToken action when remove button is pressed', async () => {
......
......@@ -12,11 +12,11 @@ import { usePollOnFocusOnly } from 'src/utils/hooks'
import { AnimatedFlex, AnimatedTouchableArea, Flex, ImpactFeedbackStyle, Text } from 'ui/src'
import { borderRadii, imageSizes } from 'ui/src/theme'
import { useFavoriteTokenCardQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import { ChainId } from 'uniswap/src/types/chains'
import { NumberType } from 'utilities/src/format/types'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import { RelativeChange } from 'wallet/src/components/text/RelativeChange'
import { ChainId } from 'wallet/src/constants/chains'
import { PollingInterval } from 'wallet/src/constants/misc'
import { isNonPollingRequestInFlight } from 'wallet/src/data/utils'
import { fromGraphQLChain } from 'wallet/src/features/chains/utils'
......@@ -30,16 +30,16 @@ export const FAVORITE_TOKEN_CARD_LOADER_HEIGHT = 114
export type FavoriteTokenCardProps = {
currencyId: string
isEditing?: boolean
isTouched: SharedValue<boolean>
pressProgress: SharedValue<number>
dragActivationProgress: SharedValue<number>
isEditing?: boolean
setIsEditing: (update: boolean) => void
} & ViewProps
function FavoriteTokenCard({
currencyId,
isEditing,
isTouched,
pressProgress,
dragActivationProgress,
setIsEditing,
...rest
......@@ -93,7 +93,7 @@ function FavoriteTokenCard({
tokenDetailsNavigation.navigate(currencyId)
}
const animatedDragStyle = useAnimatedCardDragStyle(isTouched, dragActivationProgress)
const animatedDragStyle = useAnimatedCardDragStyle(pressProgress, dragActivationProgress)
if (isNonPollingRequestInFlight(networkStatus)) {
return <Loader.Favorite height={FAVORITE_TOKEN_CARD_LOADER_HEIGHT} />
......
......@@ -52,13 +52,13 @@ export function FavoriteTokensGrid({
)
const renderItem = useCallback<SortableGridRenderItem<string>>(
({ item: currencyId, isTouched, dragActivationProgress }): JSX.Element => (
({ item: currencyId, pressProgress, dragActivationProgress }): JSX.Element => (
<FavoriteTokenCard
key={currencyId}
currencyId={currencyId}
dragActivationProgress={dragActivationProgress}
isEditing={isEditing}
isTouched={isTouched}
pressProgress={pressProgress}
setIsEditing={setIsEditing}
/>
),
......@@ -83,17 +83,18 @@ export function FavoriteTokensGrid({
<SortableGrid
{...rest}
activeItemOpacity={1}
animateContainerHeight={false}
data={favoriteCurrencyIds}
editable={isEditing}
numColumns={NUM_COLUMNS}
renderItem={renderItem}
onChange={handleOrderChange}
onDragEnd={(): void => {
isTokenDragged.value = false
}}
onDragStart={(): void => {
isTokenDragged.value = true
}}
onDrop={(): void => {
isTokenDragged.value = false
}}
/>
)}
</AnimatedFlex>
......
......@@ -2,7 +2,7 @@ import { makeMutable } from 'react-native-reanimated'
import configureMockStore from 'redux-mock-store'
import { Screens } from 'src/screens/Screens'
import { preloadedMobileState } from 'src/test/fixtures'
import { fireEvent, render } from 'src/test/test-utils'
import { fireEvent, render, waitFor } from 'src/test/test-utils'
import * as unitagHooks from 'uniswap/src/features/unitags/hooks'
import * as ensHooks from 'wallet/src/features/ens/api'
import {
......@@ -31,7 +31,7 @@ const mockStore = configureMockStore()
const defaultProps: FavoriteWalletCardProps = {
address: SAMPLE_SEED_ADDRESS_1,
isTouched: makeMutable(false),
pressProgress: makeMutable(0),
isEditing: false,
dragActivationProgress: makeMutable(0),
setIsEditing: jest.fn(),
......@@ -108,22 +108,26 @@ describe('FavoriteWalletCard', () => {
})
})
it('does not display the remove button', () => {
it('does not display the remove button', async () => {
const { getByTestId } = render(<FavoriteWalletCard {...defaultProps} />)
const removeButton = getByTestId('explore/remove-button')
expect(removeButton).toHaveAnimatedStyle({ opacity: 0 })
await waitFor(() => {
expect(removeButton).toHaveAnimatedStyle({ opacity: 0 })
})
})
})
describe('when editing', () => {
it('displays the remove button', () => {
it('displays the remove button', async () => {
const { getByTestId } = render(<FavoriteWalletCard {...defaultProps} isEditing />)
const removeButton = getByTestId('explore/remove-button')
expect(removeButton).toHaveAnimatedStyle({ opacity: 1 })
await waitFor(() => {
expect(removeButton).toHaveAnimatedStyle({ opacity: 1 })
})
})
it('dispatches removeWatchedAddress when remove button is pressed', () => {
......
......@@ -20,7 +20,7 @@ import { DisplayNameType } from 'wallet/src/features/wallet/types'
export type FavoriteWalletCardProps = {
address: Address
isEditing?: boolean
isTouched: SharedValue<boolean>
pressProgress: SharedValue<number>
dragActivationProgress: SharedValue<number>
setIsEditing: (update: boolean) => void
} & ViewProps
......@@ -28,7 +28,7 @@ export type FavoriteWalletCardProps = {
function FavoriteWalletCard({
address,
isEditing,
isTouched,
pressProgress,
dragActivationProgress,
setIsEditing,
...rest
......@@ -56,7 +56,7 @@ function FavoriteWalletCard({
]
}, [t])
const animatedDragStyle = useAnimatedCardDragStyle(isTouched, dragActivationProgress)
const animatedDragStyle = useAnimatedCardDragStyle(pressProgress, dragActivationProgress)
return (
<AnimatedFlex style={animatedDragStyle}>
......
......@@ -51,13 +51,13 @@ export function FavoriteWalletsGrid({
)
const renderItem = useCallback<SortableGridRenderItem<string>>(
({ item: address, isTouched, dragActivationProgress }): JSX.Element => (
({ item: address, pressProgress, dragActivationProgress }): JSX.Element => (
<FavoriteWalletCard
key={address}
address={address}
dragActivationProgress={dragActivationProgress}
isEditing={isEditing}
isTouched={isTouched}
pressProgress={pressProgress}
setIsEditing={setIsEditing}
/>
),
......@@ -82,17 +82,18 @@ export function FavoriteWalletsGrid({
<SortableGrid
{...rest}
activeItemOpacity={1}
animateContainerHeight={false}
data={watchedWalletsList}
editable={isEditing}
numColumns={NUM_COLUMNS}
renderItem={renderItem}
onChange={handleOrderChange}
onDragEnd={(): void => {
isTokenDragged.value = false
}}
onDragStart={(): void => {
isTokenDragged.value = true
}}
onDrop={(): void => {
isTokenDragged.value = false
}}
/>
)}
</AnimatedFlex>
......
......@@ -8,10 +8,10 @@ import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { MobileEventName } from 'src/features/telemetry/constants'
import { disableOnPress } from 'src/utils/disableOnPress'
import { AnimatedFlex, Flex, ImpactFeedbackStyle, Text, TouchableArea } from 'ui/src'
import { ChainId } from 'uniswap/src/types/chains'
import { NumberType } from 'utilities/src/format/types'
import { TokenLogo } from 'wallet/src/components/CurrencyLogo/TokenLogo'
import { RelativeChange } from 'wallet/src/components/text/RelativeChange'
import { ChainId } from 'wallet/src/constants/chains'
import { useLocalizationContext } from 'wallet/src/features/language/LocalizationContext'
import { TokenMetadataDisplayType } from 'wallet/src/features/wallet/types'
import { SectionName } from 'wallet/src/telemetry/constants'
......
......@@ -2,14 +2,14 @@
exports[`FavoriteWalletCard renders without error 1`] = `
<View
animatedStyle={
collapsable={false}
jestAnimatedStyle={
{
"value": {
"opacity": 1,
},
}
}
collapsable={false}
style={
{
"flexDirection": "column",
......@@ -336,18 +336,18 @@ exports[`FavoriteWalletCard renders without error 1`] = `
</View>
</View>
<View
animatedStyle={
cancelable={true}
collapsable={false}
disabled={false}
focusable={true}
hitSlop={[Function]}
jestAnimatedStyle={
{
"value": {
"opacity": 0,
},
}
}
cancelable={true}
collapsable={false}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
......
......@@ -2,18 +2,18 @@
exports[`RemoveButton renders without error 1`] = `
<View
animatedStyle={
cancelable={true}
collapsable={false}
disabled={false}
focusable={true}
hitSlop={[Function]}
jestAnimatedStyle={
{
"value": {
"opacity": 1,
},
}
}
cancelable={true}
collapsable={false}
disabled={false}
focusable={true}
hitSlop={[Function]}
minPressDuration={0}
onBlur={[Function]}
onFocus={[Function]}
......
......@@ -33,12 +33,12 @@ exports[`TokenItem renders without error 1`] = `
testID="token-item-cum"
>
<View
animatedStyle={
collapsable={false}
jestAnimatedStyle={
{
"value": {},
}
}
collapsable={false}
style={
{
"flexDirection": "row",
......@@ -99,6 +99,7 @@ exports[`TokenItem renders without error 1`] = `
}
>
<Image
resizeMode="contain"
source={
{
"uri": "https://loremflickr.com/640/480",
......@@ -106,9 +107,6 @@ exports[`TokenItem renders without error 1`] = `
}
style={
[
{
"resizeMode": "contain",
},
{
"backgroundColor": "#2222220D",
"borderColor": "#2222220D",
......
import { SharedEventName } from '@uniswap/analytics-events'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { NativeSyntheticEvent, ViewStyle } from 'react-native'
import { NativeSyntheticEvent } from 'react-native'
import { ContextMenuAction, ContextMenuOnPressNativeEvent } from 'react-native-context-menu-view'
import {
AnimateStyle,
SharedValue,
interpolate,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import { SharedValue, StyleProps, interpolate, useAnimatedStyle } from 'react-native-reanimated'
import { useSelectHasTokenFavorited, useToggleFavoriteCallback } from 'src/features/favorites/hooks'
import { openModal } from 'src/features/modals/modalSlice'
import { sendMobileAnalyticsEvent } from 'src/features/telemetry'
import { ChainId } from 'uniswap/src/types/chains'
import { CurrencyId } from 'uniswap/src/types/currency'
import { ScannerModalState } from 'wallet/src/components/QRCodeScanner/constants'
import { ChainId } from 'wallet/src/constants/chains'
import { useWalletNavigation } from 'wallet/src/contexts/WalletNavigationContext'
import { AssetType } from 'wallet/src/entities/assets'
import {
......@@ -147,42 +140,13 @@ export function useExploreTokenContextMenu({
}
export function useAnimatedCardDragStyle(
isTouched: SharedValue<boolean>,
pressProgress: SharedValue<number>,
dragActivationProgress: SharedValue<number>
): AnimateStyle<ViewStyle> {
const wasTouched = useSharedValue(false)
const dragAnimationProgress = useSharedValue(0)
useAnimatedReaction(
() => dragActivationProgress.value,
(activationProgress, prev) => {
const prevActivationProgress = prev ?? 0
// If the activation progress is increasing (the user is touching one of the cards)
if (activationProgress > prevActivationProgress) {
if (isTouched.value) {
// If the current card is the one being touched, reset the animation progress
wasTouched.value = true
dragAnimationProgress.value = 0
} else {
// Otherwise, animate the card
wasTouched.value = false
dragAnimationProgress.value = activationProgress
}
}
// If the activation progress is decreasing (the user is no longer touching one of the cards)
else {
if (isTouched.value || wasTouched.value) {
// If the current card is the one that was being touched, reset the animation progress
dragAnimationProgress.value = 0
} else {
// Otherwise, animate the card
dragAnimationProgress.value = activationProgress
}
}
}
)
): StyleProps {
return useAnimatedStyle(() => ({
opacity: interpolate(dragAnimationProgress.value, [0, 1], [1, 0.5]),
opacity:
pressProgress.value >= dragActivationProgress.value
? 1
: interpolate(dragActivationProgress.value, [0, 1], [1, 0.5]),
}))
}
......@@ -20,9 +20,10 @@ import { AnimatedFlex, Flex, Text } from 'ui/src'
import { Coin, Gallery, Person } from 'ui/src/components/icons'
import { useExploreSearchQuery } from 'uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks'
import i18n from 'uniswap/src/i18n/i18n'
import { ChainId } from 'uniswap/src/types/chains'
import { logger } from 'utilities/src/logger/logger'
import { BaseCard } from 'wallet/src/components/BaseCard/BaseCard'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { CHAIN_INFO } from 'wallet/src/constants/chains'
import { SearchContext } from 'wallet/src/features/search/SearchContext'
import {
NFTCollectionSearchResult,
......
import { useMemo } from 'react'
import { useUnitagByAddress, useUnitagByName } from 'uniswap/src/features/unitags/hooks'
import { ChainId } from 'wallet/src/constants/chains'
import { ChainId } from 'uniswap/src/types/chains'
import { useENS } from 'wallet/src/features/ens/useENS'
import { SearchResultType, WalletSearchResult } from 'wallet/src/features/search/SearchResult'
import { useIsSmartContractAddress } from 'wallet/src/features/transactions/transfer/hooks/useIsSmartContractAddress'
......
......@@ -3,8 +3,8 @@ import { useAppDispatch } from 'src/app/hooks'
import { getBlockExplorerIcon } from 'src/components/icons/BlockExplorerIcon'
import { Flex, ImpactFeedbackStyle, Text, TouchableArea, useSporeColors } from 'ui/src'
import { iconSizes } from 'ui/src/theme'
import { ChainId } from 'uniswap/src/types/chains'
import { Arrow } from 'wallet/src/components/icons/Arrow'
import { ChainId } from 'wallet/src/constants/chains'
import { EtherscanSearchResult } from 'wallet/src/features/search/SearchResult'
import { addToSearchHistory } from 'wallet/src/features/search/searchHistorySlice'
import { ElementName } from 'wallet/src/telemetry/constants'
......
......@@ -24,8 +24,6 @@ export function FiatOnRampCtaButton({
const continueText = eligible ? continueButtonText : t('fiatOnRamp.error.unsupported')
return (
<Button
// TODO: remove when https://linear.app/uniswap/issue/MOB-3182/disabled-ui-on-enabled-button is fixed
key={Math.random()}
color={buttonAvailable ? '$white' : '$neutral2'}
disabled={disabled}
icon={
......
......@@ -2,7 +2,8 @@ import React from 'react'
import { SvgProps } from 'react-native-svg'
import { useIsDarkMode } from 'ui/src'
import { IconSizeTokens } from 'ui/src/theme'
import { CHAIN_INFO, ChainId } from 'wallet/src/constants/chains'
import { ChainId } from 'uniswap/src/types/chains'
import { CHAIN_INFO } from 'wallet/src/constants/chains'
type IconComponentProps = SvgProps & { size?: IconSizeTokens | number }
......
import { PropsWithChildren } from 'react'
import { StyleSheet } from 'react-native'
import Animated, {
interpolate,
interpolateColor,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { colors, opacify } from 'ui/src/theme'
import { useSortableGridContext } from './SortableGridProvider'
type ActiveItemDecorationProps = PropsWithChildren<{
renderIndex: number
}>
export default function ActiveItemDecoration({
renderIndex,
children,
}: ActiveItemDecorationProps): JSX.Element {
const {
touchedIndex,
activeItemScale,
previousActiveIndex,
activeItemOpacity,
activeItemShadowOpacity,
dragActivationProgress,
} = useSortableGridContext()
const pressProgress = useSharedValue(0)
useAnimatedReaction(
() => ({
isTouched: touchedIndex.value === renderIndex,
wasTouched: previousActiveIndex.value === renderIndex,
progress: dragActivationProgress.value,
}),
({ isTouched, wasTouched, progress }) => {
if (isTouched) {
// If the item is currently touched, we want to animate the press progress
// (change the decoration) based on the drag activation progress
pressProgress.value = Math.max(pressProgress.value, progress)
} else if (wasTouched) {
// If the item was touched (the user released the finger) and the item
// was previously touched, we want to animate it based on the decreasing
// press progress
pressProgress.value = Math.min(pressProgress.value, progress)
} else {
// For all other cases, we want to ensure that the press progress is reset
// and all non-touched items are not decorated
pressProgress.value = withTiming(0)
}
}
)
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(pressProgress.value, [0, 1], [1, activeItemScale.value]),
},
],
opacity: interpolate(pressProgress.value, [0, 1], [1, activeItemOpacity.value]),
shadowColor: interpolateColor(
pressProgress.value,
[0, 1],
['transparent', opacify(100 * activeItemShadowOpacity.value, colors.black)]
),
}))
return <Animated.View style={[styles.shadow, animatedStyle]}>{children}</Animated.View>
}
const styles = StyleSheet.create({
shadow: {
borderRadius: 0,
elevation: 40,
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.25,
shadowRadius: 5,
},
})
import { PropsWithChildren, useMemo } from 'react'
import type {
AutoScrollProviderProps,
DragContextProviderProps,
LayoutContextProviderProps,
} from './contexts'
import { AutoScrollProvider, DragContextProvider, LayoutContextProvider } from './contexts'
type SortableGridProviderProps<I> = PropsWithChildren<
Omit<
LayoutContextProviderProps & DragContextProviderProps<I> & AutoScrollProviderProps,
'itemKeys'
>
>
export function SortableGridProvider<I>({
children,
data,
numColumns,
editable,
hapticFeedback,
animateContainerHeight,
activeItemScale,
activeItemOpacity,
activeItemShadowOpacity,
scrollableRef,
visibleHeight,
scrollY,
onChange,
onDragStart,
onDrop,
keyExtractor,
}: SortableGridProviderProps<I>): JSX.Element {
const itemKeys = useMemo(() => data.map(keyExtractor), [data, keyExtractor])
const sharedProps = {
itemKeys,
numColumns,
}
return (
<LayoutContextProvider {...sharedProps} animateContainerHeight={animateContainerHeight}>
<DragContextProvider
{...sharedProps}
activeItemOpacity={activeItemOpacity}
activeItemScale={activeItemScale}
activeItemShadowOpacity={activeItemShadowOpacity}
data={data}
editable={editable}
hapticFeedback={hapticFeedback}
keyExtractor={keyExtractor}
onChange={onChange}
onDragStart={onDragStart}
onDrop={onDrop}>
<AutoScrollProvider
scrollY={scrollY}
scrollableRef={scrollableRef}
visibleHeight={visibleHeight}>
{children}
</AutoScrollProvider>
</DragContextProvider>
</LayoutContextProvider>
)
}
export const TIME_TO_ACTIVATE_PAN = 300
export const TOUCH_SLOP = 10
export const ITEM_ANIMATION_DURATION = 300
export const TIME_TO_ACTIVATE_PAN = 500
export const ACTIVATE_PAN_ANIMATION_DELAY = 250
export const AUTO_SCROLL_THRESHOLD = 50
export const OFFSET_EPS = 1
export * from './AutoScrollContextProvider'
export * from './DragContextProvider'
export * from './LayoutContextProvider'
import { LayoutAnimation, withTiming } from 'react-native-reanimated'
import { ITEM_ANIMATION_DURATION } from './constants'
export const GridItemExiting = (): LayoutAnimation => {
'worklet'
const animations = {
opacity: withTiming(0, {
duration: ITEM_ANIMATION_DURATION,
}),
transform: [
{
scale: withTiming(0.5, {
duration: ITEM_ANIMATION_DURATION,
}),
},
],
}
const initialValues = {
opacity: 1,
transform: [{ scale: 1 }],
}
return {
initialValues,
animations,
}
}
import { FlatList, ScrollView, View } from 'react-native'
import { SharedValue } from 'react-native-reanimated'
export type Require<T, K extends keyof T = keyof T> = Required<Pick<T, K>> & Omit<T, K>
export type ItemMeasurements = {
height: number
width: number
export type Vector = {
x: number
y: number
}
export type Dimensions = {
width: number
height: number
}
export type AutoScrollProps = {
scrollableRef: React.RefObject<FlatList | ScrollView>
visibleHeight: SharedValue<number>
......@@ -21,44 +22,32 @@ export type AutoScrollProps = {
containerRef?: React.RefObject<View>
}
export type SortableGridContextType = {
gridContainerRef: React.RefObject<View>
itemAtIndexMeasurements: SharedValue<ItemMeasurements[]>
dragActivationProgress: SharedValue<number>
activeIndex: number | null
previousActiveIndex: SharedValue<number | null>
activeTranslation: SharedValue<{ x: number; y: number }>
scrollOffsetDiff: SharedValue<number>
renderIndexToDisplayIndex: SharedValue<number[]>
setActiveIndex: (index: number | null) => void
onDragStart?: () => void
displayToRenderIndex: SharedValue<number[]>
activeItemScale: SharedValue<number>
visibleHeight: SharedValue<number>
activeItemOpacity: SharedValue<number>
activeItemShadowOpacity: SharedValue<number>
touchedIndex: SharedValue<number | null>
editable: boolean
containerStartOffset: SharedValue<number>
containerEndOffset: SharedValue<number>
export type ActiveItemDecorationSettings = {
activeItemScale: number
activeItemOpacity: number
activeItemShadowOpacity: number
}
export type SortableGridRenderItemInfo<I> = {
export type SortableGridChangeEvent<I> = {
data: I[]
fromIndex: number
toIndex: number
}
export type SortableGridDragStartEvent<I> = {
item: I
index: number
dragActivationProgress: SharedValue<number>
isTouched: SharedValue<boolean>
}
export type SortableGridRenderItem<I> = (info: SortableGridRenderItemInfo<I>) => JSX.Element
export type Vector = {
x: number
y: number
export type SortableGridDropEvent<I> = {
item: I
index: number
}
export type SortableGridChangeEvent<I> = {
data: I[]
fromIndex: number
toIndex: number
export type SortableGridRenderItemInfo<I> = {
item: I
pressProgress: SharedValue<number>
dragActivationProgress: SharedValue<number>
}
export type SortableGridRenderItem<I> = (info: SortableGridRenderItemInfo<I>) => JSX.Element
......@@ -7,18 +7,18 @@ import { renderWithProviders } from 'src/test/render'
describe(AnimatedText, () => {
it('renders without error', () => {
const tree = render(<AnimatedText text={{ value: 'Rendered' }} />)
const tree = render(<AnimatedText text={makeMutable('Rendered')} />)
expect(tree).toMatchInlineSnapshot(`
<TextInput
allowFontScaling={true}
animatedStyle={
collapsable={false}
editable={false}
jestAnimatedStyle={
{
"value": {},
}
}
collapsable={false}
editable={false}
maxFontSizeMultiplier={1.4}
style={
{
......
This diff is collapsed.
import { hasConsecutiveRecentSwapsSelector } from 'src/features/appRating/selectors'
import { ChainId } from 'uniswap/src/types/chains'
import { ONE_HOUR_MS, ONE_MINUTE_MS } from 'utilities/src/time/time'
import { ChainId } from 'wallet/src/constants/chains'
import {
TransactionDetails,
TransactionStatus,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment