feat: product chart view

This commit is contained in:
Prajna Prayoga 2024-02-06 17:48:49 +07:00
parent d82d427bcc
commit e1cabe0a09
8 changed files with 764 additions and 120 deletions

View File

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

View File

@ -0,0 +1,3 @@
class DateFormatter {
}

View File

@ -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;
}
}

View File

@ -334,7 +334,7 @@ class _HomeViewState extends State<HomeView> {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xffEFF6FF),
color: ColorPalette.blue50,
borderRadius: BorderRadius.circular(12)
),
child: const Column(

View File

@ -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<double> dataChart;
const ProductChartView({
super.key,
required this.tabType,
required this.lastTime,
required this.dataChart,
});
@override
State<ProductChartView> createState() => _ProductChartViewState();
}
class _ProductChartViewState extends State<ProductChartView> {
bool isShowLabel = true;
DateTime dateTime = DateTime.now();
double currentPrice = 0;
int spotIndicator = 0;
List<int> showingTooltipOnSpots = [1, 3, 5];
List<FlSpot> 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),
),
);
},
),
),
),
],
);
}
}

View File

@ -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<ProductView> {
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<String> listTime = ['1D', '1M', '3M', 'YTD', '1Y', '3Y', '5Y', '10Y', 'All'];
List<Time> listTime = [
Time(2, '1D', '1 day'),
Time(30, '1M', '1 month'),
Time(90, '3M', '3 months'),
Time(365, '1Y', '1 year'),
];
List<String> listTab = ['NAV', 'AUM'];
List<String> listMachine = ['Monthly Routine', 'Connect Once'];
List<Time> listMachineTime = [
Time(1, '1D', '1 year'),
Time(3, '1M', '3 year'),
Time(5, '3M', '5 year'),
Time(10, '1Y', '10 year'),
];
TextEditingController machineController = TextEditingController();
GroupButtonController machineGroupButtonController = GroupButtonController(selectedIndex: 0);
List<String> listTopHoldings = [
'Bank Pembangunan Daerah Sulawesi Selatan dan Sulawesi Barat',
'PT. Bank Jabar Banten, TBK',
'PT. Bank Mega',
'PT. Bank Nagaria',
'PT. BPD Sulawesi Tengah'
];
List<double> data = [];
double estimatedValue = 0;
void setEstimatedValue() {
double parseValue = double.parse(machineController.text.replaceAll('Rp ', '').replaceAll('.', ''));
int machineType = selectedMachineType == 0 ? 12 : 1;
setState(() {
estimatedValue = (machineType * (listMachineTime[selectedMachineTime].value) * (parseValue * 10/100)) + parseValue;
});
}
@override
void initState() {
machineController.text = NumberFormatter.numberCurrency(100000, 'Rp ', 'id_ID', decimalDigits: 0);
selectedTime = listTime[0];
setEstimatedValue();
List.generate(2, (index) => {
data.add((2500 + index - Random().nextInt(100)).toDouble())
});
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -119,75 +179,93 @@ class _ProductViewState extends State<ProductView> {
}
Widget contentContainer() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: ListView(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Wrap(
spacing: 8,
children: [
Text(listTab[chooseTab]),
const Text('26 Jan 24'),
],
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
),
child: ListView(
children: [
ProductChartView(
tabType: listTab[selectedTab],
lastTime: selectedTime.completeName,
dataChart: data,
),
),
Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: switchTab(listTab, (value) {
setState(() {
chooseTab = value;
});
}, chooseTab),
Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: switchTab(listTab, (value) {
setState(() {
selectedTab = value;
data.clear();
List.generate(selectedTime.value, (index) => {
data.add((2500 + index - Random().nextInt(100)).toDouble())
});
});
}, selectedTab),
),
),
),
swithTime(),
const SizedBox(
height: 24,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
columnInformation('CAGR 1Y', '3,88%'),
columnInformation('Drawdown 1Y', '0%'),
columnInformation('Expense Ratio', '1,99%')
],
),
),
const SizedBox(
height: 16,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
columnInformation('Total AUM', '3,88%'),
columnInformation('Avg.Yield Dec 23', '0%'),
columnInformation('Risk Level', '1,99%')
],
),
),
const SizedBox(
swithTime(),
const SizedBox(
height: 24,
),
cardInformation('Informasi Investasi', informationInvest()),
const SizedBox(
height: 24,
),
cardInformation('Informasi Investasi', investCost()),
const SizedBox(
height: 24,
),
cardInformation('Time Machine', timeMachine())
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
columnInformation('CAGR 1Y', '3,88%'),
columnInformation('Drawdown 1Y', '0%'),
columnInformation('Expense Ratio', '1,99%')
],
),
),
const SizedBox(
height: 16,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
columnInformation('Total AUM', '3,88%'),
columnInformation('Avg.Yield Dec 23', '6,44%'),
columnInformation('Risk Level', 'Conservative')
],
),
),
const SizedBox(
height: 24,
),
cardInformation('Investment Information', informationInvest()),
const SizedBox(
height: 24,
),
cardInformation('Investment Costs', investCost()),
const SizedBox(
height: 24,
),
cardInformation('Time Machine', timeMachine()),
const SizedBox(
height: 24,
),
topFiveHoldings(),
const SizedBox(
height: 24,
),
cardInformation('Document', documentProduct()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: ButtonView(
name: 'Buy',
onPressed: () {
},
),
)
],
),
),
);
}
@ -228,7 +306,7 @@ class _ProductViewState extends State<ProductView> {
onTap(e.key);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
height: 35,
alignment: Alignment.center,
decoration: BoxDecoration(
@ -256,6 +334,7 @@ class _ProductViewState extends State<ProductView> {
Widget swithTime() {
return Container(
color: ColorPalette.slate50,
alignment: Alignment.center,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 24),
@ -265,7 +344,11 @@ class _ProductViewState extends State<ProductView> {
return GestureDetector(
onTap: () {
setState(() {
chooseTime = e;
selectedTime = e;
data.clear();
List.generate(e.value, (index) => {
data.add((2500 + index - Random().nextInt(100)).toDouble())
});
});
},
child: AnimatedContainer(
@ -275,10 +358,10 @@ class _ProductViewState extends State<ProductView> {
padding: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: chooseTime == e ? ColorPalette.primary : ColorPalette.slate50
color: selectedTime == e ? ColorPalette.primary : ColorPalette.slate50
),
child: Text(e, style: TextStyle(
color: chooseTime == e ? ColorPalette.white : ColorPalette.slate400,
child: Text(e.simpleName, style: TextStyle(
color: selectedTime == e ? ColorPalette.white : ColorPalette.slate400,
fontWeight: FontWeight.w700
)),
),
@ -293,20 +376,7 @@ class _ProductViewState extends State<ProductView> {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
maxLines: 1,
style: const TextStyle(
color: ColorPalette.slate400
),
),
const SizedBox(width: 2),
const Icon(Icons.info_outline_rounded, size: 16, color: ColorPalette.slate400)
],
),
rowInformation(title),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@ -317,6 +387,24 @@ class _ProductViewState extends State<ProductView> {
);
}
Widget rowInformation(String title) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
maxLines: 1,
style: const TextStyle(
color: ColorPalette.slate400,
fontWeight: FontWeight.w600
),
),
const SizedBox(width: 2),
const Icon(Icons.info_outline_rounded, size: 16, color: ColorPalette.slate400)
],
);
}
Widget cardInformation(String title, Widget child) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24),
@ -329,7 +417,7 @@ class _ProductViewState extends State<ProductView> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextTitle(title: title, fontSize: 16),
SizedBox(
const SizedBox(
height: 16,
),
child
@ -339,9 +427,9 @@ class _ProductViewState extends State<ProductView> {
}
Widget informationInvest() {
return const Column(
return Column(
children: [
Row(
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
@ -357,27 +445,15 @@ class _ProductViewState extends State<ProductView> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Custodian Bank',
style: TextStyle(
color: ColorPalette.slate400,
fontWeight: FontWeight.w600
),
),
Text('HSBC INDONESIA')
rowInformation('Custodian Bank'),
const Text('HSBC INDONESIA')
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Depository Bank',
style: TextStyle(
color: ColorPalette.slate400,
fontWeight: FontWeight.w600
),
),
Text('BCA')
rowInformation('Depository Bank'),
const Text('BCA')
],
)
],
@ -385,9 +461,9 @@ class _ProductViewState extends State<ProductView> {
}
Widget investCost(){
return const Column(
return Column(
children: [
Row(
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
@ -397,20 +473,22 @@ class _ProductViewState extends State<ProductView> {
fontWeight: FontWeight.w600
),
),
Text('Free')
Text('Free',
style: TextStyle(
fontWeight: FontWeight.bold
),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sales Commission',
rowInformation('Sales Commission'),
const Text('Free',
style: TextStyle(
color: ColorPalette.slate400,
fontWeight: FontWeight.w600
fontWeight: FontWeight.bold
),
),
Text('Free')
)
],
),
],
@ -419,13 +497,221 @@ class _ProductViewState extends State<ProductView> {
Widget timeMachine() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
switchTab(listMachine, (value) {
setState(() {
chooseMachine = value;
selectedMachineType = value;
});
setEstimatedValue();
},
chooseMachine
selectedMachineType
),
const Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Text(
"Today's Investment Estimated Value",
style: TextStyle(
fontWeight: FontWeight.w600,
color: ColorPalette.slate800
),
),
),
TextFormView(
name: '',
ctrl: machineController,
onChanged: (value) {
value = value.replaceAll('Rp ', '').replaceAll('.', '');
double parseValue = double.parse(value);
if(value.isNotEmpty){
machineController.text = NumberFormatter.numberCurrency(parseValue, 'Rp ', 'id_ID', decimalDigits: 0);
}else{
machineController.text = NumberFormatter.numberCurrency(0, 'Rp ', 'id_ID', decimalDigits: 0);
}
setEstimatedValue();
},
keyboardType: TextInputType.number,
),
const Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Text(
"How many years ago did you start investing?",
style: TextStyle(
fontWeight: FontWeight.w600,
color: ColorPalette.slate800
),
),
),
Center(
child: GroupButton(
buttons: listMachineTime.map((e) => e.completeName).toList(),
controller: machineGroupButtonController,
onSelected: (value, index, isSelected) {
setState(() {
selectedMachineTime = index;
});
setEstimatedValue();
},
options: GroupButtonOptions(
buttonWidth: SizeConfig.width * .375,
mainGroupAlignment: MainGroupAlignment.spaceBetween,
groupRunAlignment: GroupRunAlignment.spaceBetween,
spacing: 16,
elevation: 0,
borderRadius: BorderRadius.circular(80),
selectedShadow: const [],
selectedColor: Colors.white,
selectedBorderColor: ColorPalette.primary,
selectedTextStyle: const TextStyle(
fontWeight: FontWeight.w700,
color: ColorPalette.primary
),
unselectedShadow: const [],
unselectedBorderColor: ColorPalette.slate200,
unselectedTextStyle: const TextStyle(
fontWeight: FontWeight.w500,
color: ColorPalette.slate500
),
),
),
),
const Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Text(
"Today's Investment Estimated Value",
style: TextStyle(
fontWeight: FontWeight.w600,
color: ColorPalette.slate500
),
),
),
TextTitle(
title: NumberFormatter.numberCurrency(estimatedValue, 'Rp ', 'id_ID'),
fontSize: 24,
color: ColorPalette.primary,
)
],
);
}
Widget topFiveHoldings() {
return Column(
children: [
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextTitle(title: 'Top 5 Holdings'),
Text(
'As of 31 Dec 2023',
style: TextStyle(
fontSize: 12,
color: ColorPalette.slate400
),
)
],
),
),
const SizedBox(
height: 16,
),
...listTopHoldings.asMap().entries.map((e) {
return topProduct(e.key, e.value);
})
],
);
}
Widget topProduct(int index, String name) {
return Column(
children: [
if(index != 0)...[
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Divider(color: ColorPalette.slate200,),
)
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
children: [
Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 11),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: ColorPalette.blue50,
border: Border.all(color: ColorPalette.blue200)
),
child: Text(index.toString(), style: const TextStyle(color: ColorPalette.primary)),
),
const SizedBox(
width: 16,
),
Expanded(
child: Text(
name,
style: const TextStyle(
color: ColorPalette.slate800,
fontSize: 16
),
)
)
],
),
)
],
);
}
Widget documentProduct() {
return Column(
children: [
Wrap(
runAlignment: WrapAlignment.spaceBetween,
alignment: WrapAlignment.spaceBetween,
children: [
SizedBox(
width: SizeConfig.width * .4,
child: const Row(
children: [
Icon(Icons.file_copy_outlined, color: ColorPalette.primary),
SizedBox(
width: 16,
),
Text(
'Prospektus',
style: TextStyle(
color: ColorPalette.primary,
fontWeight: FontWeight.w600
),
)
],
),
),
SizedBox(
width: SizeConfig.width * .4,
child: const Row(
children: [
Icon(Icons.file_copy_outlined, color: ColorPalette.primary),
SizedBox(
width: 16,
),
Expanded(
child: Text(
'Fun Fact Sheet',
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: ColorPalette.primary,
fontWeight: FontWeight.w600
),
),
)
],
),
)
],
)
],
);

View File

@ -187,6 +187,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
group_button:
dependency: "direct main"
description:
name: group_button
sha256: "0610fcf28ed122bfb4b410fce161a390f7f2531d55d1d65c5375982001415940"
url: "https://pub.dev"
source: hosted
version: "5.3.4"
http:
dependency: transitive
description:

View File

@ -43,6 +43,7 @@ dependencies:
intl: ^0.19.0
carousel_slider: ^4.2.1
provider: ^6.1.1
group_button: ^5.3.4