From 1b2889a08fbeec3ae1ae106c9f689530edc61baf Mon Sep 17 00:00:00 2001 From: jayesh Date: Wed, 19 Jun 2024 17:47:24 +0530 Subject: [PATCH] small case --- lib/Utils/Common/MainController.dart | 5 +- lib/controller/small_case_controller.dart | 13 + .../SmallCaseModel/holding_data_model.dart | 112 ++ .../MainScreen/Portfolio/PortfolioEmpty.dart | 253 ++-- .../Portfolio/small_case_user_portoflio.dart | 1214 +++++++++++++++++ .../SmallCaseApi/smallcase_api_methods.dart | 59 +- pubspec.lock | 24 +- 7 files changed, 1530 insertions(+), 150 deletions(-) create mode 100644 lib/controller/small_case_controller.dart create mode 100644 lib/model/SmallCaseModel/holding_data_model.dart create mode 100644 lib/view/MainScreen/Portfolio/small_case_user_portoflio.dart diff --git a/lib/Utils/Common/MainController.dart b/lib/Utils/Common/MainController.dart index fc982b5..cb43f21 100644 --- a/lib/Utils/Common/MainController.dart +++ b/lib/Utils/Common/MainController.dart @@ -1,9 +1,10 @@ import 'package:get/get.dart'; import 'package:traderscircuit/view/MainScreen/HomeScreen.dart'; import 'package:traderscircuit/view/MainScreen/PastPerformance.dart'; -import 'package:traderscircuit/view/MainScreen/Portfolio/PortfolioEmpty.dart'; import 'package:traderscircuit/view/MainScreen/ShortTrade.dart'; +import '../../view/MainScreen/Portfolio/small_case_user_portoflio.dart'; + class MainController extends GetxController { var selectedIndex = 0.obs; @@ -11,7 +12,7 @@ class MainController extends GetxController { const HomeScreen(), const ShortTrade(), const PastPerformance(), - const Portfolio(), + const SmallCaseUserPortoflio(), ].obs; void updateTab(int index) { diff --git a/lib/controller/small_case_controller.dart b/lib/controller/small_case_controller.dart new file mode 100644 index 0000000..df1fcb8 --- /dev/null +++ b/lib/controller/small_case_controller.dart @@ -0,0 +1,13 @@ +import 'package:get/get.dart'; +import 'package:traderscircuit/model/SmallCaseModel/broker_account_model.dart'; +import 'package:traderscircuit/model/SmallCaseModel/holding_data_model.dart'; + +class SmallCaseController extends GetxController { + RxInt selectedTabIndex = 0.obs; + RxBool noBrokerAvailable = true.obs; + RxBool holdingDataAvailable = false.obs; + RxBool isLoading = false.obs; + RxBool isHoldingLoading = false.obs; + BrokerAccountModel myBrokerAccounts = BrokerAccountModel(); + HoldingDataModel holdingDataModel = HoldingDataModel(); +} diff --git a/lib/model/SmallCaseModel/holding_data_model.dart b/lib/model/SmallCaseModel/holding_data_model.dart new file mode 100644 index 0000000..834dbcf --- /dev/null +++ b/lib/model/SmallCaseModel/holding_data_model.dart @@ -0,0 +1,112 @@ +class HoldingDataModel { + String? status; + int? statusCode; + String? message; + List? data; + + HoldingDataModel({this.status, this.statusCode, this.message, this.data}); + + HoldingDataModel.fromJson(Map json) { + status = json['status']; + statusCode = json['status_code']; + message = json['message']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data!.add(Data.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = {}; + data['status'] = status; + data['status_code'] = statusCode; + data['message'] = message; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Data { + int? id; + int? iamPrincipalXid; + int? smallCaseUserXid; + String? brokerName; + String? smallcaseAuthId; + String? lastUpdate; + String? snapshotDate; + String? stockName; + String? isin; + String? bseTicker; + String? nseTicker; + int? transactableQuantity; + String? averagePrice; + int? smallcaseQuantity; + int? isActive; + String? createdAt; + String? updatedAt; + + Data( + {this.id, + this.iamPrincipalXid, + this.smallCaseUserXid, + this.brokerName, + this.smallcaseAuthId, + this.lastUpdate, + this.snapshotDate, + this.stockName, + this.isin, + this.bseTicker, + this.nseTicker, + this.transactableQuantity, + this.averagePrice, + this.smallcaseQuantity, + this.isActive, + this.createdAt, + this.updatedAt}); + + Data.fromJson(Map json) { + id = json['id']; + iamPrincipalXid = json['iam_principal_xid']; + smallCaseUserXid = json['small_case_user_xid']; + brokerName = json['broker_name']; + smallcaseAuthId = json['smallcaseAuthId']; + lastUpdate = json['lastUpdate']; + snapshotDate = json['snapshotDate']; + stockName = json['stock_name']; + isin = json['isin']; + bseTicker = json['bseTicker']; + nseTicker = json['nseTicker']; + transactableQuantity = json['transactableQuantity']; + averagePrice = json['average_price']; + smallcaseQuantity = json['smallcaseQuantity']; + isActive = json['is_active']; + createdAt = json['created_at']; + updatedAt = json['updated_at']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['iam_principal_xid'] = iamPrincipalXid; + data['small_case_user_xid'] = smallCaseUserXid; + data['broker_name'] = brokerName; + data['smallcaseAuthId'] = smallcaseAuthId; + data['lastUpdate'] = lastUpdate; + data['snapshotDate'] = snapshotDate; + data['stock_name'] = stockName; + data['isin'] = isin; + data['bseTicker'] = bseTicker; + data['nseTicker'] = nseTicker; + data['transactableQuantity'] = transactableQuantity; + data['average_price'] = averagePrice; + data['smallcaseQuantity'] = smallcaseQuantity; + data['is_active'] = isActive; + data['created_at'] = createdAt; + data['updated_at'] = updatedAt; + return data; + } +} diff --git a/lib/view/MainScreen/Portfolio/PortfolioEmpty.dart b/lib/view/MainScreen/Portfolio/PortfolioEmpty.dart index e285724..c0cca96 100644 --- a/lib/view/MainScreen/Portfolio/PortfolioEmpty.dart +++ b/lib/view/MainScreen/Portfolio/PortfolioEmpty.dart @@ -22,6 +22,8 @@ import '../../../Utils/Common/comonGlassmorphicContainer.dart'; import '../../../model/SmallCaseModel/broker_account_model.dart'; import '../../../view_model/SmallCaseApi/smallcase_api_methods.dart'; +int selectedTabIndex = 0; + class Portfolio extends StatefulWidget { const Portfolio({super.key}); @@ -31,7 +33,6 @@ class Portfolio extends StatefulWidget { class _PortfolioState extends State { final GlobalKey _scaffoldKey1 = GlobalKey(); - List containerTexts = ["Swing Trade", "Multibagger", "Options"]; final selectedIndex = 0.obs; RxBool noBrokerAvailable = true.obs; @@ -43,7 +44,7 @@ class _PortfolioState extends State { fetchUserIdAndBrokerAccounts.add(fetchBrokerAccounts()); fetchUserIdAndBrokerAccounts.close(); fetchUserIdAndBrokerAccounts.future.then((value) { - log(value.toString()); + // log(value.toString()); try { myBrokerAccounts = value[0] as List; @@ -53,7 +54,9 @@ class _PortfolioState extends State { if (myBrokerAccounts.isEmpty) { noBrokerAvailable.value = true; } else { - noBrokerAvailable.value = false; + fetchHoldingForDB(myBrokerAccounts[1].brokerName!).then((value) { + noBrokerAvailable.value = false; + }); } }); @@ -72,25 +75,27 @@ class _PortfolioState extends State { sizedBoxHeight(25.h), GestureDetector( onTap: () { - log(myBrokerAccounts[selectedIndex.value].brokerName!); + log(myBrokerAccounts[selectedTabIndex].brokerName!); ScgatewayFlutterPlugin.setConfigEnvironment( GatewayEnvironment.PRODUCTION, 'traderscircuit', false, - ["upstox"], + [ + myBrokerAccounts.elementAt(selectedTabIndex).brokerName! + ], ); fetchHoldingsImportTxnId(myBrokerAccounts - .elementAt(selectedIndex.value) + .elementAt(selectedTabIndex) .authToken!) .then((txnIdResponse) { if (txnIdResponse != "Failed") { fetchHoldingsImportTxnId(myBrokerAccounts - .elementAt(selectedIndex.value) + .elementAt(selectedTabIndex) .authToken!) .then((txnIdResponse) { debugPrint('SESSION STARTED'); debugPrint( - 'AUTH TOKEN: ${myBrokerAccounts.elementAt(selectedIndex.value).authToken!}'); + 'AUTH TOKEN: ${myBrokerAccounts.elementAt(selectedTabIndex).authToken!}'); String txnId = txnIdResponse; debugPrint('TXN ID $txnId'); ScgatewayFlutterPlugin.triggerGatewayTransaction( @@ -102,7 +107,7 @@ class _PortfolioState extends State { fetchHoldings( //holdingsAuthToken myBrokerAccounts - .elementAt(selectedIndex.value) + .elementAt(selectedTabIndex) .authToken!) .then( (holdings) { @@ -128,7 +133,7 @@ class _PortfolioState extends State { borderRadius: BorderRadius.circular(12), color: const Color(0xff6C0000), ), - child: Center(child: text15W600("Update Stock's")), + child: Center(child: text15W600("Refresh Stock's")), ), ), ), @@ -235,7 +240,7 @@ class _PortfolioState extends State { ) ], ) - : selectedIndex == 1 + : selectedIndex.value == 1 ? const Column( children: [], ) @@ -246,110 +251,77 @@ class _PortfolioState extends State { } void onTxnTimeout() { - bool showDialogContent = true; - bool replaceDialogContentWithLoader = false; showDialog( barrierDismissible: false, context: context, - builder: (context) => AlertDialog(content: StatefulBuilder( - builder: (context, setDialogState) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Visibility( - visible: showDialogContent, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text("Transaction Timeout", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - )), - const SizedBox(height: 18), - const Text("You need to login again to continue"), - const SizedBox(height: 18), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - OutlinedButton( - onPressed: () { - Get.back(); - }, - child: const Text("Cancel"), - ), - const SizedBox(width: 12), - ElevatedButton( - onPressed: () { - setDialogState(() { - showDialogContent = false; - replaceDialogContentWithLoader = true; - }); - //login again - fetchAuthToken().then((fetchedAuthToken) { - debugPrint( - "fetchedAuthToken $fetchedAuthToken"); - fetchBrokerConnectTxnId( - authToken: fetchedAuthToken) - .then( - (txnId) => - ScgatewayFlutterPlugin.initGateway( - fetchedAuthToken, - ).then( - (value) => ScgatewayFlutterPlugin - .triggerGatewayTransaction( - txnId, - ).then( - (loginRes) { - if (loginRes != null) { - var data = - jsonDecode(loginRes)['data']; - if (data != null) { - String authToken = jsonDecode( - data)['smallcaseAuthToken']; - String brokerName = - jsonDecode(data)['broker']; - String txnId = jsonDecode( - data)['transactionId']; - - postBrokerAccount( - brokerName: brokerName, - authToken: authToken, - txnId: txnId) - .then((isPosted) { - if (isPosted) { - Get.back(); - } - }); - } - } - }, - ), - ), - ); - }); - }, - child: const Text("Login Again"), - ), - ], - ) - ], - ), + builder: (context) => AlertDialog( + backgroundColor: const Color(0xFFFFF3E4), + title: const Text( + "Transaction Timeout", + ), + content: const Text("You need to login again to continue"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text( + 'No', + style: TextStyle( + color: Color(0xff1B243D), ), - Visibility( - visible: replaceDialogContentWithLoader, - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 28.0), - child: Center( - child: CircularProgressIndicator(), + ), + ), + TextButton( + onPressed: () { + fetchAuthToken().then((fetchedAuthToken) { + debugPrint("fetchedAuthToken $fetchedAuthToken"); + fetchBrokerConnectTxnId(authToken: fetchedAuthToken).then( + (txnId) => ScgatewayFlutterPlugin.initGateway( + fetchedAuthToken, + ).then( + (value) => + ScgatewayFlutterPlugin.triggerGatewayTransaction( + txnId, + ).then( + (loginRes) { + if (loginRes != null) { + log(loginRes.toString()); + var data = jsonDecode(loginRes)['data']; + if (data != null) { + String authToken = + jsonDecode(data)['smallcaseAuthToken']; + String brokerName = + jsonDecode(data)['broker']; + String txnId = + jsonDecode(data)['transactionId']; + + postBrokerAccount( + brokerName: brokerName, + authToken: authToken, + txnId: txnId) + .then((isPosted) { + if (isPosted) { + Get.back(); + } + }); + } + } + }, + ), ), - ), - ) - ], - ); - }, - ))); + ); + }); + }, + child: const Text( + 'Yes', + style: TextStyle( + color: Color(0xff1B243D), + ), + ), + ) + ], + )); } @override @@ -447,6 +419,33 @@ class _PortfolioState extends State { authToken, txnId: txnId) .then((isPosted) { + fetchUserIdAndBrokerAccounts + .add( + fetchBrokerAccounts()); + fetchUserIdAndBrokerAccounts + .close(); + fetchUserIdAndBrokerAccounts + .future + .then((value) { + log(value.toString()); + + try { + myBrokerAccounts = + value[0] + as List; + } catch (e) {} + debugPrint( + "myBrokerAccounts.length ${myBrokerAccounts.length}"); + + if (myBrokerAccounts + .isEmpty) { + noBrokerAvailable + .value = true; + } else { + noBrokerAvailable + .value = false; + } + }); ScaffoldMessenger.of( context) .clearSnackBars(); @@ -973,7 +972,7 @@ class HoldingsTabBar extends StatelessWidget { indicatorColor: const Color(0xFFFFFFFF), labelColor: Colors.white, unselectedLabelColor: const Color(0xffFFFFFF), - overlayColor: MaterialStateProperty.all(const Color(0xFFFFFFFF)), + overlayColor: WidgetStateProperty.all(const Color(0xFFFFFFFF)), tabs: const [ Tab( text: 'Holdings', @@ -986,7 +985,7 @@ class HoldingsTabBar extends StatelessWidget { } } -class PortfolioTabBar extends StatelessWidget { +class PortfolioTabBar extends StatefulWidget { final int portfolioLength; final List portoflioName; const PortfolioTabBar({ @@ -995,13 +994,35 @@ class PortfolioTabBar extends StatelessWidget { required this.portoflioName, }); - // Set the desired height + @override + State createState() => _PortfolioTabBarState(); +} + +class _PortfolioTabBarState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: widget.portfolioLength, vsync: this); + _tabController.addListener(() { + selectedTabIndex = _tabController.index; + }); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } // Set the desired height @override Widget build(BuildContext context) { return TabBar( tabAlignment: TabAlignment.start, isScrollable: true, + controller: _tabController, dividerColor: Colors.transparent, labelStyle: TextStyle( fontSize: 18.sp, @@ -1013,10 +1034,10 @@ class PortfolioTabBar extends StatelessWidget { indicatorColor: const Color(0xff6C0000), labelColor: Colors.white, unselectedLabelColor: const Color(0xFF464646), - overlayColor: MaterialStateProperty.all(const Color(0xFFFFFFFF)), - tabs: List.generate(portfolioLength, (counter) { + overlayColor: WidgetStateProperty.all(const Color(0xFFFFFFFF)), + tabs: List.generate(widget.portfolioLength, (counter) { return Tab( - text: portoflioName[counter].brokerName, + text: widget.portoflioName[counter].brokerName, ); })); } diff --git a/lib/view/MainScreen/Portfolio/small_case_user_portoflio.dart b/lib/view/MainScreen/Portfolio/small_case_user_portoflio.dart new file mode 100644 index 0000000..0f86d4f --- /dev/null +++ b/lib/view/MainScreen/Portfolio/small_case_user_portoflio.dart @@ -0,0 +1,1214 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:glassmorphism/glassmorphism.dart'; +import 'package:lottie/lottie.dart'; +import 'package:scgateway_flutter_plugin/scgateway_flutter_plugin.dart'; +import 'package:traderscircuit/Utils/Common/CommonBottomNavigation.dart'; +import 'package:traderscircuit/Utils/Common/commonBotton.dart'; +import 'package:traderscircuit/Utils/Common/sized_box.dart'; +import 'package:traderscircuit/Utils/text.dart'; +import 'package:traderscircuit/controller/small_case_controller.dart'; +import 'package:traderscircuit/model/SmallCaseModel/holding_data_model.dart'; +import 'package:traderscircuit/view/MainScreen/MainScreen.dart'; +import 'package:traderscircuit/view/Sidemenu/Sidemenu.dart'; +import 'package:traderscircuit/view/onBoarding/splashScreen1.dart'; + +import '../../../Utils/Common/comonGlassmorphicContainer.dart'; +import '../../../model/SmallCaseModel/broker_account_model.dart'; +import '../../../view_model/SmallCaseApi/smallcase_api_methods.dart'; + +class SmallCaseUserPortoflio extends StatefulWidget { + const SmallCaseUserPortoflio({super.key}); + + @override + State createState() => _PortfolioState(); +} + +class _PortfolioState extends State + with SingleTickerProviderStateMixin { + SmallCaseController smallCaseController = Get.put(SmallCaseController()); + final GlobalKey _scaffoldKey1 = GlobalKey(); + final selectedIndex = 0.obs; + + late TabController tabController; + + @override + void initState() { + smallCaseController.isLoading.value = true; + fetchBrokerAccounts().then((value) { + smallCaseController.myBrokerAccounts = + BrokerAccountModel.fromJson(value.data); + + if (smallCaseController.myBrokerAccounts.data!.isEmpty) { + smallCaseController.noBrokerAvailable.value = true; + } else { + tabController = TabController( + length: smallCaseController.myBrokerAccounts.data!.length, + vsync: this); + + tabController.addListener(() { + smallCaseController.selectedTabIndex.value = tabController.index; + smallCaseController.isHoldingLoading.value = true; + fetchHoldingForDB(smallCaseController + .myBrokerAccounts + .data![smallCaseController.selectedTabIndex.value] + .brokerName!) + .then((value) { + smallCaseController.holdingDataModel = + HoldingDataModel.fromJson(value.data); + + if (smallCaseController.holdingDataModel.data!.isEmpty) { + smallCaseController.holdingDataAvailable.value = false; + } else { + smallCaseController.holdingDataAvailable.value = true; + } + smallCaseController.isHoldingLoading.value = false; + }); + + smallCaseController.isHoldingLoading.value = false; + }); + + fetchHoldingForDB( + smallCaseController.myBrokerAccounts.data![0].brokerName!) + .then((value) { + smallCaseController.holdingDataModel = + HoldingDataModel.fromJson(value.data); + + if (smallCaseController.holdingDataModel.data!.isEmpty) { + smallCaseController.holdingDataAvailable.value = false; + } else { + smallCaseController.holdingDataAvailable.value = true; + } + + smallCaseController.noBrokerAvailable.value = false; + }); + } + smallCaseController.isLoading.value = false; + }); + + super.initState(); + } + + Widget holdings() { + return Obx(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (selectedIndex.value == 1) _unlockbottomsheet(); + }); + return selectedIndex.value == 0 + ? smallCaseController.isHoldingLoading.value + ? const Center( + child: CircularProgressIndicator(), + ) + : !smallCaseController.holdingDataAvailable.value + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "No Data Available", + style: TextStyle(color: Colors.white, fontSize: 22), + ), + sizedBoxHeight(25.h), + GestureDetector( + onTap: () { + smallCaseController.isHoldingLoading.value = true; + // ScgatewayFlutterPlugin.setConfigEnvironment( + // GatewayEnvironment.PRODUCTION, + // 'traderscircuit', + // false, + // [], + // ); + log("AUTH TOKEN ==> ${smallCaseController.myBrokerAccounts.data!.elementAt(smallCaseController.selectedTabIndex.value).authToken!}"); + fetchHoldingsImportTxnId(smallCaseController + .myBrokerAccounts.data! + .elementAt(smallCaseController + .selectedTabIndex.value) + .authToken!) + .then((txnIdResponse) { + if (txnIdResponse != "Failed") { + fetchHoldingsImportTxnId(smallCaseController + .myBrokerAccounts.data! + .elementAt(smallCaseController + .selectedTabIndex.value) + .authToken!) + .then((txnIdResponse) { + debugPrint('SESSION STARTED'); + debugPrint( + 'AUTH TOKEN: ${smallCaseController.myBrokerAccounts.data!.elementAt(smallCaseController.selectedTabIndex.value).authToken!}'); + String txnId = txnIdResponse; + debugPrint('TXN ID $txnId'); + ScgatewayFlutterPlugin + .triggerGatewayTransaction(txnId) + .then( + (txnRes) { + debugPrint('TXN RES $txnRes'); + if (txnRes != null) { + fetchHoldings( + //holdingsAuthToken + smallCaseController + .myBrokerAccounts.data! + .elementAt( + smallCaseController + .selectedTabIndex + .value) + .authToken!) + .then( + (holdings) { + fetchHoldingForDB(smallCaseController + .myBrokerAccounts + .data![smallCaseController + .selectedTabIndex.value] + .brokerName!) + .then((value) { + smallCaseController + .holdingDataModel = + HoldingDataModel.fromJson( + value.data); + + if (smallCaseController + .holdingDataModel + .data! + .isEmpty) { + smallCaseController + .holdingDataAvailable + .value = false; + } else { + smallCaseController + .holdingDataAvailable + .value = true; + } + smallCaseController + .isHoldingLoading + .value = false; + }); + }, + ); + } + }, + ); + }); + } else { + debugPrint('SESSION EXPIRED'); + onTxnTimeout(); + } + smallCaseController.isHoldingLoading.value = + false; + }); + }, + child: Align( + alignment: Alignment.center, + child: Container( + width: 180, + height: 52, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: const Color(0xff6C0000), + ), + child: Center(child: text15W600("Add Stock's")), + ), + ), + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + sizedBoxHeight(25.h), + GestureDetector( + onTap: () { + smallCaseController.isHoldingLoading.value = true; + // ScgatewayFlutterPlugin.setConfigEnvironment( + // GatewayEnvironment.PRODUCTION, + // 'traderscircuit', + // false, + // [ + // smallCaseController.myBrokerAccounts.data! + // .elementAt(smallCaseController + // .selectedTabIndex.value) + // .brokerName! + // ], + // ); + fetchHoldingsImportTxnId(smallCaseController + .myBrokerAccounts.data! + .elementAt(smallCaseController + .selectedTabIndex.value) + .authToken!) + .then((txnIdResponse) { + if (txnIdResponse != "Failed") { + fetchHoldingsImportTxnId(smallCaseController + .myBrokerAccounts.data! + .elementAt(smallCaseController + .selectedTabIndex.value) + .authToken!) + .then((txnIdResponse) { + debugPrint('SESSION STARTED'); + debugPrint( + 'AUTH TOKEN: ${smallCaseController.myBrokerAccounts.data!.elementAt(smallCaseController.selectedTabIndex.value).authToken!}'); + String txnId = txnIdResponse; + debugPrint('TXN ID $txnId'); + ScgatewayFlutterPlugin + .triggerGatewayTransaction(txnId) + .then( + (txnRes) { + debugPrint('TXN RES $txnRes'); + if (txnRes != null) { + fetchHoldings( + //holdingsAuthToken + smallCaseController + .myBrokerAccounts.data! + .elementAt( + smallCaseController + .selectedTabIndex + .value) + .authToken!) + .then( + (holdings) { + fetchHoldingForDB(smallCaseController + .myBrokerAccounts + .data![smallCaseController + .selectedTabIndex.value] + .brokerName!) + .then((value) { + smallCaseController + .holdingDataModel = + HoldingDataModel.fromJson( + value.data); + + if (smallCaseController + .holdingDataModel + .data! + .isEmpty) { + smallCaseController + .holdingDataAvailable + .value = false; + } else { + smallCaseController + .holdingDataAvailable + .value = true; + } + smallCaseController + .isHoldingLoading + .value = false; + }); + }, + ); + } + }, + ); + }); + } else { + debugPrint('SESSION EXPIRED'); + onTxnTimeout(); + } + smallCaseController.isHoldingLoading.value = + false; + }); + }, + child: Align( + alignment: Alignment.centerRight, + child: Container( + width: 180, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: const Color(0xff6C0000), + ), + child: + Center(child: text15W600("Refresh Stock's")), + ), + ), + ), + sizedBoxHeight(25.h), + Table( + children: [ + TableRow( + children: [ + TableCell( + child: Text( + "Stock Name", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + ), + ), + TableCell( + child: Text( + "AVG Price", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + ), + ), + TableCell( + child: Text( + "Quantity", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + ), + ), + ], + ), + ], + ), + const Divider( + color: Color(0xFF3A3A3A), + ), + Expanded( + child: ListView.separated( + physics: const BouncingScrollPhysics(), + shrinkWrap: true, + itemCount: smallCaseController + .holdingDataModel.data!.length, + itemBuilder: ((context, index) { + return Table( + children: [ + TableRow( + children: [ + TableCell( + child: Text( + smallCaseController.holdingDataModel + .data![index].stockName!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + ), + ), + TableCell( + child: Text( + smallCaseController.holdingDataModel + .data![index].averagePrice!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + ), + ), + TableCell( + child: Text( + smallCaseController.holdingDataModel + .data![index].transactableQuantity + .toString(), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + ), + ), + ], + ), + ], + ); + }), + separatorBuilder: + (BuildContext context, int index) { + return const Divider(); + }, + ), + ), + SizedBox( + height: 30.h, + ) + ], + ) + : selectedIndex.value == 1 + ? const Column( + children: [], + ) + : const Column( + children: [], + ); + }); + } + + void onTxnTimeout() { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AlertDialog( + backgroundColor: const Color(0xFFFFF3E4), + title: const Text( + "Transaction Timeout", + ), + content: const Text("You need to login again to continue"), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text( + 'No', + style: TextStyle( + color: Color(0xff1B243D), + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + smallCaseController.isLoading.value = true; + + fetchAuthToken().then((fetchedAuthToken) { + debugPrint("fetchedAuthToken $fetchedAuthToken"); + fetchBrokerConnectTxnId(authToken: fetchedAuthToken).then( + (txnId) => ScgatewayFlutterPlugin.initGateway( + fetchedAuthToken, + ).then( + (value) => + ScgatewayFlutterPlugin.triggerGatewayTransaction( + txnId, + ).then( + (loginRes) { + if (loginRes != null) { + log(loginRes.toString()); + var data = jsonDecode(loginRes)['data']; + if (data != null) { + String authToken = + jsonDecode(data)['smallcaseAuthToken']; + String brokerName = + jsonDecode(data)['broker']; + String txnId = + jsonDecode(data)['transactionId']; + + postBrokerAccount( + brokerName: brokerName, + authToken: authToken, + txnId: txnId) + .then((isPosted) { + fetchBrokerAccounts().then((value) { + smallCaseController.myBrokerAccounts = + BrokerAccountModel.fromJson( + value.data); + + fetchHoldingForDB(smallCaseController + .myBrokerAccounts + .data![smallCaseController + .selectedTabIndex.value] + .brokerName!) + .then((value) { + smallCaseController.holdingDataModel = + HoldingDataModel.fromJson( + value.data); + + if (smallCaseController + .holdingDataModel.data!.isEmpty) { + smallCaseController + .holdingDataAvailable + .value = false; + } else { + smallCaseController + .holdingDataAvailable + .value = true; + } + }); + + smallCaseController.isLoading.value = + false; + }); + }); + } + } + }, + ), + ), + ); + }); + }, + child: const Text( + 'Yes', + style: TextStyle( + color: Color(0xff1B243D), + ), + ), + ) + ], + )); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + _onBackButtonPressed(context); + return true; // Return true to allow the pop action + }, + child: Scaffold( + key: _scaffoldKey1, + backgroundColor: Colors.black, + drawerEnableOpenDragGesture: false, + drawer: const SideMenu(), + extendBody: true, + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Colors.black, + elevation: 0, + automaticallyImplyLeading: false, + titleSpacing: 0, + leading: InkWell( + onTap: () { + _scaffoldKey1.currentState?.openDrawer(); + }, + child: Center( + child: Image.asset( + 'assets/images/png/menu.png', + height: 15.h, + width: 20.w, + ), + ), + ), + ), + body: Obx( + () => Stack( + children: [ + const CommonBlurLeft(), + const CommonBlurRight(), + smallCaseController.isLoading.value + ? const Center(child: CircularProgressIndicator()) + : !smallCaseController.noBrokerAvailable.value + ? Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 16), + child: ListView( + physics: const NeverScrollableScrollPhysics(), + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + text25W600("My Portfolio"), + GestureDetector( + onTap: () { + // ScgatewayFlutterPlugin + // .setConfigEnvironment( + // GatewayEnvironment.PRODUCTION, + // 'traderscircuit', + // false, + // [], + // ); + fetchAuthToken() + .then((fetchedAuthToken) { + debugPrint( + "fetchedAuthToken $fetchedAuthToken"); + fetchBrokerConnectTxnId( + authToken: fetchedAuthToken) + .then( + (txnId) => ScgatewayFlutterPlugin + .initGateway( + fetchedAuthToken) + .then( + (value) => ScgatewayFlutterPlugin + .triggerGatewayTransaction( + txnId, + ).then( + (loginRes) { + log(loginRes.toString()); + if (loginRes != null) { + var data = jsonDecode( + loginRes)['data']; + if (data != null) { + String authToken = + jsonDecode(data)[ + 'smallcaseAuthToken']; + String brokerName = + jsonDecode( + data)['broker']; + String txnId = + jsonDecode(data)[ + 'transactionId']; + + postBrokerAccount( + brokerName: + brokerName, + authToken: + authToken, + txnId: txnId) + .then((isPosted) { + ScaffoldMessenger.of( + context) + .clearSnackBars(); + ScaffoldMessenger.of( + context) + .showSnackBar( + const SnackBar( + content: Text( + 'New broker account is added'))); + }); + } + } + }, + ), + ), + ); + }); + setState(() {}); + }, + child: Container( + width: 80, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(12), + color: const Color(0xff6C0000), + ), + child: + Center(child: text15W600("ADD")), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + DefaultTabController( + length: smallCaseController + .myBrokerAccounts.data!.length, + // initialIndex: selectedIndex.value, + child: Column( + children: [ + PortfolioTabBar( + portfolioLength: smallCaseController + .myBrokerAccounts.data!.length, + portoflioName: smallCaseController + .myBrokerAccounts, + tabController: tabController, + ), + SizedBox( + height: 30.h, + ), + SizedBox( + height: 570.h, + child: TabBarView( + controller: tabController, + children: List.generate( + smallCaseController + .myBrokerAccounts + .data! + .length, (count) { + return DefaultTabController( + length: 2, + child: Column( + children: [ + const HoldingsTabBar(), + Expanded( + child: TabBarView( + children: [ + holdings(), + PortfolioReview(), + ], + ), + ), + ], + ), + ); + }), + ), + ), + ], + ), + ), + sizedBoxHeight(40.h), + ], + ), + ), + ], + ) + : Stack( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 16), + child: Column( + children: [ + Row( + children: [ + text25W600("My Portfolio"), + ], + ), + SizedBox( + height: 30.h, + ), + Text( + 'Please click the "Add" button below to add a portfolio.', + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'hiragino', + color: Colors.white, + fontSize: 16.sp, + fontWeight: FontWeight.w400, + ), + ), + const Spacer(), + LottieBuilder.asset( + "assets/images/png/TAdlX7YnR7 (1).json", + width: 200.w, + height: 200.h, + ), + const Spacer(), + CommonBtn( + text: "Add", + onTap: () { + fetchAuthToken() + .then((fetchedAuthToken) { + debugPrint( + "fetchedAuthToken $fetchedAuthToken"); + fetchBrokerConnectTxnId( + authToken: fetchedAuthToken) + .then( + (txnId) => ScgatewayFlutterPlugin + .initGateway( + fetchedAuthToken) + .then( + (value) => ScgatewayFlutterPlugin + .triggerGatewayTransaction( + txnId, + ).then( + (loginRes) { + log(loginRes.toString()); + if (loginRes != null) { + var data = jsonDecode( + loginRes)['data']; + if (data != null) { + String authToken = + jsonDecode(data)[ + 'smallcaseAuthToken']; + String brokerName = + jsonDecode( + data)['broker']; + String txnId = + jsonDecode(data)[ + 'transactionId']; + + postBrokerAccount( + brokerName: + brokerName, + authToken: + authToken, + txnId: txnId) + .then((isPosted) { + ScaffoldMessenger.of( + context) + .clearSnackBars(); + ScaffoldMessenger.of( + context) + .showSnackBar( + const SnackBar( + content: Text( + 'New broker account is added'))); + }); + } + } + }, + ), + ), + ); + }); + }), + const Spacer(), + ], + ), + ), + ], + ) + ], + ), + ), + bottomNavigationBar: bottomnavigationbar(mainController), + ), + ); + } + + Widget topContainer(String text, int index) { + return Obx(() { + return selectedIndex.value == index + ? Container( + height: 40.h, + width: 126.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: const Color(0Xff6C0000), + ), + child: Center(child: text16W500(text)), + ) + : commonGlassContainer( + width: 126.w, + height: 40.h, + borderradius: 5, + customWidget: Center(child: text16W400(text)), + ); + }); + } + + void _unlockbottomsheet() { + Get.bottomSheet( + SizedBox( + height: 200, + child: commonGlassContainer( + width: double.infinity, + height: 200, + borderradius: 2, + customWidget: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image.asset( + // 'assets/images/png/Group 1000003722.png', + // height: 100.h, + // ), + // sizedBoxHeight(25.h), + text20W400('Please subscribed to unlock'), + sizedBoxHeight(30.h), + CommonBtn(text: 'Subscribe Now'), + ], + ), + ), + ), + backgroundColor: Colors.black.withOpacity(0.3), + ); + } + + Widget PortfolioReview() { + List> cardSwing = [ + { + 'text': 'Performance Overview:', + 'content': + 'Your portfolio has generated a total return of 15% over the past six months, outperforming the S&P 500 index by 5%.', + }, + { + 'text': 'Asset Allocation:', + 'content': + 'Your portfolio is well-diversified, with 60% allocated to equities, 30% to bonds, and 10% to cash equivalents.', + }, + { + 'text': 'Individual Holdings Analysis:', + 'content': + 'Your investment in Company XYZ has performed exceptionally well, with a 25% increase in share price since purchase, driven by strong quarterly earnings.', + }, + ]; + + return SingleChildScrollView( + child: Column( + children: [ + sizedBoxHeight(20.h), + Obx(() { + return selectedIndex.value == 0 + ? Column( + children: List.generate(cardSwing.length, (index) { + return Column( + children: [ + PortfolioCard( + text: cardSwing[index]['text']!, + content: cardSwing[index]['content']!, + ), + sizedBoxHeight(20.h) + ], + ); + }), + ) + : selectedIndex.value == 1 + ? Column( + children: List.generate(cardSwing.length, (index) { + return Column( + children: [ + PortfolioCard( + text: cardSwing[index]['text']!, + content: cardSwing[index]['content']!, + ), + sizedBoxHeight(20.h) + ], + ); + }), + ) + : Column( + children: List.generate(cardSwing.length, (index) { + return Column( + children: [ + PortfolioCard( + text: cardSwing[index]['text']!, + content: cardSwing[index]['content']!, + ), + sizedBoxHeight(20.h) + ], + ); + }), + ); + }), + sizedBoxHeight(200.h) + ], + ), + ); + } + + Widget PortfolioCard({ + required String text, + required String content, + }) { + return commonGlassContainer( + width: double.infinity, + height: 166.h, + borderradius: 8, + customWidget: Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 16.w), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: const Color(0xFF6C0000), + borderRadius: BorderRadius.circular(2), + ), + height: 25.h, + width: 4.w, + ), + sizedBoxWidth(15.w), + text18W600(text), + const Spacer(), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 18), + child: text16W400(content), + ) + ], + ), + ); + } + + Widget HoldingCard({ + required String text, + required String content, + }) { + return commonGlassContainer( + width: double.infinity, + height: 166.h, + borderradius: 8, + customWidget: Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 16.w), + child: Row( + children: [ + GlassmorphicContainer( + width: 47.w, + height: 47.h, + borderRadius: 100, + blur: 10, + alignment: Alignment.center, + border: 0.9, + linearGradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xff3A3A3A), + Color(0xFF3A3A3A), + ], + ), + borderGradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color.fromRGBO(70, 5, 1, 0.8), + Color.fromRGBO(102, 102, 102, 0.8), + ], + ), + child: Center( + child: Image.asset( + 'assets/images/png/TATAMOTORS.NS_BIG 1.png', + width: 26.w, + height: 23.h, + ), + ), + ), + sizedBoxWidth(15.w), + text18W600(text), + const Spacer(), + ], + ), + ), + Container( + width: double.infinity, + height: 1.h, + color: const Color(0xFF3A3A3A), + ), + Padding( + padding: const EdgeInsets.all(18), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text14W400_979797('Portfolio Value'), + sizedBoxHeight(5.h), + SizedBox(width: 150.w, child: text15W600("₹ 40,000")) + ], + ), + sizedBoxWidth(30.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + text14W400_979797('%P/L'), + sizedBoxHeight(5.h), + text14W400_00FF19("-36.006%") + ], + ) + ], + ), + ], + ), + ) + ], + ), + ); + } + + Future _onBackButtonPressed(BuildContext context) async { + bool? exitApp = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + backgroundColor: const Color(0xFFFFF3E4), + title: const Text('Exit App'), + content: const Text('Do you really want to close the app?'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text( + 'No', + style: TextStyle( + color: Color(0xff1B243D), + ), + ), + ), + TextButton( + onPressed: () { + SystemNavigator.pop(); + Navigator.pop(context); + }, + child: const Text( + 'Yes', + style: TextStyle( + color: Color(0xff1B243D), + ), + ), + ) + ], + ); + }); + return exitApp ?? false; + } +} + +class HoldingsTabBar extends StatelessWidget { + const HoldingsTabBar({super.key}); + + // Set the desired height + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all(color: const Color(0Xff3A3A3A)), + borderRadius: BorderRadius.circular(8.r), + ), + padding: const EdgeInsets.all(8.0), // Set the desired padding + child: TabBar( + indicator: BoxDecoration( + color: const Color(0xff6C0000), + borderRadius: BorderRadius.circular(5), + ), + dividerColor: Colors.transparent, + labelStyle: TextStyle( + fontSize: 16.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + indicatorSize: TabBarIndicatorSize.tab, + indicatorColor: const Color(0xFFFFFFFF), + labelColor: Colors.white, + unselectedLabelColor: const Color(0xffFFFFFF), + overlayColor: WidgetStateProperty.all(const Color(0xFFFFFFFF)), + tabs: const [ + Tab( + text: 'Holdings', + ), + Tab( + text: 'Portfolio Reviews', + ), + ]), + ); + } +} + +class PortfolioTabBar extends StatefulWidget { + final int portfolioLength; + final BrokerAccountModel portoflioName; + final TabController tabController; + const PortfolioTabBar({ + super.key, + required this.portfolioLength, + required this.portoflioName, + required this.tabController, + }); + + @override + State createState() => _PortfolioTabBarState(); +} + +class _PortfolioTabBarState extends State + with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return TabBar( + tabAlignment: TabAlignment.start, + isScrollable: true, + controller: widget.tabController, + dividerColor: Colors.transparent, + labelStyle: TextStyle( + fontSize: 18.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + fontFamily: 'hiragino'), + indicatorSize: TabBarIndicatorSize.tab, + indicatorWeight: 2, + indicatorColor: const Color(0xff6C0000), + labelColor: Colors.white, + unselectedLabelColor: const Color(0xFF464646), + overlayColor: WidgetStateProperty.all(const Color(0xFFFFFFFF)), + tabs: List.generate(widget.portfolioLength, (counter) { + return Tab( + text: widget.portoflioName.data![counter].brokerName, + ); + })); + } +} diff --git a/lib/view_model/SmallCaseApi/smallcase_api_methods.dart b/lib/view_model/SmallCaseApi/smallcase_api_methods.dart index b943ae7..b858e8a 100644 --- a/lib/view_model/SmallCaseApi/smallcase_api_methods.dart +++ b/lib/view_model/SmallCaseApi/smallcase_api_methods.dart @@ -6,7 +6,6 @@ import 'package:http/http.dart' as http; import '../../Utils/api_urls.dart'; import '../../Utils/base_manager.dart'; import '../../data/network/network_api_services.dart'; -import '../../model/SmallCaseModel/broker_account_model.dart'; // void openDashboardPage(BuildContext context) { // Navigator.pushReplacement( @@ -58,25 +57,6 @@ Future postBrokerAccount({ } //fetch broker accounts -Future> fetchBrokerAccounts() async { - final response = await NetworkApiServices().getApi( - '${ApiUrls.base}get-broker-account-data', - ); - // log(response.data.toString()); - if (response.status == ResponseStatus.SUCCESS) { - Map responseData = - Map.from(response.data); - if (responseData['status'] == "success") { - BrokerAccountModel brokerAccountModel = - BrokerAccountModel.fromJson(responseData); - - return brokerAccountModel.data!; - } else { - return []; - } - } - return []; -} Future fetchAuthToken() async { final response = await NetworkApiServices().getApi( @@ -95,6 +75,45 @@ Future fetchAuthToken() async { return "Failed"; } +Future> fetchBrokerAccounts() async { + final response = await NetworkApiServices().getApi( + '${ApiUrls.base}get-broker-account-data', + ); + log(response.data.toString()); + + if (response.status == ResponseStatus.SUCCESS) { + Map responseData = + Map.from(response.data); + if (responseData['status'] == "success") { + return response; + } else { + return ResponseData( + responseData['message'], ResponseStatus.FAILED); + } + } + return response; +} + +Future> fetchHoldingForDB(String brokerName) async { + final response = await NetworkApiServices().postApi( + FormData.fromMap({"broker_name": brokerName}), + '${ApiUrls.base}get-all-details-of-my-portfolio', + ); + log(response.data.toString()); + + if (response.status == ResponseStatus.SUCCESS) { + Map responseData = + Map.from(response.data); + if (responseData['status'] == "success") { + return response; + } else { + return ResponseData( + responseData['message'], ResponseStatus.FAILED); + } + } + return response; +} + Future fetchBrokerConnectTxnId({required String authToken}) async { final response = await NetworkApiServices().postApi( FormData.fromMap({"auth_token": authToken}), diff --git a/pubspec.lock b/pubspec.lock index de4ab81..98f6d2a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -804,26 +804,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -916,10 +916,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mime: dependency: transitive description: @@ -1361,10 +1361,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" timeago: dependency: "direct main" description: @@ -1538,10 +1538,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" wakelock: dependency: transitive description: