diff --git a/package.json b/package.json
index 91f02735eb..39104f4101 100644
--- a/package.json
+++ b/package.json
@@ -55,11 +55,11 @@
"dotenv-cli": "^7.2.1",
"eslint": "^8.50.0",
"eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-react": "^7.37.2",
+ "eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unicorn": "^48.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
- "eslint-plugin-react-hooks": "^5.0.0",
- "eslint-plugin-react": "^7.37.2",
"is-ci-cli": "^2.2.0",
"jest": "^29.2.1",
"jest-environment-jsdom": "^29.7.0",
diff --git a/packages/mobile/app/(tabs)/assets.tsx b/packages/mobile/app/(tabs)/assets.tsx
index 21bcd7c07f..3293357831 100644
--- a/packages/mobile/app/(tabs)/assets.tsx
+++ b/packages/mobile/app/(tabs)/assets.tsx
@@ -2,7 +2,7 @@ import { Dec } from "@osmosis-labs/unit";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import { FlashList } from "@shopify/flash-list";
import { debounce } from "debounce";
-import { useRouter } from "expo-router";
+import { Link } from "expo-router";
import { useMemo, useState } from "react";
import {
ActivityIndicator,
@@ -96,7 +96,7 @@ export default function TabTwoScreen() {
);
const selectedDisplayOption = useMemo(() => {
return displayOptions.find((item) => item.key === displayOption);
- }, [items, displayOption]);
+ }, [displayOption]);
return (
{isLoading ? (
-
+
+
+
) : (
)}
- {isFetchingNextPage && }
+ {isFetchingNextPage && (
+
+
+
+ )}
);
@@ -180,112 +186,116 @@ const AssetItem = ({
}: {
asset: RouterOutputs["local"]["assets"]["getMarketAssets"]["items"][number];
}) => {
- const router = useRouter();
const { displayOption } = useDisplayOptionStore();
return (
-
- router.push({
- pathname: "/asset/[id]",
- params: {
- id: asset.coinMinimalDenom.replace(/\//g, "-"),
- coinDenom: asset.coinDenom,
- coinImageUrl: asset.coinImageUrl,
- },
- })
- }
- style={styles.assetItem}
+
-
- {asset.coinImageUrl?.endsWith(".svg") ? (
-
- ) : (
-
- )}
-
- {asset.coinDenom}
+
+
+ {asset.coinImageUrl?.endsWith(".svg") ? (
+
+ ) : (
+
+ )}
+
+ {asset.coinDenom}
+
-
-
- {displayOption.startsWith("price") && (
- <>
-
- {asset.currentPrice ? (
- <>
- {asset.currentPrice.symbol}
-
- >
- ) : (
- ""
- )}
-
- {displayOption === "price-24h" && (
-
- {asset.priceChange24h?.toString()}
+
+ {displayOption.startsWith("price") && (
+ <>
+
+ {asset.currentPrice ? (
+ <>
+ {asset.currentPrice.symbol}
+
+ >
+ ) : (
+ ""
+ )}
- )}
+ {displayOption === "price-24h" && (
+
+ {asset.priceChange24h?.toString()}
+
+ )}
- {displayOption === "price-7d" && (
-
- {asset.priceChange7d?.toString()}
-
- )}
+ {displayOption === "price-7d" && (
+
+ {asset.priceChange7d?.toString()}
+
+ )}
- {displayOption === "price-1h" && (
-
- {asset.priceChange1h?.toString()}
-
- )}
- >
- )}
+ {displayOption === "price-1h" && (
+
+ {asset.priceChange1h?.toString()}
+
+ )}
+ >
+ )}
- {displayOption === "volume" && (
- {asset.volume24h?.toString() ?? "-"}
- )}
+ {displayOption === "volume" && (
+
+ {asset.volume24h?.toString() ?? "-"}
+
+ )}
- {displayOption === "market-cap" && (
- {asset.marketCap?.toString() ?? "-"}
- )}
+ {displayOption === "market-cap" && (
+
+ {asset.marketCap?.toString() ?? "-"}
+
+ )}
- {displayOption === "favorite" && Favorite}
-
-
+ {displayOption === "favorite" && Favorite}
+
+
+
);
};
@@ -293,7 +303,6 @@ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0F172A",
- padding: 16,
},
header: {
fontSize: 24,
@@ -331,6 +340,7 @@ const styles = StyleSheet.create({
justifyContent: "space-between",
alignItems: "center",
marginBottom: 16,
+ paddingHorizontal: 24,
},
subheader: {
fontSize: 16,
@@ -352,6 +362,7 @@ const styles = StyleSheet.create({
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 12,
+ paddingHorizontal: 24,
},
assetLeft: {
flexDirection: "row",
@@ -401,7 +412,6 @@ const styles = StyleSheet.create({
contentContainer: {
backgroundColor: Colors.osmoverse[900],
flex: 1,
- paddingHorizontal: 24,
paddingTop: 32,
borderTopEndRadius: 32,
borderTopStartRadius: 32,
diff --git a/packages/mobile/app/asset/[id].tsx b/packages/mobile/app/asset/[coinMinimalDenom].tsx
similarity index 71%
rename from packages/mobile/app/asset/[id].tsx
rename to packages/mobile/app/asset/[coinMinimalDenom].tsx
index 67734896ba..3d35c4e426 100644
--- a/packages/mobile/app/asset/[id].tsx
+++ b/packages/mobile/app/asset/[coinMinimalDenom].tsx
@@ -6,25 +6,23 @@ import {
Image,
ScrollView,
StyleSheet,
- TouchableOpacity,
View,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
-import { AssetChart, AssetChartHeader } from "~/components/asset-chart";
-import { AssetDetails } from "~/components/asset-details";
-import { ChevronLeftIcon } from "~/components/icons/chevron-left";
+import { AssetChart, AssetChartHeader } from "~/components/asset/asset-chart";
+import { AssetDetails } from "~/components/asset/asset-details";
+import { RouteHeader } from "~/components/route-header";
import { Button } from "~/components/ui/button";
import { Text } from "~/components/ui/text";
-import { Colors } from "~/constants/theme-colors";
import { getChangeColor } from "~/utils/price";
import { api } from "~/utils/trpc";
const TRADE_BUTTON_HEIGHT = 100;
const AssetRoute = () => {
- const { id, coinDenom, coinImageUrl } = useLocalSearchParams<{
- id: string;
+ const { coinMinimalDenom, coinDenom, coinImageUrl } = useLocalSearchParams<{
+ coinMinimalDenom: string;
coinDenom: string;
coinImageUrl: string;
}>();
@@ -32,19 +30,13 @@ const AssetRoute = () => {
return (
-
-
-
- router.back()}
- >
-
-
+
+
+
{
/>
{coinDenom}
-
-
-
+
+
+
@@ -68,10 +67,10 @@ const AssetRoute = () => {
);
};
-const AssetContent = ({ id }: { id: string }) => {
+const AssetContent = ({ coinMinimalDenom }: { coinMinimalDenom: string }) => {
const { data: asset, isLoading } = api.local.assets.getMarketAsset.useQuery(
- { findMinDenomOrSymbol: id.replace(/-/g, "/") },
- { enabled: !!id }
+ { findMinDenomOrSymbol: coinMinimalDenom.replace(/-/g, "/") },
+ { enabled: !!coinMinimalDenom }
);
if (isLoading) {
@@ -119,32 +118,20 @@ const AssetContent = ({ id }: { id: string }) => {
const styles = StyleSheet.create({
container: {
- height: "100%",
+ flex: 1,
paddingBottom: TRADE_BUTTON_HEIGHT,
},
- content: {
- paddingHorizontal: 24,
- },
- header: {
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "center",
- },
assetInfo: {
flexDirection: "row",
alignItems: "center",
gap: 8,
},
- chevronLeftIcon: {
- position: "absolute",
- left: 0,
- },
assetDenom: {
fontSize: 20,
fontWeight: "600",
},
assetContent: {
- paddingVertical: 24,
+ padding: 24,
height: "100%",
},
tradeButtonContainer: {
@@ -158,11 +145,8 @@ const styles = StyleSheet.create({
borderTopColor: "rgba(255, 255, 255, 0.2)",
},
tradeButton: {
- backgroundColor: Colors["wosmongton"][500],
paddingHorizontal: 15,
paddingVertical: 15,
- borderRadius: 255,
- alignItems: "center",
width: "80%",
},
});
diff --git a/packages/mobile/app/trade.tsx b/packages/mobile/app/trade.tsx
new file mode 100644
index 0000000000..edad2fc4ac
--- /dev/null
+++ b/packages/mobile/app/trade.tsx
@@ -0,0 +1,247 @@
+import { Stack, useLocalSearchParams, useRouter } from "expo-router";
+import React, { useState } from "react";
+import { StyleSheet, TouchableOpacity, View } from "react-native";
+import { ScrollView } from "react-native-gesture-handler";
+import { SafeAreaView } from "react-native-safe-area-context";
+
+import { ArrowDownIcon } from "~/components/icons/arrow-down";
+import { ArrowLeftIcon } from "~/components/icons/arrow-left";
+import { PlusIcon } from "~/components/icons/plus-icon";
+import { RouteHeader } from "~/components/route-header";
+import { Button } from "~/components/ui/button";
+import { Text } from "~/components/ui/text";
+import { Colors } from "~/constants/theme-colors";
+
+type Props = {};
+
+const TradeScreen = (props: Props) => {
+ const { toToken } = useLocalSearchParams<{
+ toToken: string;
+ }>();
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+ Trade
+
+
+
+
+
+
+
+
+ );
+};
+
+const PREVIEW_BUTTON_HEIGHT = 100;
+
+export function TradeInterface() {
+ const [amount, setAmount] = useState("0");
+ const [error, setError] = useState("You don't have enough ETH");
+
+ const handleNumberClick = (num: string) => {
+ if (amount === "0" && num !== ".") {
+ setAmount(num);
+ } else {
+ setAmount((prev) => {
+ if (num === "." && prev.includes(".")) return prev;
+ return prev + num;
+ });
+ }
+ };
+
+ const handleDelete = () => {
+ setAmount((prev) => {
+ if (prev.length <= 1) return "0";
+ return prev.slice(0, -1);
+ });
+ };
+
+ return (
+
+ {/* Amount Display */}
+
+
+ ${amount}
+
+
+
+ {/* Trade Cards */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Error Message */}
+ {error && {error}}
+
+ {/* Number Pad */}
+
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, ".", 0].map((num, i) => (
+ handleNumberClick(num.toString())}
+ >
+ {num}
+
+ ))}
+
+
+
+
+
+ );
+}
+
+const TradeCard = ({
+ title,
+ subtitle,
+}: {
+ title: string;
+ subtitle: string;
+}) => (
+
+
+
+
+
+ {title}
+ {subtitle}
+
+
+);
+
+const styles = StyleSheet.create({
+ container: {
+ height: "100%",
+ paddingBottom: PREVIEW_BUTTON_HEIGHT,
+ padding: 24,
+ },
+ amountDisplay: {
+ fontWeight: "600",
+ alignSelf: "center",
+ marginBottom: 32,
+ },
+ tradeCardsContainer: {
+ marginBottom: 32,
+ gap: 4,
+ },
+ tradeCard: {
+ backgroundColor: Colors["osmoverse"][825],
+ borderRadius: 12,
+ padding: 24,
+ flexDirection: "row",
+ alignItems: "center",
+ gap: 16,
+ },
+ tradeCardTitle: {
+ color: "#ffffff",
+ fontSize: 16,
+ fontWeight: 300,
+ },
+ tradeCardSubtitle: {
+ color: Colors["osmoverse"][400],
+ fontSize: 16,
+ fontWeight: 300,
+ },
+ addButton: {
+ backgroundColor: "#2c2d43",
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ addButtonText: {
+ color: "white",
+ fontSize: 24,
+ },
+ swapButtonContainer: {
+ justifyContent: "center",
+ alignItems: "center",
+ zIndex: 10,
+ position: "absolute",
+ alignSelf: "center",
+ top: "50%",
+ transform: [{ translateY: "-50%" }],
+ },
+ swapButton: {
+ backgroundColor: Colors["wosmongton"][700],
+ width: 42,
+ height: 42,
+ borderRadius: 24,
+ justifyContent: "center",
+ alignItems: "center",
+ borderColor: Colors["osmoverse"][1000],
+ borderWidth: 4,
+ },
+ errorMessage: {
+ color: "#ff4444",
+ fontSize: 14,
+ textAlign: "center",
+ marginBottom: 24,
+ },
+ numberPad: {
+ flexDirection: "row",
+ flexWrap: "wrap",
+ justifyContent: "space-between",
+ marginBottom: 32,
+ },
+ numberButton: {
+ width: "30%",
+ aspectRatio: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ marginBottom: 16,
+ },
+ numberButtonText: {
+ color: "white",
+ fontSize: 24,
+ fontWeight: "500",
+ },
+ previewButtonContainer: {
+ position: "absolute",
+ height: PREVIEW_BUTTON_HEIGHT,
+ bottom: 0,
+ width: "100%",
+ paddingTop: 10,
+ borderTopWidth: 1,
+ alignItems: "center",
+ borderTopColor: "rgba(255, 255, 255, 0.2)",
+ },
+ previewButton: {
+ paddingHorizontal: 15,
+ paddingVertical: 15,
+ width: "80%",
+ },
+});
+
+export default TradeScreen;
diff --git a/packages/mobile/components/asset-chart.tsx b/packages/mobile/components/asset/asset-chart.tsx
similarity index 77%
rename from packages/mobile/components/asset-chart.tsx
rename to packages/mobile/components/asset/asset-chart.tsx
index 6e467391bb..9c2875794b 100644
--- a/packages/mobile/components/asset-chart.tsx
+++ b/packages/mobile/components/asset/asset-chart.tsx
@@ -1,16 +1,20 @@
import { Dec, RatePretty } from "@osmosis-labs/unit";
import * as Haptics from "expo-haptics";
-import { transparentize } from "polished";
import { useEffect, useMemo, useState } from "react";
-import { ActivityIndicator, StyleSheet, View } from "react-native";
-import { GraphPoint, LineGraph } from "react-native-graph";
+import {
+ ActivityIndicator,
+ LayoutChangeEvent,
+ StyleSheet,
+ View,
+} from "react-native";
+import { runOnJS, useDerivedValue } from "react-native-reanimated";
+import { LineChart, TLineChartPoint } from "react-native-wagmi-charts";
import { create } from "zustand";
import { useShallow } from "zustand/react/shallow";
import { SubscriptDecimal } from "~/components/subscript-decimal";
import { Button } from "~/components/ui/button";
import { Text } from "~/components/ui/text";
-import { Colors } from "~/constants/theme-colors";
import { getChangeColor } from "~/utils/price";
import { api, RouterOutputs } from "~/utils/trpc";
@@ -31,8 +35,8 @@ const AssetChartAvailableDataTypes = ["price", "volume"] as const;
type AssetChartDataType = (typeof AssetChartAvailableDataTypes)[number];
interface SelectedPointState {
- selectedPoint: GraphPoint | null;
- setSelectedPoint: (point: GraphPoint | null) => void;
+ selectedPoint: TLineChartPoint | null;
+ setSelectedPoint: (point: TLineChartPoint | null) => void;
timeFrame: string;
setTimeFrame: (frame: string) => void;
priceChangeOverride: RatePretty | undefined;
@@ -56,6 +60,8 @@ export const AssetChart = ({
}: {
asset: RouterOutputs["local"]["assets"]["getMarketAsset"];
}) => {
+ const [parentWidth, setParentWidth] = useState();
+
const { setSelectedPoint } = useAssetChartSelectedPointStore(
useShallow((state) => ({
setSelectedPoint: state.setSelectedPoint,
@@ -136,10 +142,9 @@ export const AssetChart = ({
}
);
- const points = useMemo(() => {
- if (!historicalPriceData) return [];
+ const points = useMemo(() => {
return historicalPriceData?.map((point) => ({
- date: new Date(point.time),
+ timestamp: point.time,
value: dataType === "price" ? point.close : point.volume,
}));
}, [historicalPriceData, dataType]);
@@ -150,8 +155,8 @@ export const AssetChart = ({
return;
}
- const lastPoint = points[points.length - 1];
- const firstPoint = points[0];
+ const lastPoint = points?.[points.length - 1];
+ const firstPoint = points?.[0];
if (points && lastPoint && firstPoint) {
setPriceChangeOverride(
new RatePretty((lastPoint.value - firstPoint.value) / lastPoint.value)
@@ -161,7 +166,6 @@ export const AssetChart = ({
const changeColor = useMemo(() => {
let change: RatePretty | undefined;
- console.log({ timeFrame });
if (timeFrame === "1h") {
change = asset.priceChange1h;
} else if (timeFrame === "1d") {
@@ -181,43 +185,50 @@ export const AssetChart = ({
timeFrame,
]);
+ const onLayout = (event: LayoutChangeEvent) => {
+ const { width } = event.nativeEvent.layout;
+ setParentWidth(width);
+ };
+
return (
-
+
{/* // TODO: Add skeleton */}
- {isHistoricalPriceDataLoading ? (
+ {isHistoricalPriceDataLoading || !points || !parentWidth ? (
) : (
- {
- if (process.env.EXPO_OS === "ios") {
- // Add a soft haptic feedback when pressing down.
- Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
- }
- }}
- onPointSelected={(point) => {
- setSelectedPoint(point);
- }}
- onGestureEnd={() => {
- setSelectedPoint(null);
- }}
- />
+
+ {
+ setSelectedPoint(points[index]);
+ }}
+ >
+
+
+
+
+
+
+ {
+ if (process.env.EXPO_OS === "ios") {
+ // Add a soft haptic feedback when pressing down on the tabs.
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
+ }
+ }}
+ onEnded={() => {
+ if (process.env.EXPO_OS === "ios") {
+ // Add a soft haptic feedback when pressing down on the tabs.
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
+ }
+ }}
+ />
+
+
+
)}
+