Skip to content

CookUpSunny/MetalVerse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

4 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

MetalVerse 2.0 ๐Ÿ’Ž

Refactored, production-ready architecture. Same liquid-glass UI and animations as v1, with cleaner separation of concerns and significant performance improvements.


๐Ÿš€ Setup

cd MetalVerse
npm install
npx expo start

Optionally add a Google Places API key in app.json โ†’ expo.extra.googlePlacesKey to enable live location search (the Locations tab works with mock data without one).


๐Ÿ—๏ธ Refactored Architecture

MetalVerse/
โ”œโ”€โ”€ App.js                          # Provider tree
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ api/                        # Network layer
    โ”‚   โ”œโ”€โ”€ http.js                 # Configured axios instance + ApiError
    โ”‚   โ”œโ”€โ”€ cache.js                # Two-tier (memory + AsyncStorage) cache
    โ”‚   โ”œโ”€โ”€ createCachedService.js  # Factory: removes service boilerplate
    โ”‚   โ”œโ”€โ”€ metalsApi.js
    โ”‚   โ”œโ”€โ”€ cryptoApi.js
    โ”‚   โ”œโ”€โ”€ fxApi.js
    โ”‚   โ”œโ”€โ”€ placesApi.js
    โ”‚   โ””โ”€โ”€ index.js
    โ”‚
    โ”œโ”€โ”€ config/                     # Configuration & domain reference data
    โ”‚   โ”œโ”€โ”€ index.js                # Single source of all env-derived config
    โ”‚   โ””โ”€โ”€ domain.js               # METALS, UNITS, CURRENCIES, etc. + lookup maps
    โ”‚
    โ”œโ”€โ”€ theme/                      # Pure design tokens
    โ”‚   โ”œโ”€โ”€ tokens.js               # Colors, Spacing, Radius, Typography, Animation
    โ”‚   โ””โ”€โ”€ index.js
    โ”‚
    โ”œโ”€โ”€ components/                 # UI โ€” split by purpose
    โ”‚   โ”œโ”€โ”€ primitives/             # GlassPanel, GlowButton, FieldLabel
    โ”‚   โ”œโ”€โ”€ inputs/                 # DropdownPicker, SegmentedSelector, NumericInput
    โ”‚   โ”œโ”€โ”€ layout/                 # CosmicBackground, ScreenHeader
    โ”‚   โ””โ”€โ”€ index.js
    โ”‚
    โ”œโ”€โ”€ animations/                 # Pure visual/motion components
    โ”‚   โ”œโ”€โ”€ MetalOrb.js
    โ”‚   โ”œโ”€โ”€ AnimatedNumber.js       # Now updates on UI thread (zero React re-renders)
    โ”‚   โ”œโ”€โ”€ CashVisualization.js
    โ”‚   โ””โ”€โ”€ index.js
    โ”‚
    โ”œโ”€โ”€ context/
    โ”‚   โ””โ”€โ”€ PricesContext.js        # Single shared market-data provider
    โ”‚
    โ”œโ”€โ”€ hooks/
    โ”‚   โ”œโ”€โ”€ useConvertAnimation.js  # Animation choreography
    โ”‚   โ”œโ”€โ”€ useDebouncedValue.js
    โ”‚   โ””โ”€โ”€ index.js
    โ”‚
    โ”œโ”€โ”€ utils/                      # Pure functions, easy to unit test
    โ”‚   โ”œโ”€โ”€ converter.js            # Metal โ†’ USD โ†’ output pipeline
    โ”‚   โ”œโ”€โ”€ format.js
    โ”‚   โ”œโ”€โ”€ validation.js
    โ”‚   โ””โ”€โ”€ haptic.js
    โ”‚
    โ”œโ”€โ”€ screens/                    # One folder per screen, with sub-components
    โ”‚   โ”œโ”€โ”€ convert/
    โ”‚   โ”‚   โ”œโ”€โ”€ ConvertScreen.js    # Orchestrator (form state + result)
    โ”‚   โ”‚   โ”œโ”€โ”€ ConvertHero.js      # Orb + value + cash
    โ”‚   โ”‚   โ”œโ”€โ”€ ConvertControls.js  # All form inputs
    โ”‚   โ”‚   โ””โ”€โ”€ SpotPriceCard.js
    โ”‚   โ”œโ”€โ”€ metals/
    โ”‚   โ”‚   โ”œโ”€โ”€ MetalsScreen.js
    โ”‚   โ”‚   โ””โ”€โ”€ MetalCard.js
    โ”‚   โ”œโ”€โ”€ crypto/
    โ”‚   โ”‚   โ”œโ”€โ”€ CryptoScreen.js
    โ”‚   โ”‚   โ””โ”€โ”€ CryptoCard.js
    โ”‚   โ”œโ”€โ”€ locations/
    โ”‚   โ”‚   โ”œโ”€โ”€ LocationsScreen.js
    โ”‚   โ”‚   โ”œโ”€โ”€ LocationSearchForm.js
    โ”‚   โ”‚   โ””โ”€โ”€ LocationResultRow.js
    โ”‚   โ””โ”€โ”€ index.js
    โ”‚
    โ””โ”€โ”€ navigation/
        โ”œโ”€โ”€ RootNavigator.js
        โ”œโ”€โ”€ GlassTabBar.js
        โ””โ”€โ”€ index.js

๐Ÿ”„ What Changed vs v1

1. API layer

  • Before: each service had its own copy of cache + stale + fallback logic.
  • After: createCachedService factory wraps a fetcher with the cache/stale/fallback dance. Each service now declares what changes (URL, parsing, fallback) and reuses what doesn't.
  • Before: placesApi.js reached into expo-constants directly.
  • After: all environment values flow through one config/ module. Services only know about config.

2. Single source of truth for live data

  • Before: three screens (Convert, Metals, Crypto) each called useLivePrices(). Three concurrent fetch loops, three timers, three cache states.
  • After: a single PricesProvider at the app root. Selector hooks (useMetalPrices, useCryptoPrices, useFxRates) subscribe to one slice. The provider also pauses polling when the app backgrounds and prevents overlapping fetches.

3. Context split for re-render isolation

The provider exposes two contexts:

  • PricesDataContext โ€” the actual prices
  • PricesMetaContext โ€” loading, lastUpdated, warning, refresh

A component reading only metalPrices doesn't re-render when lastUpdated ticks every minute.

4. Component split (Convert screen)

The 358-line ConvertScreen became:

  • ConvertScreen โ€” form state + delegation
  • ConvertHero โ€” orb + animated value + cash visualization
  • ConvertControls โ€” all the input fields
  • SpotPriceCard โ€” current metal spot price

Now typing in the amount field doesn't re-render the orb or the spot price card.

5. Screens split into folders

Each screen got its own folder with co-located sub-components. Easier to find related code, easier to modify one screen without touching anything else.


โšก Performance Optimizations

Critical: AnimatedNumber rewrite

The original used setInterval + setState at ~60fps to tween the displayed number. That's roughly 60 React re-renders per conversion, each propagating through the screen tree.

The new implementation uses Reanimated's useAnimatedProps on an AnimatedTextInput. The text content is updated on the UI thread; zero React re-renders during the animation. Same visual result, no main-thread cost.

Pre-built option arrays

Picker option arrays (METALS.map(...), etc.) used to be rebuilt inline on every render โ€” new array identity every time, breaking memoization of children. Now they're module-level frozen constants in config/domain.js (METAL_OPTIONS, UNIT_OPTIONS, CURRENCY_OPTIONS, CRYPTO_OPTIONS, OUTPUT_MODE_OPTIONS).

Lookup maps

Frequent .find(...) calls (e.g., METALS.find(m => m.id === metalId)) are now O(1) lookups via pre-built maps (METAL_BY_ID, UNIT_BY_ID, CURRENCY_BY_ID, CRYPTO_BY_ID).

Memoization

Every UI component, animation component, and list item is wrapped in React.memo. With the inline-array fix above, memoization actually does its job now.

Stable callbacks

  • Inline arrow handlers (onChange={(t) => onChange(...)}) replaced with useCallback-wrapped handlers in NumericInput, DropdownPicker rows, SegmentedSelector options, and tab bar items.
  • Module-level constants for static prop values (gradient color arrays, gradient start/end points, button color tuples, etc.) so they don't get fresh identity each render.

Tab bar

  • Each tab is a memoized <Tab /> component. When focus changes, only the two tabs whose focused prop flipped re-render โ€” not all four.
  • screenOptions and renderTabBar are hoisted out of RootNavigator's render.

Background-aware refresh

The prices provider listens to AppState and:

  • Refreshes when the app foregrounds
  • Skips polling when overlapping fetches would occur (inFlightRef)
  • Stops re-creating its meta object if loading hasn't changed

Animation choreography centralized

useConvertAnimation owns the random-animation-per-conversion logic. Returns a stable trigger function; child animation components only re-render when the key changes.


๐Ÿงช Testability

The pure-function utilities (utils/converter.js, utils/format.js, utils/validation.js) have zero React or React Native dependencies and can be unit tested directly. Same for the conversion math.

API services depend only on http, cache, and config โ€” easily mockable.


๐Ÿ”ฎ Future Extensibility

The architecture is set up for:

  • Price charts โ€” drop a <PriceChart /> into the Metals/Crypto screens. Historical data fetcher goes into api/. Selector hook into context/.
  • Alerts โ€” add api/alertsApi.js, an AlertsProvider, and a screen. The cache layer already supports persistent storage.
  • Portfolio tracking โ€” persist holdings via the existing AsyncStorage-backed cache; new screens/portfolio/ folder.

๐Ÿ“Š v1 vs v2 at a glance

Aspect v1 v2
Duplicated fetch loops 3 (one per screen) 1 (provider)
Re-renders per conversion ~60 (in number tween) ~3
Service boilerplate ~30 lines duplicated 3ร— factory, ~5 lines per service
Largest screen file 358 lines 130 lines (orchestrator)
Memoized components 0 20+
Inline option arrays every render frozen constants, once
Convert screen splits monolithic hero / controls / spot card

About

Luxury Metal Fiat Crypto Converter App: Luxury React Native conversion app for precious metals to fiat & crypto"

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors