import 'dart:convert'; import 'package:http/http.dart' as http; class StockPricePoint { final DateTime date; final double close; const StockPricePoint({required this.date, required this.close}); } class StockPriceService { Future> fetchPriceHistory( String ticker, { String range = '1mo', String interval = '1d', }) async { final normalizedTicker = ticker.trim().toUpperCase(); final uri = Uri.parse( 'https://query1.finance.yahoo.com/v8/finance/chart/$normalizedTicker?interval=$interval&range=$range', ); final response = await http.get(uri, headers: { 'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json', }); if (response.statusCode != 200) { throw Exception('Failed to fetch price history for $normalizedTicker: ${response.statusCode}'); } final json = jsonDecode(response.body) as Map; final chart = json['chart'] as Map?; final results = chart?['result'] as List?; if (results == null || results.isEmpty) { throw Exception('No price history returned for $normalizedTicker'); } final result = results.first as Map; final timestamps = (result['timestamp'] as List?) ?? const []; final indicators = result['indicators'] as Map?; final adjCloseSeries = ((indicators?['adjclose'] as List?) ?? const []) .cast?>(); final quoteSeries = ((indicators?['quote'] as List?) ?? const []) .cast?>(); final adjCloseEntry = adjCloseSeries.isNotEmpty ? adjCloseSeries.first : null; final quoteEntry = quoteSeries.isNotEmpty ? quoteSeries.first : null; final adjCloseValues = adjCloseEntry == null ? null : adjCloseEntry['adjclose']; final quoteCloseValues = quoteEntry == null ? null : quoteEntry['close']; final closes = (adjCloseValues as List?) ?? (quoteCloseValues as List?) ?? const []; final points = []; final itemCount = timestamps.length < closes.length ? timestamps.length : closes.length; for (int i = 0; i < itemCount; i++) { final timestamp = timestamps[i]; final close = closes[i]; if (timestamp is! num || close is! num) { continue; } points.add( StockPricePoint( date: DateTime.fromMillisecondsSinceEpoch( timestamp.toInt() * 1000, isUtc: true, ).toLocal(), close: close.toDouble(), ), ); } if (points.isEmpty) { throw Exception('No valid price points returned for $normalizedTicker'); } return points; } }