From c17ec1539fa1e8970c8c15b9db5a721201130f5c Mon Sep 17 00:00:00 2001 From: jayesh Date: Fri, 17 May 2024 18:55:00 +0530 Subject: [PATCH] added stock details screen and api integration --- lib/Utils/text.dart | 4 +- lib/Utils/utils.dart | 17 + .../stock_details_model.dart | 2 +- .../stockDetails/stock_details_screen.dart | 705 +++++++++++------- pubspec.lock | 8 + pubspec.yaml | 1 + 6 files changed, 460 insertions(+), 277 deletions(-) diff --git a/lib/Utils/text.dart b/lib/Utils/text.dart index 8eea1fc..028121d 100644 --- a/lib/Utils/text.dart +++ b/lib/Utils/text.dart @@ -186,12 +186,12 @@ Widget text15W600_00FF19(String text) { ); } -Widget text12W400(String text) { +Widget text12W400(String text, {Color? clr}) { return Text( text, style: TextStyle( fontSize: 12.sp, - color: Colors.white, + color: clr ?? Colors.white, fontWeight: FontWeight.w400, fontFamily: 'hiragino'), ); diff --git a/lib/Utils/utils.dart b/lib/Utils/utils.dart index 8e4fbdd..d157bd8 100644 --- a/lib/Utils/utils.dart +++ b/lib/Utils/utils.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get/get.dart' as getx; @@ -53,9 +54,25 @@ class Utils { for (final element in data) { priceData.add(element.close!); } + return priceData; } + static List extractTimeFromGraph(List data) { + final List time = []; + for (final element in data) { + DateTime dateTime = DateTime.parse(element.timestamp!); + String formattedDate = DateFormat('MMM d yyyy').format(dateTime); + time.add(formattedDate); + } + return time; + } + + static String dateFormatterForGraph(String dateV) { + DateTime dateTime = DateTime.parse(dateV); + return DateFormat('MMM d yyyy').format(dateTime); + } + static showToast(String? msg) { if (msg != null && msg != "null" && msg.isNotEmpty) { Fluttertoast.showToast( diff --git a/lib/model/StockDetailsModel/stock_details_model.dart b/lib/model/StockDetailsModel/stock_details_model.dart index b5d2368..300fee9 100644 --- a/lib/model/StockDetailsModel/stock_details_model.dart +++ b/lib/model/StockDetailsModel/stock_details_model.dart @@ -149,7 +149,7 @@ class Ohlc { Ohlc.fromJson(Map json) { open = json['open'].toDouble(); high = json['high'].toDouble(); - low = json['low']; + low = json['low'].toDouble(); close = json['close'].toDouble(); } diff --git a/lib/view/MainScreen/stockDetails/stock_details_screen.dart b/lib/view/MainScreen/stockDetails/stock_details_screen.dart index 4ac4bba..90254bc 100644 --- a/lib/view/MainScreen/stockDetails/stock_details_screen.dart +++ b/lib/view/MainScreen/stockDetails/stock_details_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart' hide FormData; +import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:traderscircuit/Utils/text.dart'; import 'package:traderscircuit/Utils/utils.dart'; import 'package:traderscircuit/model/StockDetailsModel/stock_details_model.dart'; @@ -25,9 +26,12 @@ class StockDetailsScreen extends StatefulWidget { class _StockDetailsScreenState extends State { RxBool isLoading = true.obs; StockDetailsModel? stockDetailsModel; + String instrumentName = Get.arguments["instrument_name"]; + String percentageDifference = ""; + String netChange = ""; + @override void initState() { - String instrumentName = Get.arguments["instrument_name"]; dv.log(instrumentName); StockDetailsApi() .getStockDetails( @@ -53,7 +57,7 @@ class _StockDetailsScreenState extends State { openInterest: a[6], )); } - + candles = candles.reversed.toList(); stockDetailsModel = StockDetailsModel( status: data["status"], message: data["message"], @@ -61,12 +65,13 @@ class _StockDetailsScreenState extends State { data: Data( stockData: StockInfo( ohlc: Ohlc.fromJson(dynamicData['ohlc']), - averagePrice: dynamicData['average_price'], + averagePrice: dynamicData['average_price'].toDouble(), instrumentToken: dynamicData['instrument_token'], lastPrice: dynamicData['last_price'].toDouble(), lastTradeTime: dynamicData['last_trade_time'], - lowerCircuitLimit: dynamicData['lower_circuit_limit'], - netChange: dynamicData['net_change'], + lowerCircuitLimit: + dynamicData['lower_circuit_limit'].toDouble(), + netChange: dynamicData['net_change'].toDouble(), oi: dynamicData['oi'], oiDayHigh: dynamicData['oi_day_high'], oiDayLow: dynamicData['oi_day_low'], @@ -80,11 +85,22 @@ class _StockDetailsScreenState extends State { candleStick: CandleStick(candles: candles), optionChain: OptionChain.fromJson(data["data"]['option_chain']))); + netChange = dynamicData['net_change'].toString(); + _calculatePercentageChange( + stockDetailsModel!.data!.stockData!.ohlc!.open!, + stockDetailsModel!.data!.stockData!.lastPrice!); + isLoading.value = false; }); super.initState(); } + void _calculatePercentageChange(double openPrice, double currentPrice) { + final percentageChange = ((currentPrice - openPrice) / openPrice) * 100; + + percentageDifference = percentageChange.toStringAsFixed(2); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -94,290 +110,431 @@ class _StockDetailsScreenState extends State { appBar: const CommonAppbar( titleTxt: "", ), - body: Obx(() => Stack(children: [ - const CommonBlurLeft(), - const CommonBlurRight(), - isLoading.value - ? const Center( - child: CircularProgressIndicator( - color: Color(0xFF0093FF), - ), - ) - : Padding( - padding: const EdgeInsets.all(15.0), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W700( - stockDetailsModel!.data!.stockData!.symbol!), - const Gap(14), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - text25W600( - "₹${stockDetailsModel!.data!.stockData!.averagePrice!.toString()}"), - Container( - width: 145, - height: 40, - decoration: ShapeDecoration( - color: const Color(0xFF0093FF), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - SvgPicture.asset( - "assets/images/svg/option_chain_icon.svg"), - const Gap(5), - text12W600("Option Chain"), - ], - )), - ], + body: Obx(() => RefreshIndicator( + color: const Color(0xFF0093FF), + onRefresh: () async { + Future.delayed(const Duration(seconds: 1), () { + isLoading.value = true; + StockDetailsApi() + .getStockDetails(FormData.fromMap( + {"upstox_instrument_key": instrumentName})) + .then((value) { + final Map data = value.data; + + Map data1 = + data["data"]['stock_data']['data']; + String dynamicKey = data1.keys.first; + // Access nested data using dynamic key + Map dynamicData = data1[dynamicKey]; + + List? candles = []; + + for (var a in data["data"]['candle_stick']["data"] + ["candles"]) { + candles.add(Candles( + timestamp: a[0], + open: a[1].toDouble(), + high: a[2].toDouble(), + low: a[3].toDouble(), + close: a[4].toDouble(), + volume: a[5].toInt(), + openInterest: a[6], + )); + } + + candles = candles.reversed.toList(); + stockDetailsModel = StockDetailsModel( + status: data["status"], + message: data["message"], + statusCode: data["status_code"], + data: Data( + stockData: StockInfo( + ohlc: Ohlc.fromJson(dynamicData['ohlc']), + averagePrice: + dynamicData['average_price'].toDouble(), + instrumentToken: dynamicData['instrument_token'], + lastPrice: dynamicData['last_price'].toDouble(), + lastTradeTime: dynamicData['last_trade_time'], + lowerCircuitLimit: + dynamicData['lower_circuit_limit'].toDouble(), + netChange: dynamicData['net_change'].toDouble(), + oi: dynamicData['oi'], + oiDayHigh: dynamicData['oi_day_high'], + oiDayLow: dynamicData['oi_day_low'], + symbol: dynamicData['symbol'], + timestamp: dynamicData['timestamp'], + totalBuyQuantity: dynamicData['total_buy_quantity'], + totalSellQuantity: + dynamicData['total_sell_quantity'], + upperCircuitLimit: + dynamicData['upper_circuit_limit'], + volume: dynamicData['volume'], ), - const Gap(8), - text12W400( - "${stockDetailsModel!.data!.stockData!.netChange} 1D"), - const Gap(55), - Container( - margin: const EdgeInsets.symmetric(horizontal: 15), - width: Get.width, - height: 220, - color: Colors.transparent, - child: LineChart( - LineChartData( - lineTouchData: LineTouchData( - touchTooltipData: LineTouchTooltipData( - fitInsideHorizontally: true, - fitInsideVertically: true, - tooltipRoundedRadius: 24, - getTooltipItems: ( - List touchedBarSpots, - ) { - return touchedBarSpots.map((barSpot) { - return LineTooltipItem( - "₹ ${Utils.removeDecimal( - Utils.extractPriceFromGraph( - stockDetailsModel! - .data! - .candleStick! - .candles!)[ - barSpot.x.toInt()] - .toStringAsFixed(2), - ).replaceAllMapped( - RegExp( - r'(\d{1,3})(?=(\d{3})+(?!\d))', + candleStick: CandleStick(candles: candles), + optionChain: OptionChain.fromJson( + data["data"]['option_chain']))); + + netChange = dynamicData['net_change'].toString(); + _calculatePercentageChange( + stockDetailsModel!.data!.stockData!.ohlc!.open!, + stockDetailsModel!.data!.stockData!.lastPrice!); + isLoading.value = false; + }); + }); + }, + child: Stack(children: [ + const CommonBlurLeft(), + const CommonBlurRight(), + isLoading.value + ? const Center( + child: CircularProgressIndicator( + color: Color(0xFF0093FF), + ), + ) + : Padding( + padding: const EdgeInsets.all(15.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W700( + stockDetailsModel!.data!.stockData!.symbol!), + const Gap(14), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + text25W600( + "₹${stockDetailsModel!.data!.stockData!.lastPrice!.toString()}"), + Container( + width: 145, + height: 40, + decoration: ShapeDecoration( + color: const Color(0xFF0093FF), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5)), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + "assets/images/svg/option_chain_icon.svg"), + const Gap(5), + text12W600("Option Chain"), + ], + )), + ], + ), + const Gap(8), + Row( + children: [ + text12W400( + netChange.contains("-") + ? "${stockDetailsModel!.data!.stockData!.netChange} ($percentageDifference%)" + : "+${stockDetailsModel!.data!.stockData!.netChange} ($percentageDifference%)", + clr: netChange.contains("-") + ? Colors.redAccent + : netChange == "0.0" + ? Colors.grey + : Colors.greenAccent), + text12W400(" Today"), + ], + ), + const Gap(55), + // Container( + // // margin: const EdgeInsets.symmetric(horizontal: 8), + // width: Get.width, + // height: 220, + // color: Colors.transparent, + // child: SfCartesianChart( + // // palette: [ + // // const Color( + // // 0xFF0093FF, + // // ).withOpacity(.01), + // // const Color( + // // 0xFF0093FF, + // // ).withOpacity(.3), + // // ], + + // tooltipBehavior: _tooltipBehavior, + // plotAreaBorderWidth: 0, + // primaryYAxis: NumericAxis(isVisible: false), + // // Initialize category axis + // primaryXAxis: CategoryAxis(isVisible: false), + // series: >[ + // LineSeries( + // // Bind data source + // dataSource: salesDataV, + // color: Color(0xFF0093FF), + // enableTooltip: true, + // xValueMapper: (SalesData sales, _) => + // sales.year, + // yValueMapper: (SalesData sales, _) => + // sales.sales) + // ]), + // ), + + Container( + margin: + const EdgeInsets.symmetric(horizontal: 15), + width: Get.width, + height: 220, + color: Colors.transparent, + child: LineChart( + LineChartData( + lineTouchData: LineTouchData( + touchTooltipData: LineTouchTooltipData( + fitInsideHorizontally: true, + fitInsideVertically: true, + + tooltipRoundedRadius: + 9, // maxContentWidth: 150, + getTooltipItems: ( + List touchedBarSpots, + ) { + return touchedBarSpots.map((barSpot) { + return LineTooltipItem( + "₹ ${Utils.removeDecimal( + Utils.extractPriceFromGraph( + stockDetailsModel! + .data! + .candleStick! + .candles!)[ + barSpot.x.toInt()] + .toStringAsFixed(2), + )}", + const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, ), - (Match m) => '${m[1]},', - )}", - const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - ), - ); - }).toList(); + // children: [ + // const TextSpan(text: "\n"), + // TextSpan( + // text: Utils + // .extractTimeFromGraph( + // stockDetailsModel! + // .data! + // .candleStick! + // .candles!)[barSpot + // .x + // .toInt()]) + // ] + ); + }).toList(); + }, + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + drawHorizontalLine: false, + horizontalInterval: 4, + getDrawingHorizontalLine: (value) { + return const FlLine( + color: Color( + 0xff37434d, + ), + strokeWidth: 1, + ); + }, + getDrawingVerticalLine: (value) { + return const FlLine( + color: Color( + 0xff0093FF, + ), + strokeWidth: 1, + ); }, ), - ), - gridData: FlGridData( - show: true, - drawVerticalLine: false, - drawHorizontalLine: false, - horizontalInterval: 4, - getDrawingHorizontalLine: (value) { - return const FlLine( - color: Color( - 0xff37434d, - ), - strokeWidth: 1, - ); - }, - getDrawingVerticalLine: (value) { - return const FlLine( - color: Color( - 0xff0093FF, - ), - strokeWidth: 1, - ); - }, - ), - titlesData: const FlTitlesData( - show: false, - ), - borderData: FlBorderData( - show: false, - ), - minX: 0, - maxX: (stockDetailsModel! - .data!.candleStick!.candles!.length - .toDouble()) - - 1, - minY: Utils.extractPriceFromGraph( - stockDetailsModel! - .data!.candleStick!.candles!) - .reduce(min) - .toDouble(), - maxY: Utils.extractPriceFromGraph( - stockDetailsModel! - .data!.candleStick!.candles!) - .reduce(max) - .toDouble(), - lineBarsData: [ - LineChartBarData( - color: const Color(0xFF0093FF), - spots: listData( - Utils.extractPriceFromGraph( - stockDetailsModel! - .data!.candleStick!.candles!), - ), - barWidth: 3, - isStrokeCapRound: true, - dotData: const FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - const Color( - 0xFF0093FF, - ).withOpacity(.01), - const Color( - 0xFF0093FF, - ).withOpacity(.3), - ], - )), + titlesData: const FlTitlesData( + show: false, ), - ], + borderData: FlBorderData( + show: false, + ), + // minX: 0, + // maxX: (stockDetailsModel! + // .data!.candleStick!.candles!.length + // .toDouble()) - + // 1, + // minY: Utils.extractPriceFromGraph( + // stockDetailsModel! + // .data!.candleStick!.candles!) + // .reduce(min) + // .toDouble(), + // maxY: Utils.extractPriceFromGraph( + // stockDetailsModel! + // .data!.candleStick!.candles!) + // .reduce(max) + // .toDouble(), + lineBarsData: [ + LineChartBarData( + color: const Color(0xFF0093FF), + spots: listData( + Utils.extractPriceFromGraph( + stockDetailsModel! + .data!.candleStick!.candles!), + ), + barWidth: 1.3, + isStrokeCapRound: true, + dotData: const FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + const Color( + 0xFF0093FF, + ).withOpacity(.01), + const Color( + 0xFF0093FF, + ).withOpacity(.3), + ], + )), + ), + ], + ), + curve: Curves.linear, + duration: const Duration(milliseconds: 150), ), ), - ), - const Gap(20), - text18W400("Overview"), - const Gap(15), - text20W400("Performance"), - const Gap(22), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W400('Today’s Low', - clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel! - .data!.stockData!.ohlc!.low! - .toString(), - ) - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - text16W400('Today’s High', - clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel! - .data!.stockData!.ohlc!.high! - .toString(), - ) - ], - ) - ], - ), - Gap(40), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W400('Open price', - clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel! - .data!.stockData!.ohlc!.open - .toString(), - ) - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W400('Prev. Close', - clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel! - .data!.candleStick!.candles![0].close - .toString(), - ) - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W400('Volume', clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel!.data!.stockData!.volume - .toString(), - ) - ], - ) - ], - ), - Gap(25), - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W400('Lower Circuit', - clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel! - .data!.stockData!.lowerCircuitLimit - .toString(), - ) - ], - ), - Gap(25), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - text16W400('Upper Circuit', - clr: Color(0xFFF979797)), - Gap(5), - text16W500( - stockDetailsModel! - .data!.stockData!.upperCircuitLimit - .toString(), - ) - ], - ) - ], - ), - Gap(25), - ], + + const Gap(20), + text18W400("Overview"), + const Gap(15), + text20W400("Performance"), + const Gap(22), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W400('Today’s Low', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel! + .data!.stockData!.ohlc!.low! + .toString(), + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + text16W400('Today’s High', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel! + .data!.stockData!.ohlc!.high! + .toString(), + ) + ], + ) + ], + ), + const Gap(40), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W400('Open price', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel! + .data!.stockData!.ohlc!.open + .toString(), + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W400('Prev. Close', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel!.data!.candleStick! + .candles!.last.close + .toString(), + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W400('Volume', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel!.data!.stockData!.volume + .toString(), + ) + ], + ) + ], + ), + const Gap(25), + Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W400('Lower Circuit', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel! + .data!.stockData!.lowerCircuitLimit + .toString(), + ) + ], + ), + const Gap(25), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text16W400('Upper Circuit', + clr: const Color(0xFF979797)), + const Gap(5), + text16W500( + stockDetailsModel! + .data!.stockData!.upperCircuitLimit + .toString(), + ) + ], + ) + ], + ), + const Gap(25), + ], + ), ), ), - ), - ])), + ]), + )), ); } } List listData(List data) { return data.asMap().entries.map((e) { + dv.log("Key ${e.key.toString()} Value ${e.value.toString()}"); return FlSpot(e.key.toDouble(), e.value.toDouble()); }).toList(); } + +class SalesData { + SalesData(this.year, this.sales); + final String year; + final double sales; +} diff --git a/pubspec.lock b/pubspec.lock index c4dd6d0..3ff554c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1085,6 +1085,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + syncfusion_flutter_charts: + dependency: "direct main" + description: + name: syncfusion_flutter_charts + sha256: "5f868f5a82380ef1b3de416b7812e1ac7acce6a629f9281618adee81d90716cc" + url: "https://pub.dev" + source: hosted + version: "21.2.10" syncfusion_flutter_core: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index da0d611..4cf34ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -54,6 +54,7 @@ dependencies: device_info_plus: ^8.2.2 permission_handler: ^11.3.1 fl_chart: ^0.68.0 + syncfusion_flutter_charts: ^21.2.4 dev_dependencies: flutter_test: