Merge pull request #64 from WDI-Ideas/jayeshjain25

added stock details screen and api integration
This commit is contained in:
Jayesh jain
2024-05-17 18:55:41 +05:30
committed by GitHub
6 changed files with 460 additions and 277 deletions

View File

@@ -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'),
);

View File

@@ -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<String> extractTimeFromGraph(List<Candles> data) {
final List<String> 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(

View File

@@ -149,7 +149,7 @@ class Ohlc {
Ohlc.fromJson(Map<String, dynamic> json) {
open = json['open'].toDouble();
high = json['high'].toDouble();
low = json['low'];
low = json['low'].toDouble();
close = json['close'].toDouble();
}

View File

@@ -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<StockDetailsScreen> {
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<StockDetailsScreen> {
openInterest: a[6],
));
}
candles = candles.reversed.toList();
stockDetailsModel = StockDetailsModel(
status: data["status"],
message: data["message"],
@@ -61,12 +65,13 @@ class _StockDetailsScreenState extends State<StockDetailsScreen> {
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<StockDetailsScreen> {
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<StockDetailsScreen> {
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<String, dynamic> data = value.data;
Map<String, dynamic> data1 =
data["data"]['stock_data']['data'];
String dynamicKey = data1.keys.first;
// Access nested data using dynamic key
Map<String, dynamic> dynamicData = data1[dynamicKey];
List<Candles>? 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<LineBarSpot> 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<SalesData, String>>[
// LineSeries<SalesData, String>(
// // 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<LineBarSpot> 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('Todays Low',
clr: Color(0xFFF979797)),
Gap(5),
text16W500(
stockDetailsModel!
.data!.stockData!.ohlc!.low!
.toString(),
)
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
text16W400('Todays 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('Todays Low',
clr: const Color(0xFF979797)),
const Gap(5),
text16W500(
stockDetailsModel!
.data!.stockData!.ohlc!.low!
.toString(),
)
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
text16W400('Todays 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<FlSpot> listData(List<num> 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;
}

View File

@@ -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:

View File

@@ -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: