Compare commits
8 Commits
6e2516d9c8
...
0a347f5e6d
Author | SHA1 | Date | |
---|---|---|---|
0a347f5e6d | |||
4d58a7dced | |||
81231505b1 | |||
96c676ac4c | |||
e1cabe0a09 | |||
d82d427bcc | |||
f407eca735 | |||
0e7ad81345 |
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 icon1 = 'assets/icons/icon-1.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 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 imgKtpClear = 'assets/images/img-ktp-clear.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';
|
||||
}
|
||||
|
|
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 EdgeInsetsGeometry? contentPadding;
|
||||
final bool isSecondaryColor, isOutlined, heightWrapContent, disabled;
|
||||
final Color? backgroundColor, textColor;
|
||||
final Color? backgroundColor, textColor, borderColor;
|
||||
final MainAxisAlignment? mainAxisAlignmentContent;
|
||||
// final _widthBtn = SizeConfig.screenWidth / 1.5;
|
||||
final _widthBtn = SizeConfig.width * .9;
|
||||
// final _heightBtn = SizeConfig.screenHeight / 12;
|
||||
final _heightBtn = SizeConfig.height * .07;
|
||||
final FontWeight textWeight;
|
||||
final TextAlign textAlign;
|
||||
final double? textSize, sizeBorderRadius;
|
||||
final int? maxLines;
|
||||
|
||||
|
@ -31,9 +32,11 @@ class ButtonView extends StatelessWidget {
|
|||
this.width,
|
||||
this.contentPadding,
|
||||
this.backgroundColor,
|
||||
this.borderColor,
|
||||
this.textColor,
|
||||
this.textWeight = FontWeight.bold,
|
||||
this.textSize,
|
||||
this.textAlign = TextAlign.center,
|
||||
this.mainAxisAlignmentContent,
|
||||
this.disabled = false,
|
||||
this.heightWrapContent = false,
|
||||
|
@ -75,11 +78,11 @@ class ButtonView extends StatelessWidget {
|
|||
borderRadius: BorderRadius.circular(sizeBorderRadius ?? 48),
|
||||
side: isOutlined
|
||||
? BorderSide(
|
||||
color: disabled
|
||||
? ColorPalette.greyBorder
|
||||
color: borderColor ?? (disabled
|
||||
? color.surface
|
||||
: isSecondaryColor
|
||||
? ColorPalette.greyBorder
|
||||
: ColorPalette.primary,
|
||||
? ColorPalette.greyBorder
|
||||
: ColorPalette.primary),
|
||||
)
|
||||
: BorderSide.none,
|
||||
),
|
||||
|
@ -101,7 +104,7 @@ class ButtonView extends StatelessWidget {
|
|||
Flexible(
|
||||
child: Text(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
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 backgroundBlueLight = Color(0xFFEBF3FD);
|
||||
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 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/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';
|
||||
|
||||
class BottomNavigationView extends StatefulWidget {
|
||||
|
@ -15,15 +17,12 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
|||
Widget build(BuildContext context) {
|
||||
///TODO: masukan pagenya dilistWidget ini
|
||||
List<Widget> listWidget = [
|
||||
Container(
|
||||
color: Colors.amberAccent,
|
||||
),
|
||||
HomeView(),
|
||||
Container(
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
Container(),
|
||||
Container(),
|
||||
Container(),
|
||||
PortofolioView(),
|
||||
Container(),
|
||||
];
|
||||
|
||||
|
@ -60,6 +59,7 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
|||
},
|
||||
currentIndex: _selectedIndex,
|
||||
items: listNavigation,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showUnselectedLabels: true,
|
||||
selectedItemColor: ColorPalette.primary,
|
||||
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/utils/size_config.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';
|
||||
|
||||
class DashboardPublicView extends StatelessWidget {
|
||||
|
@ -68,7 +69,9 @@ class DashboardPublicView extends StatelessWidget {
|
|||
isOutlined: true,
|
||||
width: SizeConfig.width * .43,
|
||||
height: SizeConfig.height * .06,
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
routePush(context, page: const BottomNavigationView());
|
||||
},
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Sign Up',
|
||||
|
|
40
pubspec.lock
|
@ -49,6 +49,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -97,6 +105,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -129,6 +145,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -168,6 +192,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:
|
||||
|
@ -184,6 +216,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -39,7 +39,11 @@ dependencies:
|
|||
flutter_svg: ^1.1.6
|
||||
cached_network_image: ^3.2.3
|
||||
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
|
||||
group_button: ^5.3.4
|
||||
pinput: ^2.2.21
|
||||
|
||||
|
||||
|
|