This wiki documents the WiseInvest iOS app: a SwiftUI client for wiseinvest.in that mirrors the web experience for stock discovery, detailed stock analysis, a wealth dashboard, and a synced watchlist.
It covers:
For a deeper architectural view, see Architecture overview.
The app is a pure SwiftUI application targeting iOS 17+, organized under the WiseInvest/ directory.
Entry point: WiseInvest/WiseInvestApp.swift
@main struct WiseInvestApp: App.StocksService — stock list/detail + live quote service.AuthStore — Cognito-backed authentication store (email/password + Google + token refresh).UserDataStore — authenticated user data (watchlist, wealth dashboard, EPF data)..environment(stocksService), .environment(authStore), .environment(userDataStore)..task { await authStore.bootstrap() }..preferredColorScheme(.dark)) and applies the shared tint color from Theme.primary.ContentView inside a single WindowGroup.Root view / navigation shell: WiseInvest/ContentView.swift
Tab enum with cases: home, stocks, wealth, watchlist, profile.TabView(selection:) to host the five primary sections:
HomeView() — brand marketing / landing page.StocksListView() — discover stocks by index with search & sort.WealthDashboardView() — authenticated wealth dashboard with a demo mode.WatchlistView() — Cognito-linked, AppSync-backed watchlist with live quotes.ProfileView() — profile/settings (defined in Views/PlaceholderViews.swift).OPEN_TAB (set via SIMCTL_CHILD_OPEN_TAB) selects an initial tab: wealth, stocks, watchlist, profile, or defaults to home.OPEN_STOCK (set via SIMCTL_CHILD_OPEN_STOCK) can be used to deep-link directly into StockDetailView for a given symbol.See Architecture overview for how these pieces fit together.
Source: WiseInvest/Views/HomeView.swift + Views/HomeNew/* + Views/HeaderView.swift + Theme.swift.
HomeNewPage).NavigationStack with a custom HeaderView followed by a vertical ScrollView of content sections.BrandHeroSection, HeroSection, FeaturesSection, PricingSection, FAQSection, and FooterSection.Theme.background and hides the default navigation bar for a full-bleed brand experience.Source: WiseInvest/Views/StocksListView.swift, WiseInvest/Models/Stock.swift, WiseInvest/Models/StockIndex.swift, WiseInvest/Models/IndexMetadata.swift, WiseInvest/Services/StocksService.swift, WiseInvest/Services/APIClient.swift.
StocksListView is a NavigationStack that:
StocksService from the environment.HeaderView) with the current Nifty index label.StockIndex.all, covering NIFTY 50/100/200/500 plus sectoral & thematic indices.ScrollView + LazyVStack of StockRow views:
StockLogo plus symbol/name.StockDetailView(stock:).Sorting & filtering:
filtered array handles case-insensitive filtering and sorts by symbol, name, price, change, or market cap.Data source: See Networking & backend for the details of how StocksService fetches from S3.
Source: WiseInvest/Views/StockDetailView.swift, WiseInvest/Views/PriceChartView.swift, WiseInvest/Views/StockDetail/*, WiseInvest/Models/StockDetail.swift, WiseInvest/Models/StockHistory.swift, WiseInvest/Models/LiveQuote.swift, WiseInvest/Services/StocksService.swift, WiseInvest/Services/APIClient.swift.
StockDetailView is a native replica of the WiseInvest web stock page (e.g. /stocks/reliance).PriceChartView) backed by StockHistory, with markers for dividends and splits.StockDetail from StocksService.detail(for:) (S3 stocks/{SYMBOL}.json).StockHistory from StocksService.history(for:) (S3 history/{SYMBOL}.json).LiveQuoteResult from StocksService.liveQuote(for:) or quotes(for:).UserDataStore for watchlist membership and sync.UserDataStore and AuthStore (mirrors the web AuthPromptModal behavior):
LoginView sheet.Flex, Safe) so missing/partial fields from S3 do not crash the page.Source: WiseInvest/Views/Wealth/WealthDashboardView.swift, WiseInvest/Views/Wealth/*, WiseInvest/Models/WealthData.swift, WiseInvest/Models/EPFData.swift, WiseInvest/Services/WealthAPI.swift, UserDataStore.
WealthDashboardView is a mobile-first rebuild of the WiseInvest web wealth dashboard (/wealth/overview).WealthData plus optional retirement summary.LoginRequiredView gate with an optional View Demo button that toggles a demoMode flag and renders the same cards using WealthData.demo..refreshable to re-fetch dashboard data via UserDataStore.reloadDashboard(auth:).NetWorthHeroCard) with current net worth, 6-month change %, and a call-to-action.NetWorthHistoryCard) using Swift Charts and WealthData.MonthValue history.AssetAllocationCard) over categories like Real Estate, Equity, Gold, etc.WealthPalette.LiabilitiesCard) with outstanding loans, DTI, EMI, and highlight for high-interest debts.IncomeExpenseCard) by category.SurplusCard).GoalsCard) and insurance coverage (InsuranceCard).WealthAPI to fetch retirement corpus; when present, renders a RetirementCard.EPFCard) when no retirement API data exists.EPFEmptyCard call-to-action.Source: WiseInvest/Views/WatchlistView.swift, UserDataStore, AuthStore, StocksService, LiveQuote.
WatchlistView provides a signed-in-only watchlist that stays in sync with the WiseInvest backend.LoginRequiredView with a message about syncing watchlist across app and web.UserDataStore.watchlistLoaded is true..task(id: auth.isAuthenticated) to sync watchlist from the backend and refresh live quotes on auth changes.quotes: [String: LiveQuote] dictionary.StocksService.quotes(for:) to perform a single batched heatmap fetch covering all symbols.Fmt helpers and StockTheme colors.UserDataStore.remove(symbol:auth:).Source: WiseInvest/Views/Auth/LoginView.swift, WiseInvest/Services/CognitoAuth.swift, WiseInvest/Services/AuthStore.swift, LoginRequiredView (in LoginView.swift), ProfileView (in Views/PlaceholderViews.swift).
CognitoAuth (Services/CognitoAuth.swift):
CognitoConfig) and exposes helpers for:
USER_PASSWORD_AUTH).REFRESH_TOKEN_AUTH).ASWebAuthenticationSession:
googleAuthURL(codeVerifier:state:).exchangeCode(_:codeVerifier:).TokenStore helper (JSON-encodes AuthTokens).AuthStore (Services/AuthStore.swift):
@Observable @MainActor class that mirrors the web's useAuthStore hook.State (unknown, signedOut, signedIn(UserProfile)).bootstrap(), loads tokens from Keychain, refreshes if expired, fetches the user profile from Cognito, and updates state.isAuthenticated and profile convenience properties.signIn(email:password:) — calls CognitoAuth.signIn then adopt(tokens:).adopt(tokens:) — used for Google sign-in; persists tokens and loads profile.validAccessToken() — returns a still-valid or freshly-refreshed access token, or signs out on failure.signOut(revoke:) — optional global sign-out + local token clear.LoginView (Views/Auth/LoginView.swift):
CognitoAuth.googleAuthURL and the webAuthenticationSession environment value.Mode (Sign In/Sign Up) and Step (credentials, confirmCode, resetRequest, resetConfirm) state machine.run to show spinners and errors consistently.LoginRequiredView (in LoginView.swift):
LoginView as a sheet for the Sign In button.ProfileView:
Views/PlaceholderViews.swift.AuthStore to show profile info and a sign-out button.The app keeps its models simple and web-aligned, mirroring the JSON shapes the WiseInvest web frontend uses.
Source: WiseInvest/Models/Stock.swift, WiseInvest/Models/StockIndex.swift, WiseInvest/Models/IndexMetadata.swift.
Stock — lightweight, in-memory representation of a row in the stocks table:
symbol, name, sector, price, change (%), marketCapRaw, marketCapFormatted, volume.id (symbol), isUp, changeFormatted, priceFormatted (₹, en_IN locale).StockIndex — describes an index like "Nifty 50" or "Nifty Bank":
value (e.g. NIFTY50), label, metadataFile (e.g. nifty50_metadata.json).all array is the canonical list of indices rendered in StocksListView.IndexMetadata + IndexTreeRoot/Sector/RawStock — decode index-level S3 JSON:
IndexMetadata.latestFilePath points to the latest heatmap snapshot (S3 URL).IndexTreeRoot is the root with children sectors; each Sector has name and children of RawStock.RawStock carries raw number/string fields and nested Changes.StocksService.stocks(for:) maps this tree into a flat [Stock] with sector name and formatted fields.Source: WiseInvest/Models/StockDetail.swift, WiseInvest/Models/StockHistory.swift, WiseInvest/Models/LiveQuote.swift.
struct mirroring the full stocks/{SYMBOL}.json payload used by the web stock page.@Flex and @Safe property wrappers (see below) to tolerate messy JSON and optional fields.liveTimestamp and isLiveData that are populated after merging heatmap data.OHLCPoint + ChartRange:
history/{SYMBOL}.json with multiple ranges (1w hourly, 6m daily, etc.).StockHistory.points(for:) selects the correct array based on the chart's selected ChartRange.OHLCPoint.parsedDate parses flexible date strings in the India time zone.HeatmapMetadata + LiveTreeRoot + MarketDump:
LiveQuote contains both pricing and fundamental quick stats (roe, margins, growth, etc.) and nested Changes.HeatmapMetadata.bucketKey converts S3-style URIs (s3://bucket/path) into bucket-relative keys.LiveTreeRoot is a sector-decorated tree; MarketDump is a symbol-keyed flat map.Source: WiseInvest/Models/WealthData.swift, WiseInvest/Models/EPFData.swift.
WealthData:
NetWorth, Assets, Liabilities, CashFlowSide, Goal, and Insurance.WealthData.demo provides a realistic hard-coded demo payload that matches the web demo numbers.WealthAPI.fetchDashboard(token:) maps raw backend responses into WealthData so the UI can remain backend-agnostic.EPFData:
UserWealth.epfData AWSJSON), matching the web's EPFWealthSection.Source: WiseInvest/Models/StockDetail.swift (top of file).
@Flex — property wrapper that decodes a Double? from Int, Double, or numeric String values; invalid/missing values become nil.@Safe — tolerant wrapper that decodes nested Decodable types but treats any failure as nil instead of failing the whole payload.FlexText — helper for values that might be string/number/bool but are rendered as text.KeyedDecodingContainer extensions add helpers for decoding these wrappers without crashing.Source: WiseInvest/Services/APIClient.swift, WiseInvest/Services/StocksService.swift, WiseInvest/Models/*.
APIClient is the shared low-level HTTP client for S3-backed JSON:
bucket = "s3financialvisualizationdata11322-dev"region = "ap-south-1"bucketBase = https://{bucket}.s3.{region}.amazonaws.com.stockDataBase = bucketBase/stock_page_data.logoURL(for:), logoPNGURL(for:), historyURL(for:), stockDetailURL(for:).URLSession with caching enabled (32 MB in-memory, 128 MB disk) and .returnCacheDataElseLoad to reduce network calls.fetchData(_:cacheBust:) — fetches Data, optionally cache-busting by appending a minute-based _t query param and ignoring local cache (used for metadata files with in-place updates).fetchJSON<T: Decodable>(_:sanitizeNaN:cacheBust:) — fetches and decodes JSON, with optional NaN sanitization (regex replacement of : NaN → : null).StocksService is the high-level stock data service:
@Observable @MainActor final class injected into the environment.cache: [String: [Stock]] per index.historyCache: [String: StockHistory].detailCache: [String: StockDetail].history(for:) uses APIClient.historyURL + fetchJSON(sanitizeNaN: true).detail(for:) uses APIClient.stockDetailURL + fetchJSON(sanitizeNaN: true).HeatmapMetadata and S3 metadata/*.json to find latest NIFTY 50 and full-market snapshots.liveQuote(for:) first checks the NIFTY 50 tree, then falls back to the full market dump.quotes(for:) is a batched lookup: one NIFTY 50 tree fetch and, if needed, one full-market dump fetch.mergedDetail(for:) concurrently fetches static detail and live quote, then merges them into a single StockDetail (or builds a detail-only view from live data when static JSON is missing).stocks(for index: StockIndex) loads index metadata from metadata/{index.metadataFile}, extracts the latest S3 key from latestFilePath, fetches the heatmap tree JSON, and flattens into [Stock].Source: WiseInvest/Services/CognitoAuth.swift, WiseInvest/Services/AuthStore.swift, WiseInvest/Views/Auth/LoginView.swift.
CognitoAuth calls the Cognito IDP REST API directly using URLSession with application/x-amz-json-1.1 and X-Amz-Target headers.USER_PASSWORD_AUTH, so SRP is not used.AuthStore and LoginView coordinate flows but treat Cognito as an implementation detail.AuthTokens) include access, ID, optional refresh, and expiry timestamp; AuthStore.validAccessToken() ensures a usable token for each privileged call and will sign out if refresh fails.TokenStore and the iOS Keychain.Source: WiseInvest/Services/WealthAPI.swift, WealthData, WealthDashboardView, UserDataStore.
WealthAPI is a light REST client for https://wealth.wiseinvestbackend.in.
get<T: Decodable>(_ path: String, query: [String:String] = [:], token: String) that:
URLComponents.Bearer {token} Authorization header from AuthStore.validAccessToken().apiLog.fetchDashboard(token:) that requests all dashboard endpoints in parallel using async let, then assembles a single (WealthData, RetirementSummary?) payload for the UI.categoryDisplay, assetPalette, goalPalette, icons, etc.) to convert raw categories to user-friendly labels and colors.UserDataStore (in Services/UserDataService.swift):
dashboardLoaded.Source: README.md, project.yml.
The project uses XcodeGen, so the .xcodeproj is generated and not committed.
brew install xcodegen # one-time
cd ~/workplace/wiseinvest-ios # or your cloned path
xcodegen generate # produces WiseInvest.xcodeproj
WiseInvest.xcodeproj in Xcode.Requires a generated WiseInvest.xcodeproj and at least one available simulator:
xcrun simctl list devices available | head
xcodebuild -project WiseInvest.xcodeproj \
-scheme WiseInvest \
-destination 'platform=iOS Simulator,name=iPhone 15' \
build
Public stock data:
APIClient:
bucket = "s3financialvisualizationdata11322-dev"region = "ap-south-1"Cognito auth:
CognitoConfig in CognitoAuth.swift contains the Cognito region, pool ID, client ID, Hosted UI domain, scopes, and redirect URI.wiseinvest://auth/callback) is registered in the Cognito app client.Wealth backend:
WealthAPI.base is currently https://wealth.wiseinvestbackend.in.Simulator debug hooks:
SIMCTL_CHILD_OPEN_TAB=wealth — opens directly to the Wealth tab.SIMCTL_CHILD_OPEN_TAB=stocks — opens directly to the Stocks tab.SIMCTL_CHILD_OPEN_STOCK=RELIANCE — opens the stock detail page for RELIANCE on launch.For a more detailed breakdown of data flow, dependencies, and how to safely change key areas (stocks, auth, wealth, watchlist), see Architecture overview.
UserDataStore and AppSync watchlist schema in more depth once the backend schema stabilizes.