diff --git a/lib/application/theme/color_palette.dart b/lib/application/theme/color_palette.dart index a21c90c..d8c5704 100644 --- a/lib/application/theme/color_palette.dart +++ b/lib/application/theme/color_palette.dart @@ -73,9 +73,11 @@ class ColorPalette { static const Color chathamsBlue = Color(0xFF285BB9); static const Color background = Color(0xFFDADADA); static const Color backgroundBlueLight = Color(0xFFEBF3FD); + static const Color blue50 = Color(0xFFEFF6FF); static const Color blue200 = Color(0xFFBFDBFE); static const Color slate50 = Color(0xFFF8FAFC); static const Color slate200 = Color(0xFFE2E8F0); + static const Color slate300 = Color(0xFFCBD5E1); static const Color slate400 = Color(0xFF94A3B8); static const Color slate500 = Color(0xFF64748B); static const Color slate800 = Color(0xFF1E293B); diff --git a/lib/core/utils/date_formatter.dart b/lib/core/utils/date_formatter.dart new file mode 100644 index 0000000..94c0a90 --- /dev/null +++ b/lib/core/utils/date_formatter.dart @@ -0,0 +1,3 @@ +class DateFormatter { + +} \ No newline at end of file diff --git a/lib/core/utils/number_formatter.dart b/lib/core/utils/number_formatter.dart new file mode 100644 index 0000000..ee0e22c --- /dev/null +++ b/lib/core/utils/number_formatter.dart @@ -0,0 +1,19 @@ +import 'package:intl/intl.dart'; + +class NumberFormatter { + NumberFormatter._(); + + static String numberCurrency(dynamic value, String symbol, String locale, {int? decimalDigits = 0}) { + NumberFormat numberFormat = NumberFormat.currency(locale: locale, symbol: symbol, decimalDigits: decimalDigits); + String formatValue = numberFormat.format(value); + + return formatValue; + } + + static compactCurrency(dynamic value, String symbol, String locale) { + NumberFormat numberFormat = NumberFormat.compactCurrency(locale: locale, symbol: symbol); + String formatValue = numberFormat.format(value); + + return formatValue; + } +} \ No newline at end of file diff --git a/lib/features/dashboard/dashboard_account/view/homepage/homepage_view.dart b/lib/features/dashboard/dashboard_account/view/homepage/homepage_view.dart index 258b3ea..8b35382 100644 --- a/lib/features/dashboard/dashboard_account/view/homepage/homepage_view.dart +++ b/lib/features/dashboard/dashboard_account/view/homepage/homepage_view.dart @@ -334,7 +334,7 @@ class _HomeViewState extends State { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xffEFF6FF), + color: ColorPalette.blue50, borderRadius: BorderRadius.circular(12) ), child: const Column( diff --git a/lib/features/dashboard/dashboard_account/view/product/product_chart_view.dart b/lib/features/dashboard/dashboard_account/view/product/product_chart_view.dart new file mode 100644 index 0000000..186e06e --- /dev/null +++ b/lib/features/dashboard/dashboard_account/view/product/product_chart_view.dart @@ -0,0 +1,325 @@ +import 'package:cims_apps/application/component/text_title/text_title.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/number_formatter.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'dart:math'; +import 'package:intl/intl.dart'; + +class ProductChartView extends StatefulWidget { + final String tabType; + final String lastTime; + final List dataChart; + + const ProductChartView({ + super.key, + required this.tabType, + required this.lastTime, + required this.dataChart, + }); + + @override + State createState() => _ProductChartViewState(); +} + +class _ProductChartViewState extends State { + bool isShowLabel = true; + DateTime dateTime = DateTime.now(); + double currentPrice = 0; + int spotIndicator = 0; + List showingTooltipOnSpots = [1, 3, 5]; + List spots = []; + + double dataHighest = 0; + double dataLowest = 0; + + int indexHighestValue = 0; + int indexLowestValue = 0; + + @override + void initState() { + spots = widget.dataChart.asMap().entries.map((e) { + return FlSpot(double.parse(e.key.toString()), e.value); + }).toList(); + dataHighest = widget.dataChart.reduce(max); + dataLowest = widget.dataChart.reduce(min); + currentPrice = dataHighest; + indexHighestValue = widget.dataChart.indexWhere((element) => element == dataHighest); + indexLowestValue = widget.dataChart.indexWhere((element) => element == dataLowest); + // TODO: implement initState + super.initState(); + } + + @override + void didUpdateWidget(covariant ProductChartView oldWidget) { + // TODO: implement didUpdateWidget + spots = widget.dataChart.asMap().entries.map((e) { + return FlSpot(double.parse(e.key.toString()), e.value); + }).toList(); + dataHighest = widget.dataChart.reduce(max); + dataLowest = widget.dataChart.reduce(min); + currentPrice = dataHighest; + indexHighestValue = widget.dataChart.indexWhere((element) => element == dataHighest); + indexLowestValue = widget.dataChart.indexWhere((element) => element == dataLowest); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Wrap( + spacing: 8, + children: [ + Text( + widget.tabType, + style: TextStyle( + color: ColorPalette.slate400, + fontWeight: FontWeight.w700 + ), + ), + Text( + DateFormat('dd MMM yyyy').format(dateTime), + style: TextStyle( + color: ColorPalette.slate300 + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: TextTitle(title: NumberFormatter.numberCurrency(currentPrice, 'Rp ', 'id_ID')), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 8, + children: [ + Icon( + Icons.trending_up_outlined, + size: 18, + color: ColorPalette.green500, + ), + Text( + 'Rp20,30 (+3,88%)', + style: TextStyle( + color: ColorPalette.green500, + fontWeight: FontWeight.w600 + ), + ), + Text( + 'Last ${widget.lastTime}', + style: TextStyle( + color: ColorPalette.slate300, + fontWeight: FontWeight.w600 + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 48), + child: AspectRatio( + aspectRatio: 2.5, + child: LayoutBuilder( + builder: (context, constraints) { + return LineChart( + LineChartData( + // showingTooltipIndicators: showingTooltipOnSpots.map((index) { + // return ShowingTooltipIndicators([ + // LineBarSpot( + // tooltipsOnBar, + // lineBarsData.indexOf(tooltipsOnBar), + // tooltipsOnBar.spots[index], + // ), + // ]); + // }).toList(), + extraLinesData: ExtraLinesData( + verticalLines: [ + VerticalLine( + color: Colors.transparent, + x: indexHighestValue.toDouble(), + label: VerticalLineLabel( + show: spots.length < 7 ? false : isShowLabel, + padding: EdgeInsets.only(bottom: 20, top: -25), + alignment: (indexHighestValue / widget.dataChart.length) < 0.25 ? Alignment.topRight : (indexHighestValue / widget.dataChart.length) > 0.75 ? Alignment.topLeft : Alignment.topCenter, + style: const TextStyle( + color: ColorPalette.slate400, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + labelResolver: (p0) => NumberFormatter.numberCurrency(dataHighest, 'Rp ', 'id_ID'), + ) + ), + VerticalLine( + color: Colors.transparent, + x: indexLowestValue.toDouble(), + label: VerticalLineLabel( + show: spots.length < 7 ? false : isShowLabel, + padding: EdgeInsets.only(bottom: -5, top: 20), + alignment: (indexLowestValue / widget.dataChart.length ) < 0.25 ? Alignment.bottomRight : (indexLowestValue / widget.dataChart.length ) > 0.75 ? Alignment.bottomLeft : Alignment.bottomCenter, + style: const TextStyle( + color: ColorPalette.slate400, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + labelResolver: (p0) => NumberFormatter.numberCurrency(dataLowest, 'Rp ', 'id_ID'), + ) + ) + ] + ), + lineTouchData: LineTouchData( + getTouchLineEnd: (barData, spotIndex) => double.infinity, + getTouchedSpotIndicator: (barData, spotIndexes) { + return spotIndexes.map((spotIndex) { + return TouchedSpotIndicatorData( + const FlLine(strokeWidth: 1, color: ColorPalette.primary), + FlDotData( + getDotPainter: (spot, percent, barData, index) => + FlDotCirclePainter( + radius: 1, + color: ColorPalette.white, + strokeColor: ColorPalette.primary, + strokeWidth: 2, + ), + ), + ); + }).toList(); + }, + touchCallback: (FlTouchEvent event, LineTouchResponse? response) { + if (response == null || response.lineBarSpots == null) { + setState(() { + isShowLabel = true; + currentPrice = widget.dataChart.last; + dateTime = DateTime.now(); + }); + return; + } + double? spotIndex = response.lineBarSpots?[0].x; + if(spotIndex != null){ + int absIndex = (spotIndex.toInt() - (spots.length - 1)).abs(); + setState(() { + isShowLabel = false; + spotIndicator = spotIndex.toInt(); + currentPrice = widget.dataChart[spotIndex.toInt()]; + dateTime = DateTime.now().subtract(Duration(days: absIndex)); + }); + } + }, + touchTooltipData: LineTouchTooltipData( + tooltipBgColor: Colors.transparent, + tooltipPadding: EdgeInsets.all(0), + fitInsideHorizontally: true, + showOnTopOfTheChartBoxArea: true, + getTooltipItems: (touchedSpots) { + return touchedSpots.map((LineBarSpot touchedSpot) { + final textStyle = TextStyle( + color: ColorPalette.slate500, + fontWeight: FontWeight.bold, + fontSize: 12, + ); + return LineTooltipItem( + DateFormat('dd MMM yyyy').format(dateTime), + textStyle, + ); + }).toList(); + }, + ), + handleBuiltInTouches: true, + ), + lineBarsData: [ + LineChartBarData( + color: ColorPalette.primary, + spots: spots, + isCurved: true, + preventCurveOverShooting: true, + isStrokeCapRound: true, + isStrokeJoinRound: true, + barWidth: 1.2, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xFF5B8FF9), + Colors.white + ] + ), + spotsLine: BarAreaSpotsLine( + show: true, + applyCutOffY: true, + flLineStyle: FlLine( + color: ColorPalette.primary, + strokeWidth: 1, + ), + checkToShowSpotLine: (spot) { + if(spot.x == spotIndicator && !isShowLabel){ + return true; + } + return false; + }, + ) + ), + dotData: FlDotData( + show: true, + getDotPainter: (p0, p1, p2, p3) { + return FlDotCirclePainter( + radius: 1, + color: ColorPalette.white, + strokeColor: ColorPalette.primary, + strokeWidth: 2, + ); + }, + checkToShowDot: (spot, barData) { + if(spot.x == spotIndicator && !isShowLabel){ + return true; + } + return false; + }, + ), + ), + ], + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 56, + ), + drawBelowEverything: true, + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 36, + interval: 1, + ), + drawBelowEverything: true, + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + gridData: FlGridData( + show: false + ), + borderData: FlBorderData(show: false), + ), + ); + }, + ), + ), + ), + ], + ); + } +} diff --git a/lib/features/dashboard/dashboard_account/view/product/product_view.dart b/lib/features/dashboard/dashboard_account/view/product/product_view.dart index 0c6aac7..0b003c2 100644 --- a/lib/features/dashboard/dashboard_account/view/product/product_view.dart +++ b/lib/features/dashboard/dashboard_account/view/product/product_view.dart @@ -2,11 +2,23 @@ import 'dart:math'; import 'package:cims_apps/application/assets/path_assets.dart'; import 'package:cims_apps/application/component/button/button_back.dart'; +import 'package:cims_apps/application/component/button/button_view.dart'; import 'package:cims_apps/application/component/image/image_view.dart'; +import 'package:cims_apps/application/component/text_form/text_form_view.dart'; import 'package:cims_apps/application/component/text_title/text_title.dart'; import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/number_formatter.dart'; import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/dashboard/dashboard_account/view/product/product_chart_view.dart'; import 'package:flutter/material.dart'; +import 'package:group_button/group_button.dart'; + +class Time { + int value; + String simpleName, completeName; + + Time(this.value, this.simpleName, this.completeName); +} class ProductView extends StatefulWidget { final String investType; @@ -17,16 +29,64 @@ class ProductView extends StatefulWidget { } class _ProductViewState extends State { - int chooseTab = 0; - String chooseTime = '1Y'; - int chooseMachine = 0; + int selectedTab = 0; + Time selectedTime = Time(2, '1D', '1 day'); + int selectedMachineType = 0; + int selectedMachineTime = 0; - List listTime = ['1D', '1M', '3M', 'YTD', '1Y', '3Y', '5Y', '10Y', 'All']; + List