yoga #3
BIN
assets/icons/icon-portofolio-bonds.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/icons/icon-portofolio-moneymarket.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
assets/icons/icon-portofolio-shares.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/icons/icon-portofolio-sharia.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/images/img-articles.png
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
assets/images/img-carousel.png
Normal file
After Width: | Height: | Size: 636 KiB |
BIN
assets/images/img-dashboard-account.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/images/img-product.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
|
@ -10,6 +10,10 @@ class PathAssets {
|
||||||
static const String iconGoogle = 'assets/icons/icon-google.png';
|
static const String iconGoogle = 'assets/icons/icon-google.png';
|
||||||
static const String icon1 = 'assets/icons/icon-1.png';
|
static const String icon1 = 'assets/icons/icon-1.png';
|
||||||
static const String iconConnect = 'assets/icons/icon-connect.png';
|
static const String iconConnect = 'assets/icons/icon-connect.png';
|
||||||
|
static const String iconPortofolioBonds = 'assets/icons/icon-portofolio-bonds.png';
|
||||||
|
static const String iconPortofolioShares = 'assets/icons/icon-portofolio-shares.png';
|
||||||
|
static const String iconPortofolioSharia = 'assets/icons/icon-portofolio-sharia.png';
|
||||||
|
static const String iconPortofolioMoneyMarket = 'assets/icons/icon-portofolio-moneymarket.png';
|
||||||
static const String iconShield = 'assets/icons/icon-shield.png';
|
static const String iconShield = 'assets/icons/icon-shield.png';
|
||||||
static const String iconFlag = 'assets/icons/icon-flag.png';
|
static const String iconFlag = 'assets/icons/icon-flag.png';
|
||||||
|
|
||||||
|
@ -24,5 +28,9 @@ class PathAssets {
|
||||||
static const String imgKtpCropped = 'assets/images/img-ktp-cropped.png';
|
static const String imgKtpCropped = 'assets/images/img-ktp-cropped.png';
|
||||||
static const String imgKtpClear = 'assets/images/img-ktp-clear.png';
|
static const String imgKtpClear = 'assets/images/img-ktp-clear.png';
|
||||||
static const String imgKtpBlur = 'assets/images/img-ktp-blur.png';
|
static const String imgKtpBlur = 'assets/images/img-ktp-blur.png';
|
||||||
|
static const String imgDashboardAccount = 'assets/images/img-dashboard-account.png';
|
||||||
|
static const String imgCarousel = 'assets/images/img-carousel.png';
|
||||||
|
static const String imgArticles = 'assets/images/img-articles.png';
|
||||||
|
static const String imgProduct = 'assets/images/img-product.png';
|
||||||
static const String imgSuccessSignup = 'assets/images/img-success-signup.png';
|
static const String imgSuccessSignup = 'assets/images/img-success-signup.png';
|
||||||
}
|
}
|
||||||
|
|
23
lib/application/component/button/button_back.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:cims_apps/core/utils/size_config.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ButtonBack extends StatelessWidget {
|
||||||
|
const ButtonBack({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: SizeConfig.width * 0.1,
|
||||||
|
child: IconButton(
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
shape: const CircleBorder()
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.arrow_back)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,14 @@ class ButtonView extends StatelessWidget {
|
||||||
final double? height, width, widthSuffix, widthPrefix, marginVertical;
|
final double? height, width, widthSuffix, widthPrefix, marginVertical;
|
||||||
final EdgeInsetsGeometry? contentPadding;
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
final bool isSecondaryColor, isOutlined, heightWrapContent, disabled;
|
final bool isSecondaryColor, isOutlined, heightWrapContent, disabled;
|
||||||
final Color? backgroundColor, textColor;
|
final Color? backgroundColor, textColor, borderColor;
|
||||||
final MainAxisAlignment? mainAxisAlignmentContent;
|
final MainAxisAlignment? mainAxisAlignmentContent;
|
||||||
// final _widthBtn = SizeConfig.screenWidth / 1.5;
|
// final _widthBtn = SizeConfig.screenWidth / 1.5;
|
||||||
final _widthBtn = SizeConfig.width * .9;
|
final _widthBtn = SizeConfig.width * .9;
|
||||||
// final _heightBtn = SizeConfig.screenHeight / 12;
|
// final _heightBtn = SizeConfig.screenHeight / 12;
|
||||||
final _heightBtn = SizeConfig.height * .07;
|
final _heightBtn = SizeConfig.height * .07;
|
||||||
final FontWeight textWeight;
|
final FontWeight textWeight;
|
||||||
|
final TextAlign textAlign;
|
||||||
final double? textSize, sizeBorderRadius;
|
final double? textSize, sizeBorderRadius;
|
||||||
final int? maxLines;
|
final int? maxLines;
|
||||||
|
|
||||||
|
@ -31,9 +32,11 @@ class ButtonView extends StatelessWidget {
|
||||||
this.width,
|
this.width,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
|
this.borderColor,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
this.textWeight = FontWeight.bold,
|
this.textWeight = FontWeight.bold,
|
||||||
this.textSize,
|
this.textSize,
|
||||||
|
this.textAlign = TextAlign.center,
|
||||||
this.mainAxisAlignmentContent,
|
this.mainAxisAlignmentContent,
|
||||||
this.disabled = false,
|
this.disabled = false,
|
||||||
this.heightWrapContent = false,
|
this.heightWrapContent = false,
|
||||||
|
@ -75,11 +78,11 @@ class ButtonView extends StatelessWidget {
|
||||||
borderRadius: BorderRadius.circular(sizeBorderRadius ?? 48),
|
borderRadius: BorderRadius.circular(sizeBorderRadius ?? 48),
|
||||||
side: isOutlined
|
side: isOutlined
|
||||||
? BorderSide(
|
? BorderSide(
|
||||||
color: disabled
|
color: borderColor ?? (disabled
|
||||||
? ColorPalette.greyBorder
|
? color.surface
|
||||||
: isSecondaryColor
|
: isSecondaryColor
|
||||||
? ColorPalette.greyBorder
|
? ColorPalette.greyBorder
|
||||||
: ColorPalette.primary,
|
: ColorPalette.primary),
|
||||||
)
|
)
|
||||||
: BorderSide.none,
|
: BorderSide.none,
|
||||||
),
|
),
|
||||||
|
@ -101,7 +104,7 @@ class ButtonView extends StatelessWidget {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
name,
|
name,
|
||||||
textAlign: TextAlign.center,
|
textAlign: textAlign,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
26
lib/application/component/text_title/text_title.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
class TextTitle extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final Color? color;
|
||||||
|
final double? fontSize;
|
||||||
|
const TextTitle({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
this.color,
|
||||||
|
this.fontSize,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: fontSize ?? 20,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: color ?? ColorPalette.slate800,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,5 +74,37 @@ class ColorPalette {
|
||||||
static const Color background = Color(0xFFDADADA);
|
static const Color background = Color(0xFFDADADA);
|
||||||
static const Color backgroundBlueLight = Color(0xFFEBF3FD);
|
static const Color backgroundBlueLight = Color(0xFFEBF3FD);
|
||||||
static const Color slate500 = Color(0xFF64748B);
|
static const Color slate500 = Color(0xFF64748B);
|
||||||
|
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);
|
static const Color slate800 = Color(0xFF1E293B);
|
||||||
|
static const Color purple100 = Color(0xFFEDE9FE);
|
||||||
|
static const Color purple500 = Color(0xFF8B5CF6);
|
||||||
|
static const Color orange50 = Color(0xFFFFF7ED);
|
||||||
|
static const Color orange100 = Color(0xFFFFEDD5);
|
||||||
|
static const Color orange500 = Color(0xFFF97316);
|
||||||
|
static const Color cyan100 = Color(0xFFCFFAFE);
|
||||||
|
static const Color cyan500 = Color(0xFF06B6D4);
|
||||||
|
static const Color green100 = Color(0xFFDCFCE7);
|
||||||
|
static const Color green400 = Color(0xFF4ADE80);
|
||||||
|
static const Color green500 = Color(0xFF16A34A);
|
||||||
|
|
||||||
|
|
||||||
|
static const Map<String, Color> investTypeColor = {
|
||||||
|
'Money Market': purple500,
|
||||||
|
'Shares': orange500,
|
||||||
|
'Sharia': green500,
|
||||||
|
'Bonds': cyan500
|
||||||
|
};
|
||||||
|
|
||||||
|
static const Map<String, Color> investTypeBgColor = {
|
||||||
|
'Money Market': purple100,
|
||||||
|
'Shares': orange100,
|
||||||
|
'Sharia': green100,
|
||||||
|
'Bonds': cyan100
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
3
lib/core/utils/date_formatter.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
class DateFormatter {
|
||||||
|
|
||||||
|
}
|
19
lib/core/utils/number_formatter.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class NumberFormatter {
|
||||||
|
NumberFormatter._();
|
||||||
|
|
||||||
|
static String numberCurrency(dynamic value, String symbol, String locale, {int? decimalDigits = 2}) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||||
|
import 'package:cims_apps/features/dashboard/dashboard_account/view/homepage/homepage_view.dart';
|
||||||
|
import 'package:cims_apps/features/dashboard/dashboard_account/view/portfolio/portfolio_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class BottomNavigationView extends StatefulWidget {
|
class BottomNavigationView extends StatefulWidget {
|
||||||
|
@ -15,15 +17,12 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
///TODO: masukan pagenya dilistWidget ini
|
///TODO: masukan pagenya dilistWidget ini
|
||||||
List<Widget> listWidget = [
|
List<Widget> listWidget = [
|
||||||
Container(
|
HomeView(),
|
||||||
color: Colors.amberAccent,
|
|
||||||
),
|
|
||||||
Container(
|
Container(
|
||||||
color: Colors.redAccent,
|
color: Colors.redAccent,
|
||||||
),
|
),
|
||||||
Container(),
|
Container(),
|
||||||
Container(),
|
PortofolioView(),
|
||||||
Container(),
|
|
||||||
Container(),
|
Container(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -60,6 +59,7 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
||||||
},
|
},
|
||||||
currentIndex: _selectedIndex,
|
currentIndex: _selectedIndex,
|
||||||
items: listNavigation,
|
items: listNavigation,
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
showUnselectedLabels: true,
|
showUnselectedLabels: true,
|
||||||
selectedItemColor: ColorPalette.primary,
|
selectedItemColor: ColorPalette.primary,
|
||||||
unselectedItemColor: Colors.black,
|
unselectedItemColor: Colors.black,
|
||||||
|
|
|
@ -0,0 +1,529 @@
|
||||||
|
import 'package:carousel_slider/carousel_slider.dart';
|
||||||
|
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||||
|
import 'package:cims_apps/application/component/image/image_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/route/route.dart';
|
||||||
|
import 'package:cims_apps/core/utils/size_config.dart';
|
||||||
|
import 'package:cims_apps/features/dashboard/dashboard_account/view/invest_type/invest_type_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class InvestType {
|
||||||
|
String name;
|
||||||
|
String iconImage;
|
||||||
|
|
||||||
|
InvestType(this.name, this.iconImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StepVerification {
|
||||||
|
int currentStep;
|
||||||
|
List<String> nameStep;
|
||||||
|
|
||||||
|
StepVerification(this.currentStep, this.nameStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Article {
|
||||||
|
String image;
|
||||||
|
String title;
|
||||||
|
String type;
|
||||||
|
|
||||||
|
Article(this.type, this.title, this.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomeView extends StatefulWidget {
|
||||||
|
const HomeView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeView> createState() => _HomeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeViewState extends State<HomeView> {
|
||||||
|
bool seePortofolioValue = false;
|
||||||
|
|
||||||
|
List<InvestType> listPortofolioType = [
|
||||||
|
InvestType('Money Market', PathAssets.iconPortofolioMoneyMarket),
|
||||||
|
InvestType('Shares', PathAssets.iconPortofolioShares),
|
||||||
|
InvestType('Bonds', PathAssets.iconPortofolioBonds),
|
||||||
|
InvestType('Sharia', PathAssets.iconPortofolioSharia)
|
||||||
|
];
|
||||||
|
|
||||||
|
StepVerification listStepVerification = StepVerification(1, [
|
||||||
|
'Registration', 'Verification', 'Complete'
|
||||||
|
]);
|
||||||
|
|
||||||
|
List<Article> listArticle = [
|
||||||
|
Article('Education', 'Menggali Potensi Pasar: Analisis Sebelum Memulai Investasi', PathAssets.imgArticles),
|
||||||
|
Article('News', 'Tren Investasi 2024: Peluang dan Risiko yang Perlu Diketahui', PathAssets.imgArticles),
|
||||||
|
Article('Education', 'Investasi Berkelanjutan: Mengenal Portofolio Hijau untuk Masa Depan', PathAssets.imgArticles),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xffF8FAFC),
|
||||||
|
body: SizedBox(
|
||||||
|
width: SizeConfig.width,
|
||||||
|
height: SizeConfig.height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
const ImageView(image: PathAssets.imgDashboardAccount),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24, right: 16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Home',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w700
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: const Color(0xff2563EB),
|
||||||
|
elevation: 0,
|
||||||
|
shape: const CircleBorder()
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.notifications_outlined)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 32,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: portofolioValue(),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
cardInvestType(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 32,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
children: [
|
||||||
|
cardVerification(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
infoAndPromo(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
articles(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget portofolioValue() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('Portofolio Value', style: TextStyle(color: Colors.white)),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
seePortofolioValue = !seePortofolioValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.visibility_outlined, color: Color(0xff93C5FD))
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
AnimatedCrossFade(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
crossFadeState: seePortofolioValue ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
||||||
|
firstChild: RichText(
|
||||||
|
text: const TextSpan(
|
||||||
|
text: 'Rp ',
|
||||||
|
style: TextStyle(fontSize: 32, color: Color(0xff93C5FD), fontFamily: 'Manrope',),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '22.500.000',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
fontFamily: 'Manrope',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
secondChild: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 7,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
Container(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget cardInvestType() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
spreadRadius: 2,
|
||||||
|
blurRadius: 4,
|
||||||
|
color: const Color(0xff1E293B).withOpacity(0.04)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
children: listPortofolioType.asMap().entries.map((e) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
routePush(context, page: InvestTypeView(e.value.name));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
width: SizeConfig.width * .18,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ImageView(image: e.value.iconImage, height: SizeConfig.width * .12, width: SizeConfig.width * .12,),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
e.value.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget cardVerification() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
stepVerification(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.verified, size: 18,),
|
||||||
|
SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Verified by PT Gemilang',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.slate500
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(color: const Color(0xffCBD5E1), width: 1.5),
|
||||||
|
shape: BoxShape.circle
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.verified, size: 18,),
|
||||||
|
SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Verified by KSEI',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.slate500
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(color: const Color(0xffCBD5E1), width: 1.5),
|
||||||
|
shape: BoxShape.circle
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorPalette.blue50,
|
||||||
|
borderRadius: BorderRadius.circular(12)
|
||||||
|
),
|
||||||
|
child: const Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Your registration is currently being verified by PT Gemilang',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.slate500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text('Estimated:',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.slate500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'January 30 2024',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xff1E293B)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget stepVerification() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: listStepVerification.nameStep.asMap().entries.map((e) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if(e.key != 0)
|
||||||
|
SizedBox(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
child: Divider(color: listStepVerification.currentStep >= e.key ? const Color(0xff2563EB) : const Color(0xffCBD5E1),),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: listStepVerification.currentStep <= e.key ? Colors.white : const Color(0xff2563EB),
|
||||||
|
border: Border.all(
|
||||||
|
color: listStepVerification.currentStep < e.key ? const Color(0xffCBD5E1) : const Color(0xff2563EB),
|
||||||
|
width: 2
|
||||||
|
)
|
||||||
|
),
|
||||||
|
child: listStepVerification.currentStep <= e.key ? const SizedBox() : const Icon(Icons.done_rounded, color: Colors.white,),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Text(e.value, style: TextStyle(color: listStepVerification.currentStep == e.key ? const Color(0xff2563EB) : Colors.black),)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget infoAndPromo() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const TextTitle(title: 'Info and Special Promo', color: ColorPalette.slate800,),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
CarouselSlider(
|
||||||
|
items: [1,2,3].map((e) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: ImageView(image: PathAssets.imgCarousel, height: 150, width: SizeConfig.width * .9,),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
options: CarouselOptions(
|
||||||
|
height: 150,
|
||||||
|
autoPlay: true,
|
||||||
|
pageSnapping: true,
|
||||||
|
viewportFraction: 0.9,
|
||||||
|
autoPlayInterval: const Duration(seconds: 5),
|
||||||
|
autoPlayCurve: Curves.fastOutSlowIn,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget articles() {
|
||||||
|
return Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const TextTitle(title: 'Article', color: ColorPalette.slate800),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
child: const Text('See More',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.primary,
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
...listArticle.asMap().entries.map((e) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
if(e.key != 0)...[
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Divider(color: ColorPalette.slate200,),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
cardArticle(e.value),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget cardArticle(Article article) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ImageView(image: PathAssets.imgArticles, width: SizeConfig.width * .17, height: SizeConfig.height * .08, borderRadius: 8,),
|
||||||
|
const SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(article.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
color: ColorPalette.colorSwitchButtonActive
|
||||||
|
),
|
||||||
|
child: Text(article.type),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
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/image/image_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/route/route.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_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Product {
|
||||||
|
String name;
|
||||||
|
double yield;
|
||||||
|
double priceUnit;
|
||||||
|
double funds;
|
||||||
|
|
||||||
|
Product(this.name, this.yield, this.priceUnit, this.funds);
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvestTypeView extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
const InvestTypeView(this.title, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InvestTypeView> createState() => _InvestTypeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InvestTypeViewState extends State<InvestTypeView> {
|
||||||
|
|
||||||
|
List<Product> listProduct = [
|
||||||
|
Product('Gemilang Dana Kas Maxima', 8.17, 2600.79, 6300000),
|
||||||
|
Product('Gemilang Dana Likuid', 6.42, 1600.79, 2340000),
|
||||||
|
Product('Gemilang Income Fund', 8.17, 2600.79, 6300000)
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SizedBox(
|
||||||
|
width: SizeConfig.width,
|
||||||
|
height: SizeConfig.height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
const ImageView(image: PathAssets.imgDashboardAccount),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const ButtonBack(),
|
||||||
|
TextTitle(title: widget.title, color: Colors.white),
|
||||||
|
SizedBox(
|
||||||
|
width: SizeConfig.width * 0.1,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: Colors.white
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
filters(),
|
||||||
|
ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: listProduct.asMap().entries.map((e) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
routePush(context, page: ProductView(widget.title));
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: e.key != 0 ? 24 : 0),
|
||||||
|
child: cardProduct(e.value),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget filters() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
segmentFilter(const Icon(Icons.filter_alt_outlined, color: ColorPalette.slate400,), 'Filter', () { }),
|
||||||
|
segmentFilter(const RotatedBox(quarterTurns: 1, child: Icon(Icons.compare_arrows, color: ColorPalette.slate400)), 'Sort', () { }),
|
||||||
|
segmentFilter(const Icon(Icons.dashboard_outlined, color: ColorPalette.slate400), 'Compare', () { }),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget segmentFilter(Widget leading, String text, void Function()? onTap) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorPalette.slate200),
|
||||||
|
borderRadius: BorderRadius.circular(56)
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
leading,
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorPalette.slate500,
|
||||||
|
fontWeight: FontWeight.w700
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget cardProduct(Product product) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: ColorPalette.slate200),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ImageView(image: PathAssets.imgProduct, width: SizeConfig.width * .12,),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
product.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 18
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 12),
|
||||||
|
child: Divider(color: ColorPalette.slate200),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const Text('Yield', style: TextStyle(color: ColorPalette.slate400, fontWeight: FontWeight.w600),),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${product.yield.toString()}%',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorPalette.green400,
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text('/'),
|
||||||
|
const Text('3year', style: TextStyle(color: ColorPalette.slate400, fontWeight: FontWeight.w600),)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const Text('Price/unit', style: TextStyle(color: ColorPalette.slate400, fontWeight: FontWeight.w600),),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.trending_up_outlined, size: 18, color: ColorPalette.green400,),
|
||||||
|
const SizedBox(
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
NumberFormatter.numberCurrency(product.priceUnit, 'Rp', 'id_ID'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const Text('Managed funds', style: TextStyle(color: ColorPalette.slate400, fontWeight: FontWeight.w600),),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
NumberFormatter.compactCurrency(product.funds, 'Rp ', 'id_ID'),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,347 @@
|
||||||
|
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||||
|
import 'package:cims_apps/application/component/image/image_view.dart';
|
||||||
|
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||||
|
import 'package:cims_apps/core/utils/size_config.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CategoryPortofolio {
|
||||||
|
String name;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
CategoryPortofolio(this.value, this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PortofolioView extends StatefulWidget {
|
||||||
|
const PortofolioView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PortofolioView> createState() => _PortofolioViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PortofolioViewState extends State<PortofolioView> {
|
||||||
|
List<Color> contentColor = [
|
||||||
|
ColorPalette.purple500,
|
||||||
|
ColorPalette.orange500,
|
||||||
|
ColorPalette.cyan500,
|
||||||
|
ColorPalette.green500,
|
||||||
|
];
|
||||||
|
List<Color> bgContentColor = [
|
||||||
|
ColorPalette.purple100,
|
||||||
|
ColorPalette.orange100,
|
||||||
|
ColorPalette.cyan100,
|
||||||
|
ColorPalette.green100,
|
||||||
|
];
|
||||||
|
|
||||||
|
bool seePortofolioValue = false;
|
||||||
|
int touchedIndex = -1;
|
||||||
|
|
||||||
|
List<CategoryPortofolio> listCategoryPortofolio = [
|
||||||
|
CategoryPortofolio(20, 'Money Market'),
|
||||||
|
CategoryPortofolio(15, 'Shares'),
|
||||||
|
CategoryPortofolio(8, 'Bonds'),
|
||||||
|
CategoryPortofolio(50, 'Sharia'),
|
||||||
|
];
|
||||||
|
|
||||||
|
List<String> assetsImage = [
|
||||||
|
PathAssets.iconPortofolioMoneyMarket,
|
||||||
|
PathAssets.iconPortofolioShares,
|
||||||
|
PathAssets.iconPortofolioBonds,
|
||||||
|
PathAssets.iconPortofolioSharia,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: SizedBox(
|
||||||
|
width: SizeConfig.width,
|
||||||
|
height: SizeConfig.height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
const ImageView(image: PathAssets.imgDashboardAccount),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
const Center(
|
||||||
|
child: Text(
|
||||||
|
'Portofolio',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: portofolioValue(),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 24),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(24),
|
||||||
|
topRight: Radius.circular(24)),
|
||||||
|
),
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 0),
|
||||||
|
children: [
|
||||||
|
Stack(alignment: Alignment.center, children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1.3,
|
||||||
|
child: PieChart(PieChartData(
|
||||||
|
pieTouchData: PieTouchData(
|
||||||
|
touchCallback:
|
||||||
|
(FlTouchEvent event, pieTouchResponse) {
|
||||||
|
setState(() {
|
||||||
|
if (!event.isInterestedForInteractions ||
|
||||||
|
pieTouchResponse == null ||
|
||||||
|
pieTouchResponse.touchedSection ==
|
||||||
|
null) {
|
||||||
|
touchedIndex = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
touchedIndex = pieTouchResponse
|
||||||
|
.touchedSection!.touchedSectionIndex;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(
|
||||||
|
show: true,
|
||||||
|
),
|
||||||
|
sectionsSpace: 20,
|
||||||
|
centerSpaceRadius: 100,
|
||||||
|
sections: sectionsDataPortofolio())),
|
||||||
|
),
|
||||||
|
const Column(
|
||||||
|
children: [
|
||||||
|
Text('Total Mutual Fund'),
|
||||||
|
Text('10',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 44,
|
||||||
|
fontWeight: FontWeight.w700)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
menuPortofolio(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
...listColumnPortofolio(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget portofolioValue() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('Portofolio Value',
|
||||||
|
style: TextStyle(color: Colors.white)),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
seePortofolioValue = !seePortofolioValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.visibility_outlined,
|
||||||
|
color: Color(0xff93C5FD)))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 4,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
AnimatedCrossFade(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
crossFadeState: seePortofolioValue
|
||||||
|
? CrossFadeState.showSecond
|
||||||
|
: CrossFadeState.showFirst,
|
||||||
|
firstChild: RichText(
|
||||||
|
text: const TextSpan(
|
||||||
|
text: 'Rp ',
|
||||||
|
style: TextStyle(fontSize: 32, color: Color(0xff93C5FD), fontFamily: 'Manrope'),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '22.500.000',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
fontFamily: 'Manrope',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white
|
||||||
|
),
|
||||||
|
)
|
||||||
|
])),
|
||||||
|
secondChild: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 7,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
Container(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white, shape: BoxShape.circle),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PieChartSectionData> sectionsDataPortofolio() {
|
||||||
|
return listCategoryPortofolio.asMap().entries.map((e) {
|
||||||
|
final isTouched = e.key == touchedIndex;
|
||||||
|
final radius = isTouched ? 40.0 : 30.0;
|
||||||
|
return PieChartSectionData(
|
||||||
|
color: contentColor[e.key],
|
||||||
|
value: e.value.value.toDouble(),
|
||||||
|
title: e.value.name,
|
||||||
|
showTitle: isTouched,
|
||||||
|
radius: radius,
|
||||||
|
titleStyle: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget menuPortofolio() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
spreadRadius: 2,
|
||||||
|
blurRadius: 8,
|
||||||
|
color: const Color(0xff1E293B).withOpacity(0.04))
|
||||||
|
]),
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
spacing: 12,
|
||||||
|
children: listCategoryPortofolio.asMap().entries.map((e) {
|
||||||
|
return Container(
|
||||||
|
color: Colors.white,
|
||||||
|
width: SizeConfig.width * 0.18,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 58,
|
||||||
|
width: 58,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: bgContentColor[e.key],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${e.value.value}%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: contentColor[e.key]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
e.value.name,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> listColumnPortofolio() {
|
||||||
|
return listCategoryPortofolio.asMap().entries.map((e) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
if (e.key > 0) ...[
|
||||||
|
const Divider(
|
||||||
|
color: ColorPalette.slate200,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
ListTile(
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 0, horizontal: 24),
|
||||||
|
leading: ImageView(
|
||||||
|
image: assetsImage[e.key],
|
||||||
|
width: SizeConfig.width * .15,
|
||||||
|
height: SizeConfig.height * .08,
|
||||||
|
borderRadius: 8,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
e.value.name,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
e.value.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w600, color: Color(0xff94A3B8)),
|
||||||
|
),
|
||||||
|
trailing: const Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Rp 2.000.000',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'+ Rp 200.000',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Color(0xff4ADE80),
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,789 @@
|
||||||
|
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;
|
||||||
|
const ProductView(this.investType, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProductView> createState() => _ProductViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProductViewState extends State<ProductView> {
|
||||||
|
int selectedTab = 0;
|
||||||
|
Time selectedTime = Time(2, '1D', '1 day');
|
||||||
|
int selectedMachineType = 0;
|
||||||
|
int selectedMachineTime = 0;
|
||||||
|
|
||||||
|
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 * machineType) * 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
|
||||||
|
void dispose() {
|
||||||
|
machineController.dispose();
|
||||||
|
machineGroupButtonController.dispose();
|
||||||
|
// TODO: implement dispose
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SizedBox(
|
||||||
|
width: SizeConfig.width,
|
||||||
|
height: SizeConfig.height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
const ImageView(image: PathAssets.imgDashboardAccount),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 50,
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
ButtonBack(),
|
||||||
|
Wrap(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.search_rounded, color: ColorPalette.blue200, size: 32),
|
||||||
|
Icon(Icons.file_download_outlined, color: ColorPalette.blue200, size: 32),
|
||||||
|
Icon(Icons.star_outline_rounded, color: ColorPalette.blue200, size: 32)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
headContainer(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: contentContainer()
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: ButtonView(
|
||||||
|
name: 'Buy',
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
height: SizeConfig.height * 0.06,
|
||||||
|
marginVertical: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget headContainer() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ImageView(image: PathAssets.imgProduct, width: SizeConfig.width * .12),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Gemilang Dana Kas Maxima',
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 18
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
color: ColorPalette.investTypeBgColor[widget.investType] ?? Colors.white,
|
||||||
|
border: Border.all(width: 2, color: ColorPalette.investTypeColor[widget.investType] ?? Colors.white)
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
widget.investType,
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.investTypeColor[widget.investType],
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget contentContainer() {
|
||||||
|
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(() {
|
||||||
|
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', '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,
|
||||||
|
),
|
||||||
|
moreAction(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
cardInformation('Time Machine', timeMachine()),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
topFiveHoldings(),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
cardInformation('Document', documentProduct()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget switchTab(List<String> dataTab, void Function(int value) onTap, int current) {
|
||||||
|
return Container(
|
||||||
|
width: SizeConfig.width,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorPalette.slate200,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(80)
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// AnimatedPositioned(
|
||||||
|
// duration: const Duration(milliseconds: 200),
|
||||||
|
// curve: Curves.fastOutSlowIn,
|
||||||
|
// right: current == dataTab.length - 1 ? 0 : SizeConfig.width * current / dataTab.length - (12 * dataTab.length),
|
||||||
|
// left: current == 0 ? 0 : SizeConfig.width * current / dataTab.length - (12 * dataTab.length),
|
||||||
|
// child: Container(
|
||||||
|
// height: 35,
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: ColorPalette.blue200,
|
||||||
|
// borderRadius: BorderRadius.circular(80)
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Container(
|
||||||
|
height: 35,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(80)
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: dataTab.asMap().entries.map((e) => Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
onTap(e.key);
|
||||||
|
},
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
height: 35,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(80),
|
||||||
|
color: e.key == current ? ColorPalette.blue200 : ColorPalette.white
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
e.value,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: e.key == current ? FontWeight.w700 : FontWeight.w500,
|
||||||
|
color: e.key == current ? ColorPalette.primary : ColorPalette.slate500
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)).toList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget swithTime() {
|
||||||
|
return Container(
|
||||||
|
color: ColorPalette.slate50,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: listTime.map((e) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedTime = e;
|
||||||
|
data.clear();
|
||||||
|
List.generate(e.value, (index) => {
|
||||||
|
data.add((2500 + index - Random().nextInt(100)).toDouble())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
width: SizeConfig.width * .12,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
color: selectedTime == e ? ColorPalette.primary : ColorPalette.slate50
|
||||||
|
),
|
||||||
|
child: Text(e.simpleName, style: TextStyle(
|
||||||
|
color: selectedTime == e ? ColorPalette.white : ColorPalette.slate400,
|
||||||
|
fontWeight: FontWeight.w700
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget columnInformation(String title, String value) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
rowInformation(title),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(value)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorPalette.slate200),
|
||||||
|
borderRadius: BorderRadius.circular(12)
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TextTitle(title: title, fontSize: 16),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
child
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget informationInvest() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Minimum Purchase',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.slate400,
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('Rp10.000')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
rowInformation('Custodian Bank'),
|
||||||
|
const Text('HSBC INDONESIA')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
rowInformation('Depository Bank'),
|
||||||
|
const Text('BCA')
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget investCost(){
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Purchase Commission',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorPalette.slate400,
|
||||||
|
fontWeight: FontWeight.w600
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('Free',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
rowInformation('Sales Commission'),
|
||||||
|
const Text('Free',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget moreAction() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ButtonView(
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
prefixIcon: Icon(Icons.space_dashboard_sharp),
|
||||||
|
backgroundColor: ColorPalette.blue50,
|
||||||
|
sizeBorderRadius: 8,
|
||||||
|
isSecondaryColor: false,
|
||||||
|
width: SizeConfig.width * .5,
|
||||||
|
heightWrapContent: true,
|
||||||
|
isOutlined: true,
|
||||||
|
widthPrefix: 10,
|
||||||
|
name: 'Routine Savings',
|
||||||
|
textSize: 14,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
contentPadding: EdgeInsets.all(12),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ButtonView(
|
||||||
|
onPressed: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
prefixIcon: Icon(Icons.space_dashboard_sharp, color: ColorPalette.orange500),
|
||||||
|
widthPrefix: 10,
|
||||||
|
name: 'Compare Mutual Funds',
|
||||||
|
width: SizeConfig.width * .5,
|
||||||
|
heightWrapContent: true,
|
||||||
|
backgroundColor: ColorPalette.orange50,
|
||||||
|
sizeBorderRadius: 8,
|
||||||
|
isOutlined: true,
|
||||||
|
borderColor: ColorPalette.orange500,
|
||||||
|
textSize: 14,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
textColor: ColorPalette.orange500,
|
||||||
|
contentPadding: EdgeInsets.all(12),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget timeMachine() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
switchTab(listMachine, (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedMachineType = value;
|
||||||
|
});
|
||||||
|
setEstimatedValue();
|
||||||
|
},
|
||||||
|
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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:cims_apps/application/theme/color_palette.dart';
|
||||||
import 'package:cims_apps/core/route/route.dart';
|
import 'package:cims_apps/core/route/route.dart';
|
||||||
import 'package:cims_apps/core/utils/size_config.dart';
|
import 'package:cims_apps/core/utils/size_config.dart';
|
||||||
import 'package:cims_apps/features/auth/registration/view/registration_view.dart';
|
import 'package:cims_apps/features/auth/registration/view/registration_view.dart';
|
||||||
|
import 'package:cims_apps/features/bottom_navigation_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DashboardPublicView extends StatelessWidget {
|
class DashboardPublicView extends StatelessWidget {
|
||||||
|
@ -68,7 +69,9 @@ class DashboardPublicView extends StatelessWidget {
|
||||||
isOutlined: true,
|
isOutlined: true,
|
||||||
width: SizeConfig.width * .43,
|
width: SizeConfig.width * .43,
|
||||||
height: SizeConfig.height * .06,
|
height: SizeConfig.height * .06,
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
routePush(context, page: const BottomNavigationView());
|
||||||
|
},
|
||||||
),
|
),
|
||||||
ButtonView(
|
ButtonView(
|
||||||
name: 'Sign Up',
|
name: 'Sign Up',
|
||||||
|
|
40
pubspec.lock
|
@ -49,6 +49,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
carousel_slider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: carousel_slider
|
||||||
|
sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -97,6 +105,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.6"
|
||||||
|
equatable:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: equatable
|
||||||
|
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -129,6 +145,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
fl_chart:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: fl_chart
|
||||||
|
sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.65.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -168,6 +192,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -184,6 +216,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.19.0"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -39,7 +39,11 @@ dependencies:
|
||||||
flutter_svg: ^1.1.6
|
flutter_svg: ^1.1.6
|
||||||
cached_network_image: ^3.2.3
|
cached_network_image: ^3.2.3
|
||||||
remove_emoji_input_formatter: ^0.0.1+1
|
remove_emoji_input_formatter: ^0.0.1+1
|
||||||
|
fl_chart: ^0.65.0
|
||||||
|
intl: ^0.19.0
|
||||||
|
carousel_slider: ^4.2.1
|
||||||
provider: ^6.1.1
|
provider: ^6.1.1
|
||||||
|
group_button: ^5.3.4
|
||||||
pinput: ^2.2.21
|
pinput: ^2.2.21
|
||||||
|
|
||||||
|
|
||||||
|
|