Skip to content

Commit

Permalink
feat: add chart
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseRFelix committed Dec 2, 2024
1 parent 215ccdc commit 1ba68d4
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 269 deletions.
82 changes: 63 additions & 19 deletions packages/mobile/app/(tabs)/assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { SafeAreaView } from "react-native-safe-area-context";
import { SvgUri } from "react-native-svg";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";

import { ChevronDownIcon } from "~/components/icons/chevron-down";
import { FilterIcon } from "~/components/icons/filter";
Expand All @@ -28,13 +29,16 @@ import {
} from "~/components/ui/dropdown";
import { Text } from "~/components/ui/text";
import { Colors } from "~/constants/theme-colors";
import { mmkvStorage } from "~/utils/mmkv";
import { getChangeColor } from "~/utils/price";
import { api, RouterOutputs } from "~/utils/trpc";

const itemSize = 70;

const displayOptions = [
{ key: "price", title: "Price (24h)" },
{ key: "price-1h", title: "Price (1h)" },
{ key: "price-24h", title: "Price (24h)" },
{ key: "price-7d", title: "Price (7d)" },
{ key: "volume", title: "Volume" },
{ key: "market-cap", title: "Market Cap" },
{ key: "favorite", title: "Favorite" },
Expand All @@ -45,11 +49,19 @@ type DisplayOptionStore = {
setDisplayOption: (option: (typeof displayOptions)[number]["key"]) => void;
};

const useDisplayOptionStore = create<DisplayOptionStore>((set) => ({
displayOption: displayOptions[0].key,
setDisplayOption: (option: (typeof displayOptions)[number]["key"]) =>
set({ displayOption: option }),
}));
const useDisplayOptionStore = create<DisplayOptionStore>()(
persist(
(set) => ({
displayOption: displayOptions[1].key,
setDisplayOption: (option: (typeof displayOptions)[number]["key"]) =>
set({ displayOption: option }),
}),
{
name: "display-option",
storage: createJSONStorage(() => mmkvStorage),
}
)
);

export default function TabTwoScreen() {
const [search, setSearch] = useState("");
Expand Down Expand Up @@ -204,7 +216,7 @@ const AssetItem = ({
</View>
</View>
<View style={styles.assetRight}>
{displayOption === "price" && (
{displayOption.startsWith("price") && (
<>
<Text style={styles.price}>
{asset.currentPrice ? (
Expand All @@ -216,18 +228,50 @@ const AssetItem = ({
""
)}
</Text>
<Text
style={[
styles.percentage,
{
color: getChangeColor(
asset.priceChange24h?.toDec() || new Dec(0)
),
},
]}
>
{asset.priceChange24h?.toString()}
</Text>
{displayOption === "price-24h" && (
<Text
style={[
styles.percentage,
{
color: getChangeColor(
asset.priceChange24h?.toDec() || new Dec(0)
),
},
]}
>
{asset.priceChange24h?.toString()}
</Text>
)}

{displayOption === "price-7d" && (
<Text
style={[
styles.percentage,
{
color: getChangeColor(
asset.priceChange24h?.toDec() || new Dec(0)
),
},
]}
>
{asset.priceChange7d?.toString()}
</Text>
)}

{displayOption === "price-1h" && (
<Text
style={[
styles.percentage,
{
color: getChangeColor(
asset.priceChange1h?.toDec() || new Dec(0)
),
},
]}
>
{asset.priceChange1h?.toString()}
</Text>
)}
</>
)}

Expand Down
25 changes: 17 additions & 8 deletions packages/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { superjson } from "@osmosis-labs/server";
import { localLink } from "@osmosis-labs/trpc";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ThemeProvider } from "@react-navigation/native";
import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { persistQueryClient } from "@tanstack/react-query-persist-client";
import { loggerLink } from "@trpc/client";
Expand All @@ -15,14 +14,12 @@ import { GestureHandlerRootView } from "react-native-gesture-handler";

import { DefaultTheme } from "~/constants/themes";
import { getMobileAssetListAndChains } from "~/utils/asset-lists";
import { mmkvStorage } from "~/utils/mmkv";
import { api } from "~/utils/trpc";
import { appRouter } from "~/utils/trpc-routers/root-router";

const localStoragePersister = createAsyncStoragePersister({
storage: AsyncStorage,
serialize: (client) => superjson.stringify(client),
deserialize: (cachedString) => superjson.parse(cachedString),
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
global.Buffer = require("buffer").Buffer;

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -32,6 +29,19 @@ const queryClient = new QueryClient({
},
});

const localStoragePersister = createSyncStoragePersister({
storage: mmkvStorage,
serialize: (client) => {
try {
return superjson.stringify(client);
} catch (error) {
console.error("Error serializing client", error);
return "";
}
},
deserialize: (cachedString) => superjson.parse(cachedString),
});

persistQueryClient({
queryClient,
persister: localStoragePersister,
Expand Down Expand Up @@ -60,7 +70,6 @@ export default function RootLayout() {
(opts.direction === "down" && opts.result instanceof Error),
}),
(runtime) => {
// initialize the different links for different targets (edge and node)
const servers = {
local: localLink({
router: appRouter,
Expand Down
140 changes: 48 additions & 92 deletions packages/mobile/app/asset/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import {
TouchableOpacity,
View,
} from "react-native";
import { GraphPoint, LineGraph } from "react-native-graph";
import { SafeAreaView } from "react-native-safe-area-context";

import {
AssetChart,
useAssetChartSelectedPointStore,
} from "~/components/asset-chart";
import { ChevronLeftIcon } from "~/components/icons/chevron-left";
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 } from "~/utils/trpc";
import { api, RouterOutputs } from "~/utils/trpc";

const AssetRoute = () => {
const { id, coinDenom, coinImageUrl } = useLocalSearchParams<{
Expand Down Expand Up @@ -60,72 +64,15 @@ const AssetRoute = () => {
</View>

<View style={styles.tradeButtonContainer}>
<TouchableOpacity style={styles.tradeButton}>
<Text>Trade</Text>
</TouchableOpacity>
<Button
title="Trade"
onPress={() => {}}
buttonStyle={styles.tradeButton}
/>
</View>
</SafeAreaView>
);
};
function gaussian(mean: number, variance: number) {
return {
ppf: (p: number) => {
// Using the inverse error function to approximate the inverse CDF of a Gaussian distribution
const a1 = 0.254829592;
const a2 = -0.284496736;
const a3 = 1.421413741;
const a4 = -1.453152027;
const a5 = 1.061405429;
const p_low = 0.02425;
const p_high = 1 - p_low;

if (p < p_low) {
const q = Math.sqrt(-2 * Math.log(p));
return (
mean +
(Math.sqrt(variance) *
((((a5 * q + a4) * q + a3) * q + a2) * q + a1)) /
(1 + q)
);
} else if (p_high < p) {
const q = Math.sqrt(-2 * Math.log(1 - p));
return (
mean -
(Math.sqrt(variance) *
((((a5 * q + a4) * q + a3) * q + a2) * q + a1)) /
(1 + q)
);
} else {
const q = p - 0.5;
const r = q * q;
return (
mean +
(Math.sqrt(variance) *
q *
((((a5 * r + a4) * r + a3) * r + a2) * r + a1)) /
(1 + r)
);
}
},
};
}

function weightedRandom(mean: number, variance: number): number {
const distribution = gaussian(mean, variance);
// Take a random sample using inverse transform sampling method.
return distribution.ppf(Math.random());
}

export function generateRandomGraphData(length: number): GraphPoint[] {
return Array<number>(length)
.fill(0)
.map((_, index) => ({
date: new Date(
new Date(2000, 0, 1).getTime() + 1000 * 60 * 60 * 24 * index
),
value: weightedRandom(10, Math.pow(index + 1, 2)),
}));
}

const AssetContent = ({ id }: { id: string }) => {
const { data: asset, isLoading } = api.local.assets.getMarketAsset.useQuery(
Expand All @@ -147,36 +94,44 @@ const AssetContent = ({ id }: { id: string }) => {

return (
<ScrollView style={styles.assetContent}>
<View style={styles.assetPriceContainer}>
<Text type="title">
{asset.currentPrice?.symbol}
{asset.currentPrice ? (
<SubscriptDecimal decimal={asset.currentPrice.toDec()} />
) : null}
</Text>
<Text
type="subtitle"
style={{
color: getChangeColor(asset.priceChange24h?.toDec() || new Dec(0)),
marginBottom: 5,
}}
>
{asset.priceChange24h?.toString()}
</Text>
</View>
<AssetChartHeader asset={asset} />

<LineGraph
animated
color={getChangeColor(asset.priceChange24h?.toDec() || new Dec(0))}
points={generateRandomGraphData(100)}
enablePanGesture
<AssetChart asset={asset} />
</ScrollView>
);
};

const AssetChartHeader = ({
asset,
}: {
asset: RouterOutputs["local"]["assets"]["getMarketAsset"];
}) => {
const { selectedPoint } = useAssetChartSelectedPointStore((state) => state);

return (
<View style={styles.assetPriceContainer}>
<Text type="title">
{asset.currentPrice?.symbol}
{asset.currentPrice || selectedPoint ? (
<SubscriptDecimal
decimal={
selectedPoint
? new Dec(selectedPoint?.value)
: asset.currentPrice.toDec() ?? new Dec(0)
}
/>
) : null}
</Text>
<Text
type="subtitle"
style={{
height: 220,
width: "100%",
color: getChangeColor(asset.priceChange24h?.toDec() || new Dec(0)),
marginBottom: 5,
}}
gradientFillColors={["#ff00005D", "#ff000000"]}
/>
</ScrollView>
>
{asset.priceChange24h?.toString()}
</Text>
</View>
);
};

Expand Down Expand Up @@ -227,7 +182,8 @@ const styles = StyleSheet.create({
},
tradeButton: {
backgroundColor: Colors["wosmongton"][500],
padding: 15,
paddingHorizontal: 15,
paddingVertical: 15,
borderRadius: 255,
alignItems: "center",
width: "80%",
Expand Down
Loading

0 comments on commit 1ba68d4

Please sign in to comment.