Compare commits
51 Commits
main
...
5cb76fca7f
Author | SHA1 | Date | |
---|---|---|---|
5cb76fca7f | |||
e86e67b9c9 | |||
a6248520ef | |||
eb1eb83d52 | |||
7706fe4387 | |||
9da1675250 | |||
298d7f46d2 | |||
368f326123 | |||
a574f30424 | |||
3c1f7e210a | |||
b3a68b4436 | |||
ce2bf8a777 | |||
7e9c109fa2 | |||
8537045d74 | |||
23d189c288 | |||
4bad9cd18c | |||
3dca727a5e | |||
4c1cc7422b | |||
219339f577 | |||
0c2441091f | |||
d86820ec98 | |||
ececa5e541 | |||
b0de8c255e | |||
6977c8166d | |||
7a1cddee03 | |||
db6e4d543d | |||
d672a23564 | |||
5e97154100 | |||
59e6e82d13 | |||
477eb5d2b1 | |||
370db229de | |||
9475767021 | |||
0a347f5e6d | |||
4d58a7dced | |||
81231505b1 | |||
6e2516d9c8 | |||
e538fa5927 | |||
7ec266cded | |||
96c676ac4c | |||
e1cabe0a09 | |||
4b4b42beae | |||
dacf5461f3 | |||
5d4bc47adf | |||
80e4657240 | |||
d82d427bcc | |||
e63e5588fb | |||
f407eca735 | |||
ff1886cec1 | |||
1616f22925 | |||
0b754bf939 | |||
0e7ad81345 |
@@ -12,6 +12,12 @@ if (localPropertiesFile.exists()) {
|
||||
}
|
||||
}
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
@@ -45,17 +51,27 @@ android {
|
||||
applicationId "com.example.cims_apps"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
keyAlias keystoreProperties['keyAlias']
|
||||
keyPassword keystoreProperties['keyPassword']
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
// signingConfig signingConfigs.debug
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<application
|
||||
android:label="cims_apps"
|
||||
android:label="cims investment"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
|
BIN
assets/icons/icon-balance.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/icons/icon-cake.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/icons/icon-ceklis-outline.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/icons/icon-coins.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/icons/icon-create-plan.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/icons/icon-flag.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/icons/icon-house.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/icons/icon-ktp1.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/icons/icon-ktp2.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
assets/icons/icon-ktp3.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/icons/icon-ktp4.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/icons/icon-money-receive.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
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/icons/icon-question.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/icons/icon-selfie1.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/icons/icon-selfie2.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/icons/icon-selfie3.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
assets/icons/icon-selfie4.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/icons/icon-shield.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/icons/icon-strongbox.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/icons/icon-toga.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/images/frame-signature.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
assets/images/img-articles.png
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
assets/images/img-bg-photo-ktp.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
assets/images/img-bg-photo-selfie.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
assets/images/img-business-failure.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
assets/images/img-carousel.png
Normal file
After Width: | Height: | Size: 636 KiB |
BIN
assets/images/img-cat.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
assets/images/img-dashboard-account.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/images/img-data-analysis.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
assets/images/img-data-report.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
assets/images/img-deer.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/img-finish.png
Normal file
After Width: | Height: | Size: 105 KiB |
BIN
assets/images/img-growing.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
assets/images/img-guide-bank.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
assets/images/img-guide1.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
assets/images/img-guide2.png
Normal file
After Width: | Height: | Size: 119 KiB |
BIN
assets/images/img-leader.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
assets/images/img-lion.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
assets/images/img-money-income.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
assets/images/img-product.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
assets/images/img-success-signup.png
Normal file
After Width: | Height: | Size: 88 KiB |
@@ -1,7 +1,7 @@
|
||||
class PathAssets {
|
||||
PathAssets._();
|
||||
|
||||
/// LOGO
|
||||
/// ICON
|
||||
static const String iconSplashRight = 'assets/icons/splash-right.png';
|
||||
static const String iconSplashLeft = 'assets/icons/splash-left.png';
|
||||
static const String iconReksadana = 'assets/icons/icon-reksadana.png';
|
||||
@@ -10,6 +10,35 @@ 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';
|
||||
static const String iconKtp1 = 'assets/icons/icon-ktp1.png';
|
||||
static const String iconKtp2 = 'assets/icons/icon-ktp2.png';
|
||||
static const String iconKtp3 = 'assets/icons/icon-ktp3.png';
|
||||
static const String iconKtp4 = 'assets/icons/icon-ktp4.png';
|
||||
static const String iconSelfie1 = 'assets/icons/icon-selfie1.png';
|
||||
static const String iconSelfie2 = 'assets/icons/icon-selfie2.png';
|
||||
static const String iconSelfie3 = 'assets/icons/icon-selfie3.png';
|
||||
static const String iconSelfie4 = 'assets/icons/icon-selfie4.png';
|
||||
static const String iconStrongBox = 'assets/icons/icon-strongbox.png';
|
||||
static const String iconBalance = 'assets/icons/icon-balance.png';
|
||||
static const String iconMoneyReceive = 'assets/icons/icon-money-receive.png';
|
||||
static const String iconCoins = 'assets/icons/icon-coins.png';
|
||||
static const String iconQuestion = 'assets/icons/icon-question.png';
|
||||
static const String iconCake = 'assets/icons/icon-cake.png';
|
||||
static const String iconHouse = 'assets/icons/icon-house.png';
|
||||
static const String iconToga = 'assets/icons/icon-toga.png';
|
||||
static const String iconCreatePlan = 'assets/icons/icon-create-plan.png';
|
||||
static const String iconChecklistOutlined =
|
||||
'assets/icons/icon-ceklis-outline.png';
|
||||
|
||||
/// IMAGE
|
||||
static const String imgSplashLogo = 'assets/images/splash-logo.png';
|
||||
@@ -22,4 +51,27 @@ 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';
|
||||
static const String imgBgKtp = 'assets/images/img-bg-photo-ktp.png';
|
||||
static const String imgBgSelfie = 'assets/images/img-bg-photo-selfie.png';
|
||||
static const String imgDataReport = 'assets/images/img-data-report.png';
|
||||
static const String imgDataAnalysis = 'assets/images/img-data-analysis.png';
|
||||
static const String imgBusinessFailure =
|
||||
'assets/images/img-business-failure.png';
|
||||
static const String imgLeader = 'assets/images/img-leader.png';
|
||||
static const String imgMoneyIncome = 'assets/images/img-money-income.png';
|
||||
static const String imgGrowing = 'assets/images/img-growing.png';
|
||||
static const String imgCat = 'assets/images/img-cat.png';
|
||||
static const String imgDeer = 'assets/images/img-deer.png';
|
||||
static const String imgLion = 'assets/images/img-lion.png';
|
||||
static const String imgGuideBank = 'assets/images/img-guide-bank.png';
|
||||
static const String imgGuide1 = 'assets/images/img-guide1.png';
|
||||
static const String imgGuide2 = 'assets/images/img-guide2.png';
|
||||
static const String frameSignature = 'assets/images/frame-signature.png';
|
||||
static const String imgFinish = 'assets/images/img-finish.png';
|
||||
}
|
||||
|
27
lib/application/component/button/back_button_view.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BackButtonView extends StatelessWidget {
|
||||
final EdgeInsets? margin;
|
||||
final void Function()? onPress;
|
||||
const BackButtonView({super.key, this.margin, this.onPress});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: margin ?? EdgeInsets.all(0),
|
||||
width: SizeConfig.width * 0.1,
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)
|
||||
)
|
||||
),
|
||||
onPressed: onPress ?? () => 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,
|
||||
@@ -55,8 +58,9 @@ class ButtonView extends StatelessWidget {
|
||||
final widthPrefix =
|
||||
this.widthPrefix ?? (heightWrapContent ? width! / 4.7 : _widthBtn / 16);
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.symmetric(vertical: marginVertical ?? 32.0),
|
||||
return Center(
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: marginVertical ?? 24.0),
|
||||
width: width ?? _widthBtn,
|
||||
height: heightWrapContent ? null : height ?? _heightBtn,
|
||||
child: ElevatedButton(
|
||||
@@ -74,11 +78,11 @@ class ButtonView extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(sizeBorderRadius ?? 48),
|
||||
side: isOutlined
|
||||
? BorderSide(
|
||||
color: disabled
|
||||
color: borderColor ?? (disabled
|
||||
? color.surface
|
||||
: isSecondaryColor
|
||||
? ColorPalette.greyBorder
|
||||
: ColorPalette.primary,
|
||||
: ColorPalette.primary),
|
||||
)
|
||||
: BorderSide.none,
|
||||
),
|
||||
@@ -100,7 +104,7 @@ class ButtonView extends StatelessWidget {
|
||||
Flexible(
|
||||
child: Text(
|
||||
name,
|
||||
textAlign: TextAlign.center,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
@@ -129,6 +133,7 @@ class ButtonView extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
42
lib/application/component/custom_app_bar/custom_app_bar.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:cims_apps/application/component/button/back_button_view.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final Widget? leading;
|
||||
final String title;
|
||||
final List<Widget>? trailing;
|
||||
final double height;
|
||||
|
||||
const CustomAppBar({
|
||||
Key? key,
|
||||
required this.height,
|
||||
this.leading,
|
||||
required this.title,
|
||||
this.trailing,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: ColorPalette.slate200))
|
||||
),
|
||||
child: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
leadingWidth: 40,
|
||||
leading: leading ?? BackButtonView(),
|
||||
title: Text(title),
|
||||
centerTitle: true,
|
||||
actions: trailing ?? [],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(height);
|
||||
}
|
59
lib/application/component/goal_investing_view.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GoalInvest {
|
||||
String icon;
|
||||
String title;
|
||||
|
||||
GoalInvest(this.icon, this.title);
|
||||
}
|
||||
|
||||
class GoalInvestingView extends StatelessWidget {
|
||||
const GoalInvestingView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<GoalInvest> listGoalInvest = [
|
||||
GoalInvest(PathAssets.iconToga, 'Education'),
|
||||
GoalInvest(PathAssets.iconCake, 'Marriage'),
|
||||
GoalInvest(PathAssets.iconHouse, 'Old age days'),
|
||||
GoalInvest(PathAssets.iconCreatePlan, 'Create Plan'),
|
||||
];
|
||||
|
||||
return Column(
|
||||
children:
|
||||
listGoalInvest.asMap().entries.map((e) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: e.key != 0 ? 16 : 0),
|
||||
child: ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200),
|
||||
borderRadius: BorderRadius.circular(14)
|
||||
),
|
||||
leading: Container(
|
||||
padding: EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorPalette.blue200.withOpacity(0.5),
|
||||
borderRadius: BorderRadius.circular(8)
|
||||
),
|
||||
child: Image.asset(
|
||||
e.value.icon,
|
||||
width: SizeConfig.width * 0.07
|
||||
)
|
||||
),
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
title: Text(e.value.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16
|
||||
),
|
||||
),
|
||||
trailing: Icon(Icons.chevron_right_rounded),
|
||||
),
|
||||
);
|
||||
}).toList()
|
||||
);
|
||||
}
|
||||
}
|
70
lib/application/component/list_tile/list_tile_view.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
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:flutter/material.dart';
|
||||
|
||||
class ListTileView extends StatelessWidget {
|
||||
final String title;
|
||||
final VoidCallback? onPressed;
|
||||
final Widget? prefixIcon, suffixIcon;
|
||||
final Color? colorTitle;
|
||||
const ListTileView(
|
||||
{Key? key,
|
||||
required this.title,
|
||||
this.onPressed,
|
||||
this.prefixIcon,
|
||||
this.suffixIcon,
|
||||
this.colorTitle})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: SizeConfig.width,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8.0),
|
||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorPalette.blue50,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: ColorPalette.greyLights,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
prefixIcon ??
|
||||
const ImageView(
|
||||
image: PathAssets.iconChecklistOutlined,
|
||||
width: 38,
|
||||
height: 38,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorTitle ?? ColorPalette.slate500,
|
||||
),
|
||||
),
|
||||
),
|
||||
suffixIcon != null
|
||||
? IconButton(
|
||||
onPressed: onPressed,
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: ColorPalette.primary,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
109
lib/application/component/numeric_pad/numeric_pad.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NumericPad extends StatelessWidget {
|
||||
final Function(String) onNumberSelected;
|
||||
final bool isPin;
|
||||
const NumericPad({super.key, required this.onNumberSelected, this.isPin = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
numberWidget('1'),
|
||||
dividerGradient(false, Alignment.bottomCenter, Alignment.topCenter),
|
||||
numberWidget('2'),
|
||||
dividerGradient(false, Alignment.bottomCenter, Alignment.topCenter),
|
||||
numberWidget('3')
|
||||
],
|
||||
),
|
||||
dividerGradient(true, Alignment.centerLeft, Alignment.centerRight),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
numberWidget('4'),
|
||||
dividerGradient(false, Alignment.center, Alignment.center, fullColor: true),
|
||||
numberWidget('5'),
|
||||
dividerGradient(false, Alignment.center, Alignment.center, fullColor: true),
|
||||
numberWidget('6')
|
||||
],
|
||||
),
|
||||
dividerGradient(true, Alignment.centerLeft, Alignment.centerRight),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
isPin ? spaceWidget() : numberWidget('0'),
|
||||
dividerGradient(false, Alignment.topCenter, Alignment.bottomCenter),
|
||||
numberWidget(isPin ? '0' : '000'),
|
||||
dividerGradient(false, Alignment.topCenter, Alignment.bottomCenter),
|
||||
removeWidget()
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget dividerGradient(bool isHorizontal, AlignmentGeometry gradientFrom, AlignmentGeometry gradientTo, {bool fullColor = false}) {
|
||||
return Container(
|
||||
width: isHorizontal ? SizeConfig.width : 1,
|
||||
height: isHorizontal ? 1 : SizeConfig.height * 0.11,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
if(isHorizontal) ...[
|
||||
ColorPalette.slate200.withOpacity(0)
|
||||
],
|
||||
ColorPalette.slate200,
|
||||
fullColor ? ColorPalette.slate200 : ColorPalette.slate200.withOpacity(0)
|
||||
],
|
||||
begin: gradientFrom,
|
||||
end: gradientTo
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget spaceWidget() {
|
||||
return Expanded(
|
||||
child: SizedBox()
|
||||
);
|
||||
}
|
||||
|
||||
Widget numberWidget(String number) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onNumberSelected(number);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(vertical: SizeConfig.height * .028),
|
||||
child: Text(
|
||||
number,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget removeWidget() {
|
||||
return Expanded(
|
||||
child: Icon(
|
||||
Icons.highlight_remove,
|
||||
size: 28,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
146
lib/application/component/otp/otp_view.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'package:cims_apps/application/component/otp/otp_viewmodel.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.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/auth/registration/view/registration_password_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinput/pinput.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class OtpView extends StatelessWidget {
|
||||
final String title;
|
||||
final String? contentTitle, contentSubtitle;
|
||||
const OtpView({
|
||||
Key? key,
|
||||
required this.title,
|
||||
this.contentTitle,
|
||||
this.contentSubtitle,
|
||||
}) : super(key: key);
|
||||
|
||||
Widget _otpContent(BuildContext context, OtpViewModel provider) {
|
||||
return Form(
|
||||
key: provider.formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
Pinput(
|
||||
length: 4,
|
||||
controller: provider.ctrlPin,
|
||||
focusNode: provider.focusNode,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Pin must be complete';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
defaultPinTheme: PinTheme(
|
||||
textStyle: const TextStyle(
|
||||
color: ColorPalette.slate800,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
width: SizeConfig.width * .19,
|
||||
height: SizeConfig.height * .08,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
errorPinTheme: PinTheme(
|
||||
textStyle: const TextStyle(
|
||||
color: ColorPalette.slate800,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
width: SizeConfig.width * .19,
|
||||
height: SizeConfig.height * .08,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.redAccent),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
onCompleted: (pin) => provider.enableButton(),
|
||||
onChanged: (value) {
|
||||
if (provider.ctrlPin.length != 4) {
|
||||
provider.enableButton(isActive: false);
|
||||
}
|
||||
},
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 32.0),
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height * .07,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
elevation: 0,
|
||||
backgroundColor: ColorPalette.primary,
|
||||
),
|
||||
onPressed: !provider.buttonIsActive
|
||||
? null
|
||||
: () {
|
||||
if (provider.formKey.currentState!.validate()) {
|
||||
final pin = provider.ctrlPin.text;
|
||||
provider.validateOtp(pin).then((value) {
|
||||
if (value) {
|
||||
routePush(context,
|
||||
page: const RegistrationPasswordView(),
|
||||
routeType: RouteType.pushReplace);
|
||||
} else {
|
||||
provider.ctrlPin.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
'Verify',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => OtpViewModel(),
|
||||
builder: (context, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child:
|
||||
Consumer<OtpViewModel>(builder: (context, provider, child) {
|
||||
return Column(
|
||||
children: [
|
||||
TextCaption(
|
||||
title: contentTitle ?? '',
|
||||
subtitle: contentSubtitle ?? '',
|
||||
),
|
||||
_otpContent(context, provider),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
provider.ctrlPin.clear();
|
||||
},
|
||||
child: const Text(
|
||||
'Resend Code',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
22
lib/application/component/otp/otp_viewmodel.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OtpViewModel extends ChangeNotifier {
|
||||
var formKey = GlobalKey<FormState>();
|
||||
var focusNode = FocusNode();
|
||||
bool buttonIsActive = false;
|
||||
|
||||
TextEditingController ctrlPin = TextEditingController();
|
||||
|
||||
Future<bool> validateOtp(String pin) async {
|
||||
final pinLength = pin.length;
|
||||
if (pinLength == 4) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void enableButton({bool isActive = true}) {
|
||||
buttonIsActive = isActive;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
176
lib/application/component/select_form/select_form_view.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
import 'package:cims_apps/application/component/button/button_view.dart';
|
||||
import 'package:cims_apps/application/component/text_form/text_form_view.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ItemSelectForm {
|
||||
final String key;
|
||||
final String text;
|
||||
final String? description;
|
||||
final bool isOther;
|
||||
final String image;
|
||||
|
||||
ItemSelectForm(
|
||||
this.key,
|
||||
this.text, {
|
||||
this.isOther = false,
|
||||
this.image = "",
|
||||
this.description,
|
||||
});
|
||||
}
|
||||
|
||||
class SelectFormView extends StatelessWidget {
|
||||
final String name;
|
||||
final String? hintText;
|
||||
final TextStyle? hintTextStyle;
|
||||
final TextEditingController? ctrl;
|
||||
final Widget? bottomSheetTitle;
|
||||
final List<ItemSelectForm> listItem;
|
||||
final ValueChanged<String> onSelect;
|
||||
final FormFieldValidator<String>? validator;
|
||||
final _borderRadius = const Radius.circular(24);
|
||||
final bool? enabled;
|
||||
const SelectFormView(
|
||||
{Key? key,
|
||||
required this.name,
|
||||
this.hintText,
|
||||
this.hintTextStyle,
|
||||
this.ctrl,
|
||||
this.bottomSheetTitle,
|
||||
required this.listItem,
|
||||
required this.onSelect,
|
||||
this.validator,
|
||||
this.enabled})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bottomSheet() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: _borderRadius,
|
||||
topRight: _borderRadius,
|
||||
),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
ItemSelectForm? selectedForm;
|
||||
String? selectedKey;
|
||||
if (listItem.isNotEmpty) {
|
||||
var res = listItem.where((element) => element.key == selectedKey);
|
||||
if (res.isNotEmpty) {
|
||||
selectedForm = res.first;
|
||||
}
|
||||
}
|
||||
return StatefulBuilder(builder: (
|
||||
BuildContext context,
|
||||
StateSetter stateSetter,
|
||||
) {
|
||||
return Container(
|
||||
height: SizeConfig.height * .45,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
bottomSheetTitle ?? Container(),
|
||||
// const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
children: [
|
||||
...listItem.map(
|
||||
(e) => Card(
|
||||
elevation: 0,
|
||||
color: Colors.transparent,
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: ColorPalette.greyBorder,
|
||||
),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
e.text,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
subtitle: e.description != null
|
||||
? Text(
|
||||
e.description!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: null,
|
||||
// trailing: const Icon(
|
||||
// Icons.check_circle,
|
||||
// color: ColorPalette.primary,
|
||||
// ),
|
||||
trailing: Radio(
|
||||
focusColor: ColorPalette.primary,
|
||||
activeColor: ColorPalette.primary,
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: VisualDensity.minimumDensity,
|
||||
vertical: VisualDensity.minimumDensity,
|
||||
),
|
||||
materialTapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
value: e.key,
|
||||
groupValue: selectedKey,
|
||||
onChanged: (value) {
|
||||
// selectedForm =
|
||||
// ItemSelectForm(e.key, e.text);
|
||||
// stateSetter(() {
|
||||
// selectedKey = selectedForm!.key;
|
||||
// });
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
ctrl?.text = e.text;
|
||||
onSelect(e.key);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Select',
|
||||
marginVertical: 4.0,
|
||||
onPressed: () {
|
||||
// print('object $')
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return TextFormView(
|
||||
name: name,
|
||||
readOnly: true,
|
||||
enabled: enabled ?? true,
|
||||
onTap: () {
|
||||
if (listItem.isNotEmpty) bottomSheet();
|
||||
},
|
||||
validator: validator,
|
||||
hintText: hintText,
|
||||
hintTextStyle: hintTextStyle,
|
||||
ctrl: ctrl,
|
||||
suffixIcon: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
size: SizeConfig.width * .07,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,194 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cims_apps/application/assets/path_assets.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/take_picture_screen/take_picture_screen.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/auth/registration/view/submission_data/submission_parent.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DisplayPictureScreen extends StatelessWidget {
|
||||
final String imagePath, content;
|
||||
|
||||
const DisplayPictureScreen(
|
||||
{super.key, required this.imagePath, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List listIcons = [
|
||||
{
|
||||
'key': 'ktp',
|
||||
'urlImg': PathAssets.iconKtp1,
|
||||
'tag': 'ID card that matches your identity'
|
||||
},
|
||||
{
|
||||
'key': 'ktp',
|
||||
'urlImg': PathAssets.iconKtp2,
|
||||
'tag': 'ID card is not glare & blurry'
|
||||
},
|
||||
{
|
||||
'key': 'ktp',
|
||||
'urlImg': PathAssets.iconKtp3,
|
||||
'tag': 'ID card is clearly legible and not cut'
|
||||
},
|
||||
{
|
||||
'key': 'ktp',
|
||||
'urlImg': PathAssets.iconKtp4,
|
||||
'tag': 'No objects other than ID cards in the photo'
|
||||
},
|
||||
{
|
||||
'key': 'selfie',
|
||||
'urlImg': PathAssets.iconSelfie1,
|
||||
'tag': 'Good lighting, not dark and no reflections',
|
||||
},
|
||||
{
|
||||
'key': 'selfie',
|
||||
'urlImg': PathAssets.iconSelfie2,
|
||||
'tag': 'ID card does not cover the face',
|
||||
},
|
||||
{
|
||||
'key': 'selfie',
|
||||
'urlImg': PathAssets.iconSelfie3,
|
||||
'tag': 'Face not covered by mask, hat or glasses'
|
||||
},
|
||||
{
|
||||
'key': 'selfie',
|
||||
'urlImg': PathAssets.iconSelfie4,
|
||||
'tag': 'Face and ID card are clear and not blurry'
|
||||
},
|
||||
];
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
)
|
||||
],
|
||||
builder: (context, child) {
|
||||
return Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Preview'),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height * .4,
|
||||
child: Image.file(File(imagePath))),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Text(
|
||||
'Make sure the photo meets the requirements:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: List.generate(4, (index) {
|
||||
List filteredList = listIcons
|
||||
.where((element) => element['key'] == content)
|
||||
.toList();
|
||||
final urlImg = filteredList[index]['urlImg'];
|
||||
final tag = filteredList[index]['tag'];
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
width: SizeConfig.width * .42,
|
||||
height: SizeConfig.height * .13,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0, horizontal: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorPalette.blue50,
|
||||
borderRadius: BorderRadius.circular(6.0)),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(bottom: 8.0),
|
||||
child: ImageView(
|
||||
image: urlImg,
|
||||
width: SizeConfig.width * .1,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
tag,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorPalette.slate800,
|
||||
fontWeight: FontWeight.normal),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: SizeConfig.width * .42,
|
||||
child: ButtonView(
|
||||
name: 'Retake',
|
||||
isOutlined: true,
|
||||
marginVertical: 8.0,
|
||||
onPressed: () {
|
||||
provider.initCamera().then((cameras) {
|
||||
routePush(context,
|
||||
page: TakePictureScreen(
|
||||
camera: content == 'ktp'
|
||||
? cameras[0]
|
||||
: cameras[1],
|
||||
takeContent: content,
|
||||
));
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * .4,
|
||||
child: ButtonView(
|
||||
marginVertical: 0.0,
|
||||
name: 'Next',
|
||||
onPressed: () {
|
||||
provider.nextSubmission(context);
|
||||
routePush(context,
|
||||
page: const SubmissionParent());
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
)),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
import 'package:camera/camera.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/take_picture_screen/display_picture_screen.dart';
|
||||
import 'package:cims_apps/core/route/route.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TakePictureScreen extends StatefulWidget {
|
||||
const TakePictureScreen({
|
||||
super.key,
|
||||
required this.camera,
|
||||
required this.takeContent,
|
||||
});
|
||||
|
||||
final CameraDescription camera;
|
||||
final String takeContent;
|
||||
|
||||
@override
|
||||
TakePictureScreenState createState() => TakePictureScreenState();
|
||||
}
|
||||
|
||||
class TakePictureScreenState extends State<TakePictureScreen> {
|
||||
late CameraController _controller;
|
||||
late Future<void> _initializeControllerFuture;
|
||||
bool isFlash = false;
|
||||
late String _takeContent;
|
||||
|
||||
Future<void> changeFlash() async {
|
||||
await _controller.setFlashMode(FlashMode.auto);
|
||||
setState(() {
|
||||
isFlash = !isFlash;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = CameraController(
|
||||
widget.camera,
|
||||
ResolutionPreset.medium,
|
||||
enableAudio: false,
|
||||
);
|
||||
// Next, initialize the controller. This returns a Future.
|
||||
_initializeControllerFuture = _controller.initialize();
|
||||
_takeContent = widget.takeContent;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Dispose of the controller when the widget is disposed.
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Fill this out in the next steps.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Registration'),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
changeFlash();
|
||||
},
|
||||
icon: Icon(isFlash ? Icons.flash_on : Icons.flash_off))
|
||||
],
|
||||
),
|
||||
body: FutureBuilder<void>(
|
||||
future: _initializeControllerFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
// If the Future is complete, display the preview.
|
||||
return Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height,
|
||||
child: CameraPreview(_controller)),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.5),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ImageView(
|
||||
image: _takeContent == 'ktp'
|
||||
? PathAssets.imgBgKtp
|
||||
: PathAssets.imgBgSelfie,
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
// Ensure that the camera is initialized.
|
||||
await _initializeControllerFuture;
|
||||
|
||||
// Attempt to take a picture and get the file `image`
|
||||
// where it was saved.
|
||||
final image = await _controller.takePicture();
|
||||
|
||||
if (!mounted) return;
|
||||
routePush(context,
|
||||
page: DisplayPictureScreen(
|
||||
imagePath: image.path,
|
||||
content: _takeContent,
|
||||
),
|
||||
routeType: RouteType.pushReplace);
|
||||
} catch (e) {
|
||||
// If an error occurs, log the error to the console.
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.album_outlined,
|
||||
color: Colors.white,
|
||||
size: SizeConfig.width * .16,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -3,10 +3,15 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class TextCaption extends StatelessWidget {
|
||||
final String title, subtitle;
|
||||
final TextAlign? textAlign, textAlignSubtitle;
|
||||
final CrossAxisAlignment? crossAxisAlignment;
|
||||
const TextCaption({
|
||||
Key? key,
|
||||
required this.title,
|
||||
this.subtitle = '',
|
||||
this.textAlignSubtitle,
|
||||
this.crossAxisAlignment,
|
||||
this.textAlign,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -14,11 +19,12 @@ class TextCaption extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 32.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: crossAxisAlignment ?? CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
textAlign: textAlign ?? TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -30,10 +36,11 @@ class TextCaption extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
subtitle,
|
||||
textAlign: textAlignSubtitle ?? TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ColorPalette.slate800,
|
||||
color: ColorPalette.slate500,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@@ -14,7 +14,7 @@ class TextFormView extends StatelessWidget {
|
||||
final String? hintText, errorText;
|
||||
final TextEditingController? ctrl;
|
||||
final Widget? suffixIcon, suffixLable;
|
||||
final Widget? prefixIcon;
|
||||
final Widget? prefixIcon, prefix;
|
||||
final TextInputType? keyboardType;
|
||||
final FormFieldValidator<String>? validator;
|
||||
final bool obscureText;
|
||||
@@ -33,6 +33,7 @@ class TextFormView extends StatelessWidget {
|
||||
final Color? disabledborderColor;
|
||||
final bool? enableInteractiveSelection;
|
||||
final Color? fontColorDisabled;
|
||||
final EdgeInsets? contentPadding;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
// ignore: prefer_const_constructors_in_immutables
|
||||
@@ -69,8 +70,10 @@ class TextFormView extends StatelessWidget {
|
||||
this.preffixIconConstraints,
|
||||
this.disableColor = false,
|
||||
this.enableInteractiveSelection = true,
|
||||
this.contentPadding,
|
||||
this.focusNode,
|
||||
this.isTextAlignCenter = false})
|
||||
this.isTextAlignCenter = false,
|
||||
this.prefix})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@@ -78,28 +81,31 @@ class TextFormView extends StatelessWidget {
|
||||
if (inputFormatters != null && maxLength != null) {
|
||||
inputFormatters?.add(LengthLimitingTextInputFormatter(maxLength));
|
||||
}
|
||||
return Column(
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
name.isNotEmpty
|
||||
? validator != null
|
||||
// ? validator != null
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: ColorPalette.greyLight,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
),
|
||||
suffixLable ??
|
||||
const Text(
|
||||
" * ",
|
||||
"",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.red,
|
||||
@@ -112,8 +118,8 @@ class TextFormView extends StatelessWidget {
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
// : const SizedBox(),
|
||||
trailingTitleWidget ?? const SizedBox(),
|
||||
],
|
||||
),
|
||||
@@ -126,7 +132,7 @@ class TextFormView extends StatelessWidget {
|
||||
initialValue: initialValue,
|
||||
enabled: enabled,
|
||||
controller: ctrl,
|
||||
// maxLength: maxLength,
|
||||
maxLength: maxLength,
|
||||
keyboardType: keyboardType,
|
||||
onTap: onTap,
|
||||
onEditingComplete: onSubmit,
|
||||
@@ -155,7 +161,7 @@ class TextFormView extends StatelessWidget {
|
||||
hintStyle: hintTextStyle ??
|
||||
const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
color: ColorPalette.greyFont,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
isDense: true,
|
||||
@@ -167,19 +173,19 @@ class TextFormView extends StatelessWidget {
|
||||
disabledBorder: OutlineInputBorder(
|
||||
borderRadius: _borderRadius,
|
||||
borderSide: BorderSide(
|
||||
color: disabledborderColor ?? ColorPalette.greyFont,
|
||||
color: disabledborderColor ?? ColorPalette.greyBorder,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: _borderRadius,
|
||||
borderSide: BorderSide(
|
||||
color: enabledborderColor ?? ColorPalette.greyBase,
|
||||
color: enabledborderColor ?? ColorPalette.greyBorder,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: _borderRadius,
|
||||
borderSide: BorderSide(
|
||||
color: focusedBorderColor ?? ColorPalette.greyBase,
|
||||
color: focusedBorderColor ?? ColorPalette.greyBorder,
|
||||
),
|
||||
),
|
||||
border: OutlineInputBorder(borderRadius: _borderRadius),
|
||||
@@ -187,9 +193,15 @@ class TextFormView extends StatelessWidget {
|
||||
prefixIcon: prefixIcon,
|
||||
suffixIconConstraints: suffixIconConstraints,
|
||||
prefixIconConstraints: preffixIconConstraints,
|
||||
),
|
||||
prefix: prefix,
|
||||
contentPadding: contentPadding ??
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 16.0,
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -73,5 +73,36 @@ class ColorPalette {
|
||||
static const Color chathamsBlue = Color(0xFF285BB9);
|
||||
static const Color background = Color(0xFFDADADA);
|
||||
static const Color backgroundBlueLight = Color(0xFFEBF3FD);
|
||||
static const Color blue50 = Color(0xFFEFF6FF);
|
||||
static const Color blue200 = Color(0xFFBFDBFE);
|
||||
static const Color slate50 = Color(0xFFF8FAFC);
|
||||
static const Color slate200 = Color(0xFFE2E8F0);
|
||||
static const Color slate300 = Color(0xFFCBD5E1);
|
||||
static const Color slate400 = Color(0xFF94A3B8);
|
||||
static const Color slate500 = Color(0xFF64748B);
|
||||
static const Color slate800 = Color(0xFF1E293B);
|
||||
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;
|
||||
}
|
||||
}
|
16
lib/core/utils/string_utils.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class StringUtils {
|
||||
static bool emailValidation(String email) {
|
||||
return RegExp(
|
||||
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
|
||||
.hasMatch(email);
|
||||
}
|
||||
|
||||
static bool phoneValidation(String phone) {
|
||||
return RegExp(r'^(\+62|62|0)8[1-9][0-9]{6,10}$').hasMatch(phone);
|
||||
}
|
||||
|
||||
static bool passwordValidation(String password) {
|
||||
return RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*?[\W_])(?=.{8,})')
|
||||
.hasMatch(password);
|
||||
}
|
||||
}
|
96
lib/features/auth/login/view/login_view.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/component/button/back_button_view.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/route/route.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:cims_apps/features/auth/login/view/password_view.dart';
|
||||
import 'package:cims_apps/features/auth/login/view/phone_number_view.dart';
|
||||
import 'package:cims_apps/features/auth/login/view_model/login_view_model.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/risk_profile/risk_profile_view.dart';
|
||||
import 'package:cims_apps/features/bottom_navigation_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class LoginView extends StatefulWidget {
|
||||
const LoginView({super.key});
|
||||
|
||||
@override
|
||||
State<LoginView> createState() => _LoginViewState();
|
||||
}
|
||||
|
||||
class _LoginViewState extends State<LoginView> {
|
||||
int currentPage = 0;
|
||||
PageController pageController = PageController(initialPage: 0);
|
||||
TextEditingController phoneNumberController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// TODO: implement dispose
|
||||
pageController.dispose();
|
||||
phoneNumberController.dispose();
|
||||
passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => LoginViewModel(),
|
||||
child: WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (currentPage == 1) {
|
||||
currentPage--;
|
||||
pageController.jumpToPage(0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BackButtonView(),
|
||||
const Text('Sign In'),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * 0.1,
|
||||
)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: PageView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: pageController,
|
||||
children: [
|
||||
PhoneNumberView(
|
||||
nextStep: nextStep, controller: phoneNumberController),
|
||||
PasswordView(nextStep: nextStep, controller: passwordController)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void nextStep() {
|
||||
if (pageController.page == 0) {
|
||||
currentPage++;
|
||||
pageController.jumpToPage(1);
|
||||
} else {
|
||||
routePush(context, page: BottomNavigationView());
|
||||
}
|
||||
}
|
||||
}
|
78
lib/features/auth/login/view/password_view.dart
Normal file
@@ -0,0 +1,78 @@
|
||||
import 'package:cims_apps/application/component/button/button_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/size_config.dart';
|
||||
import 'package:cims_apps/features/auth/login/view_model/login_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PasswordView extends StatelessWidget {
|
||||
final void Function() nextStep;
|
||||
final TextEditingController controller;
|
||||
const PasswordView({super.key, required this.nextStep, required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<LoginViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Container(
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextTitle(title: 'Enter your password', fontSize: 24),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextFormView(
|
||||
name: 'Password',
|
||||
ctrl: controller,
|
||||
obscureText: !provider.showPassword,
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () {
|
||||
provider.changeShowPassword();
|
||||
},
|
||||
child: Icon(
|
||||
provider.showPassword
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
color: ColorPalette.greyDarker,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(0)
|
||||
),
|
||||
onPressed: () {
|
||||
|
||||
},
|
||||
child: Text(
|
||||
'Forget the password?',
|
||||
style: TextStyle(
|
||||
color: ColorPalette.primary,
|
||||
),
|
||||
)
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Confirm',
|
||||
heightWrapContent: true,
|
||||
width: SizeConfig.width,
|
||||
marginVertical: 0,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
onPressed: nextStep,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
118
lib/features/auth/login/view/phone_number_view.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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/numeric_pad/numeric_pad.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/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:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class PhoneNumberView extends StatelessWidget {
|
||||
final void Function() nextStep;
|
||||
final TextEditingController controller;
|
||||
const PhoneNumberView({super.key, required this.nextStep, required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextTitle(title: 'Enter your phone number', fontSize: 24),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextFormView(
|
||||
name: 'Phone Number',
|
||||
keyboardType: TextInputType.number,
|
||||
ctrl: controller,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp(r'^0'))
|
||||
],
|
||||
contentPadding: EdgeInsets.all(1),
|
||||
prefixIcon: Container(
|
||||
width: SizeConfig.width * .23,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorPalette.grey,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(8),
|
||||
)),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ImageView(
|
||||
image: PathAssets.iconFlag,
|
||||
fit: BoxFit.contain,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
'+62',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Phone number must be filled';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Next',
|
||||
heightWrapContent: true,
|
||||
width: SizeConfig.width,
|
||||
marginVertical: 0,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
onPressed: nextStep,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Don't have an account yet?",
|
||||
style: TextStyle(
|
||||
color: ColorPalette.slate500,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
routePush(context, page: RegistrationView());
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(0)
|
||||
),
|
||||
child: Text(
|
||||
'Sign Up',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.primary
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
10
lib/features/auth/login/view_model/login_view_model.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class LoginViewModel extends ChangeNotifier {
|
||||
bool showPassword = false;
|
||||
|
||||
void changeShowPassword() {
|
||||
showPassword = !showPassword;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/component/text_form/text_form_view.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/auth/registration/view/submission_data/submission_parent.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/registration_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class RegistrationPasswordView extends StatelessWidget {
|
||||
const RegistrationPasswordView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => RegistrationViewModel(),
|
||||
builder: (context, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Sign Up'),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Consumer<RegistrationViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Form(
|
||||
key: provider.formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextCaption(
|
||||
title: 'Create your password',
|
||||
subtitle:
|
||||
'The password you create serves as your login',
|
||||
),
|
||||
TextFormView(
|
||||
name: 'Password',
|
||||
hintText: 'Input password',
|
||||
ctrl: provider.passwordCtrl,
|
||||
obscureText: !provider.showPassword,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Password must filled';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () {
|
||||
provider.toggleVisibility();
|
||||
},
|
||||
child: Icon(
|
||||
provider.showPassword
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
color: ColorPalette.greyDarker,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 32.0,
|
||||
),
|
||||
TextFormView(
|
||||
name: 'Confirm Password',
|
||||
hintText: 'Input password',
|
||||
ctrl: provider.confirmPasswordCtrl,
|
||||
obscureText: !provider.showPasswordConfirm,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Password must filled';
|
||||
} else if (value != provider.passwordCtrl.text) {
|
||||
return 'Password must be same';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
suffixIcon: GestureDetector(
|
||||
onTap: () {
|
||||
provider.toggleVisibilityConfirm();
|
||||
},
|
||||
child: Icon(
|
||||
provider.showPasswordConfirm
|
||||
? Icons.visibility_outlined
|
||||
: Icons.visibility_off_outlined,
|
||||
color: ColorPalette.greyDarker,
|
||||
),
|
||||
),
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Confirm',
|
||||
onPressed: () {
|
||||
if (provider.formKey.currentState!.validate()) {
|
||||
routePush(context, page: const DialogSuccess());
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DialogSuccess extends StatelessWidget {
|
||||
const DialogSuccess({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ImageView(
|
||||
image: PathAssets.imgSuccessSignup,
|
||||
width: SizeConfig.width * .8,
|
||||
),
|
||||
const TextCaption(
|
||||
title: 'Success',
|
||||
subtitle:
|
||||
'Congratulations, your account creation was successful!',
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
textAlignSubtitle: TextAlign.center,
|
||||
),
|
||||
SizedBox(
|
||||
height: SizeConfig.height * .2,
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Next',
|
||||
marginVertical: 8.0,
|
||||
onPressed: () {
|
||||
routePush(context,
|
||||
page: const SubmissionParent(),
|
||||
routeType: RouteType.pushReplace);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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_caption/text_caption.dart';
|
||||
import 'package:cims_apps/core/route/route.dart';
|
||||
import 'package:cims_apps/features/bottom_navigation_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RegistrationSuccessView extends StatelessWidget {
|
||||
const RegistrationSuccessView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
const ImageView(image: PathAssets.imgFinish),
|
||||
const TextCaption(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
textAlignSubtitle: TextAlign.center,
|
||||
title: 'Registration Successful!',
|
||||
subtitle:
|
||||
'Please wait for the data verification process so that you can start investing.',
|
||||
),
|
||||
const Spacer(),
|
||||
ButtonView(
|
||||
name: 'Next',
|
||||
marginVertical: 0.0,
|
||||
onPressed: () {
|
||||
routePush(context, page: const BottomNavigationView());
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,11 +1,16 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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/otp/otp_view.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/component/text_form/text_form_view.dart';
|
||||
import 'package:cims_apps/core/route/route.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/initial_registration_step.dart';
|
||||
import 'package:cims_apps/features/bottom_navigation_view.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/registration_viewmodel.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class RegistrationView extends StatelessWidget {
|
||||
static const routName = '/RegistrationView';
|
||||
@@ -13,12 +18,43 @@ class RegistrationView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
showOtpWidget() {
|
||||
Navigator.of(context).pop();
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
enableDrag: false,
|
||||
builder: (BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: MediaQueryData.fromView(
|
||||
WidgetsBinding.instance.window,
|
||||
).padding.top,
|
||||
),
|
||||
child: const OtpView(
|
||||
title: 'Sign Up',
|
||||
contentTitle: 'Check your SMS',
|
||||
contentSubtitle:
|
||||
'Enter 4 digit code We’ve sent to verify your phone number',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => RegistrationViewModel(),
|
||||
builder: (context, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Sign Up'),
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Consumer<RegistrationViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Form(
|
||||
key: provider.formKeyPhone,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -26,11 +62,56 @@ class RegistrationView extends StatelessWidget {
|
||||
title: 'Enter your phone number',
|
||||
subtitle: 'Input your registered phone number',
|
||||
),
|
||||
TextFormView(name: 'Phone Number'),
|
||||
TextFormView(
|
||||
name: 'Phone Number',
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp(r'^0'))
|
||||
],
|
||||
prefixIcon: Container(
|
||||
width: SizeConfig.width * .23,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
margin: const EdgeInsets.only(right: 16),
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorPalette.grey,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(8),
|
||||
)),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ImageView(
|
||||
image: PathAssets.iconFlag,
|
||||
fit: BoxFit.contain,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
'+62',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
ctrl: provider.phoneNumberCtrl,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Phone number must be filled';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Next',
|
||||
onPressed: () {
|
||||
routePush(context, page: const InitialRegistrationStep());
|
||||
if (provider.formKeyPhone.currentState!.validate()) {
|
||||
showOtpWidget();
|
||||
}
|
||||
},
|
||||
),
|
||||
Align(
|
||||
@@ -42,14 +123,11 @@ class RegistrationView extends StatelessWidget {
|
||||
text: 'Already have an account? ',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
decoration: TextDecoration.underline,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
print('object');
|
||||
},
|
||||
recognizer: TapGestureRecognizer()..onTap = () {},
|
||||
text: ' Sign In',
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
@@ -60,7 +138,10 @@ class RegistrationView extends StatelessWidget {
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,112 @@
|
||||
import 'package:cims_apps/application/component/button/back_button_view.dart';
|
||||
import 'package:cims_apps/application/component/button/button_view.dart';
|
||||
import 'package:cims_apps/application/component/list_tile/list_tile_view.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.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/auth/registration/view/submission_data/submission_parent.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ConfirmBankAccount extends StatelessWidget {
|
||||
const ConfirmBankAccount({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List listData = [
|
||||
{'title': 'Bank Name', 'subtitle': 'Bank Mandiri'},
|
||||
{'title': 'Account Number', 'subtitle': '123002212084'},
|
||||
{'title': 'Account Owner Name', 'subtitle': 'Muhamad Rosyidin'},
|
||||
{'title': 'Name on ID card', 'subtitle': 'Muhamad Rosyidin'},
|
||||
];
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const BackButtonView(),
|
||||
const Text('Registration'),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * 0.1,
|
||||
)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
height: SizeConfig.height * .85,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextCaption(title: 'Bank account confirmation'),
|
||||
SizedBox(
|
||||
height: SizeConfig.height * .6,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...listData.map((e) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
e['title'],
|
||||
style: const TextStyle(
|
||||
color: ColorPalette.slate400, fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
e['subtitle'],
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: ColorPalette.slate800,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
const ListTileView(
|
||||
title:
|
||||
'Make sure your data is correct as it will affect the disbursement process',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ButtonView(
|
||||
name: 'Recheck',
|
||||
isOutlined: true,
|
||||
width: SizeConfig.width * .42,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Confirm',
|
||||
width: SizeConfig.width * .42,
|
||||
onPressed: () {
|
||||
routePush(context, page: const SubmissionParent());
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/component/button/back_button_view.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_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GuideScreen extends StatelessWidget {
|
||||
const GuideScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List listImg = [
|
||||
{
|
||||
'urlImg': PathAssets.imgGuide1,
|
||||
'title': 'Passbook',
|
||||
'subtitle': 'Look in your passbook for the account number'
|
||||
},
|
||||
{
|
||||
'urlImg': PathAssets.imgGuide2,
|
||||
'title': 'Mobile Banking App',
|
||||
'subtitle':
|
||||
'Open the mobile banking app and you will find your account number'
|
||||
},
|
||||
];
|
||||
|
||||
listGuide() {
|
||||
return Column(
|
||||
children: listImg.map((e) {
|
||||
return Container(
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height * .18,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
margin: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorPalette.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: ColorPalette.slate400,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: ImageView(
|
||||
image: e['urlImg'],
|
||||
width: SizeConfig.width * .4,
|
||||
fit: BoxFit.fill,
|
||||
borderRadius: 8,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Text(
|
||||
e['title'],
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
e['subtitle'],
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const BackButtonView(),
|
||||
const Text('Guide'),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * 0.1,
|
||||
)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
const ImageView(image: PathAssets.imgGuideBank),
|
||||
const TextCaption(
|
||||
textAlign: TextAlign.center,
|
||||
title: 'Guide to knowing your account number'),
|
||||
listGuide(),
|
||||
ButtonView(
|
||||
name: 'close',
|
||||
isOutlined: true,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
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/select_form/select_form_view.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/component/text_form/text_form_view.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/route/route.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/data_bank/guide_screen.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubmitBankAccount extends StatelessWidget {
|
||||
const SubmitBankAccount({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<ItemSelectForm> listForm = [
|
||||
ItemSelectForm('key1', 'BCA'),
|
||||
ItemSelectForm('key2', 'BRI'),
|
||||
ItemSelectForm('key3', 'BNI'),
|
||||
ItemSelectForm('key4', 'BANK MANDIRI'),
|
||||
ItemSelectForm('key5', 'CIMB NIAGA'),
|
||||
];
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
)
|
||||
],
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
child: Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextCaption(title: 'Input your bank account data'),
|
||||
SelectFormView(
|
||||
name: 'Bank Name',
|
||||
listItem: listForm,
|
||||
onSelect: (value) {},
|
||||
),
|
||||
TextFormView(
|
||||
name: 'Account Number',
|
||||
trailingTitleWidget: SizedBox(
|
||||
width: 24,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
routePush(context, page: const GuideScreen());
|
||||
},
|
||||
child: const ImageView(image: PathAssets.iconQuestion),
|
||||
),
|
||||
),
|
||||
),
|
||||
TextFormView(name: 'Account Owner Name'),
|
||||
const Text(
|
||||
"Make sure the account you use is in your name, not someone else's",
|
||||
style: TextStyle(
|
||||
color: ColorPalette.slate400,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/component/button/back_button_view.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/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/submission_data/risk_profile/results_view.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/risk_profile/risk_profile_view_model/risk_profile_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class QuestionView extends StatefulWidget {
|
||||
const QuestionView({super.key});
|
||||
|
||||
@override
|
||||
State<QuestionView> createState() => _QuestionViewState();
|
||||
}
|
||||
|
||||
class _QuestionViewState extends State<QuestionView> {
|
||||
int currentPage = 0;
|
||||
PageController pageController = PageController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => RiskProfileViewModel(),
|
||||
child:
|
||||
Consumer<RiskProfileViewModel>(builder: (context, provider, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: true,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BackButtonView(),
|
||||
const Text('Risk Profile', textAlign: TextAlign.center),
|
||||
SizedBox(width: SizeConfig.width * 0.1)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: PageView(
|
||||
controller: pageController,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
children: provider.listRiskProfileQuestion.asMap().entries.map((e) {
|
||||
return containerQuestion(e.value, e.key,
|
||||
(index, score) => provider.selectedAnswer(index, score));
|
||||
}).toList(),
|
||||
),
|
||||
bottomNavigationBar: SizedBox(
|
||||
height: 84,
|
||||
child: ButtonView(
|
||||
name: 'Next',
|
||||
marginVertical: 16,
|
||||
onPressed: () {
|
||||
if (currentPage > 3) {
|
||||
int totalScore = provider.listScore
|
||||
.reduce((value, element) => value + element);
|
||||
provider.setTypeResult(totalScore);
|
||||
routePush(context,
|
||||
page: ResultsView(
|
||||
totalScore: totalScore.toString(),
|
||||
typeResult: provider.typeResult));
|
||||
} else {
|
||||
setState(() {
|
||||
currentPage += 1;
|
||||
});
|
||||
pageController.nextPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.ease);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget containerQuestion(RiskProfileQuestion data, int index,
|
||||
void Function(int index, int score) onTapAnswers) {
|
||||
return SizedBox(
|
||||
height: SizeConfig.height,
|
||||
width: SizeConfig.width,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.all(24),
|
||||
children: [
|
||||
ImageView(
|
||||
image: data.img,
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
'${index + 1} of 5 question',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, color: ColorPalette.primary),
|
||||
),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Text(
|
||||
data.question,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24,
|
||||
color: ColorPalette.slate800),
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Column(
|
||||
children: data.answers.asMap().entries.map((e) {
|
||||
bool selected = data.selectedScore == e.value.score;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
onTapAnswers(index, e.value.score);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
margin: EdgeInsets.only(top: 12),
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? ColorPalette.primary
|
||||
: ColorPalette.slate200),
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
e.value.title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: selected
|
||||
? ColorPalette.primary
|
||||
: ColorPalette.slate500,
|
||||
fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
if (selected) ...[
|
||||
Icon(Icons.check_circle_rounded,
|
||||
color: ColorPalette.primary)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,178 @@
|
||||
import 'package:cims_apps/application/component/button/back_button_view.dart';
|
||||
import 'package:cims_apps/application/component/button/button_view.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/auth/registration/view/submission_data/risk_profile/risk_profile_view_model/risk_profile_view_model.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/terms_and_condition/terms_and_condition_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ResultsView extends StatelessWidget {
|
||||
final String totalScore;
|
||||
final RiskProfileResult typeResult;
|
||||
const ResultsView(
|
||||
{super.key, required this.typeResult, required this.totalScore});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const BackButtonView(),
|
||||
const Text('Risk Profile', textAlign: TextAlign.center),
|
||||
SizedBox(width: SizeConfig.width * 0.1)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: typeResult.color,
|
||||
image: DecorationImage(
|
||||
image: AssetImage(typeResult.img),
|
||||
alignment: Alignment.centerRight)),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
typeResult.type,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24,
|
||||
color: ColorPalette.white),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Text(
|
||||
'Total Score :',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
color: ColorPalette.white),
|
||||
),
|
||||
Text(
|
||||
totalScore,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 28,
|
||||
color: ColorPalette.white),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(typeResult.desc,
|
||||
style: const TextStyle(
|
||||
color: ColorPalette.slate500, fontSize: 16)),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
const Text(
|
||||
'Suitable Product',
|
||||
style: TextStyle(
|
||||
color: ColorPalette.slate800,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 16,
|
||||
children: typeResult.suitableProduct.map((e) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: ColorPalette.slate200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: typeResult.color.withOpacity(0.1)),
|
||||
child: Image.asset(e['icon'],
|
||||
width: SizeConfig.width * 0.07,
|
||||
color: typeResult.color)),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
e['desc'],
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorPalette.slate800),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Re-test',
|
||||
onPressed: () {},
|
||||
marginVertical: 0,
|
||||
backgroundColor: ColorPalette.white,
|
||||
textColor: ColorPalette.primary,
|
||||
borderColor: ColorPalette.primary,
|
||||
isOutlined: true,
|
||||
textSize: 16,
|
||||
heightWrapContent: true,
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
width: SizeConfig.width,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Confirm',
|
||||
onPressed: () {
|
||||
routePush(context, page: const TermsAndConditionView());
|
||||
},
|
||||
marginVertical: 0,
|
||||
textSize: 16,
|
||||
heightWrapContent: true,
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
width: SizeConfig.width,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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/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/submission_data/risk_profile/question_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RiskProfileView extends StatelessWidget {
|
||||
const RiskProfileView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: SizeConfig.width,
|
||||
height: SizeConfig.height,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
ImageView(image: PathAssets.imgDataReport),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
'Know Your Risk Profile',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: ColorPalette.slate800,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 24),
|
||||
),
|
||||
SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Text(
|
||||
'We will provide recommendations that match your profile and risk tolerance level.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate500),
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ImageView(
|
||||
image: PathAssets.iconShield,
|
||||
width: 20,
|
||||
height: 22,
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
'Your data is secure and encrypted',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.primary,
|
||||
fontSize: 16),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
ButtonView(
|
||||
name: "Let's Start",
|
||||
onPressed: () {
|
||||
routePush(context, page: QuestionView());
|
||||
},
|
||||
marginVertical: 0,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RiskProfileQuestion {
|
||||
String img;
|
||||
int selectedScore;
|
||||
String question;
|
||||
List<Answer> answers;
|
||||
|
||||
RiskProfileQuestion(this.img, this.question, this.selectedScore, this.answers);
|
||||
}
|
||||
|
||||
class RiskProfileResult {
|
||||
String type;
|
||||
String img;
|
||||
String desc;
|
||||
Color color;
|
||||
List<dynamic> suitableProduct;
|
||||
|
||||
RiskProfileResult(this.type, this.img, this.color, this.desc, this.suitableProduct);
|
||||
}
|
||||
|
||||
class Answer {
|
||||
int score;
|
||||
String title;
|
||||
|
||||
Answer(this.score, this.title);
|
||||
}
|
||||
|
||||
class RiskProfileViewModel extends ChangeNotifier {
|
||||
List<int> listScore = [0,0,0,0,0];
|
||||
RiskProfileResult typeResult = RiskProfileResult('', '', Colors.transparent, '', []);
|
||||
|
||||
List<RiskProfileQuestion> listRiskProfileQuestion = [
|
||||
RiskProfileQuestion(
|
||||
PathAssets.imgGrowing, 'How long is your planned investment horizon?', 0,
|
||||
[
|
||||
Answer(2, '<1 year'),
|
||||
Answer(4, '1-3 year'),
|
||||
Answer(6, '3-5 year'),
|
||||
Answer(8, '>5 year')
|
||||
]
|
||||
),
|
||||
RiskProfileQuestion(
|
||||
PathAssets.imgLeader, 'Investment objectives of mutualfund investors?', 0,
|
||||
[
|
||||
Answer(2, 'Security of Investment Funds'),
|
||||
Answer(4, 'Investment Income and Security'),
|
||||
Answer(6, 'Revenue and growth over the long term'),
|
||||
Answer(8, 'Growth')
|
||||
]
|
||||
),
|
||||
RiskProfileQuestion(
|
||||
PathAssets.imgBusinessFailure, 'How much can I afford to lose on my investment (Risk level you can afford)?', 0,
|
||||
[
|
||||
Answer(2, '0%'),
|
||||
Answer(4, '<25%'),
|
||||
Answer(6, '26-50%'),
|
||||
Answer(8, '>50%')
|
||||
]
|
||||
),
|
||||
RiskProfileQuestion(
|
||||
PathAssets.imgMoneyIncome, 'I am willing to use .... % of my income for investment (financial circumstances of the financier)?', 0,
|
||||
[
|
||||
Answer(2, '0%'),
|
||||
Answer(4, '<25%'),
|
||||
Answer(6, '26-50%'),
|
||||
Answer(8, '>50%')
|
||||
]
|
||||
),
|
||||
RiskProfileQuestion(
|
||||
PathAssets.imgDataAnalysis, 'Mutual Fund Investment Knowledge Level?', 0,
|
||||
[
|
||||
Answer(2, 'Low'),
|
||||
Answer(4, 'Medium'),
|
||||
Answer(6, 'High'),
|
||||
]
|
||||
)
|
||||
];
|
||||
|
||||
List<RiskProfileResult> listRiskProfileResult = [
|
||||
RiskProfileResult(
|
||||
'Conservative',
|
||||
PathAssets.imgCat,
|
||||
ColorPalette.green500,
|
||||
'Investors with a conservative risk profile are risk-averse or do not want to experience large losses. Therefore, mutual fund products that are suitable for conservative investors are products that have low risk and stable returns.',
|
||||
[
|
||||
{'desc': 'Money Market Mutual Fund', 'icon': PathAssets.iconStrongBox},
|
||||
{'desc': 'Fixed Income Mutual Fund', 'icon': PathAssets.iconMoneyReceive},
|
||||
{'desc': 'Balanced Mutual Fund', 'icon': PathAssets.iconBalance},
|
||||
]
|
||||
),
|
||||
RiskProfileResult(
|
||||
'Moderate',
|
||||
PathAssets.imgDeer,
|
||||
ColorPalette.orange500,
|
||||
'Investors with a moderate risk profile are investors who are ready to accept moderate risk to get higher returns than conservative mutual fund products. Therefore, mutual fund products that are suitable for moderate investors are products that have moderate risk and higher returns than conservative mutual fund products.',
|
||||
[
|
||||
{'desc': 'Fixed Income Mutual Fund', 'icon': PathAssets.iconMoneyReceive},
|
||||
{'desc': 'Balanced Mutual Fund', 'icon': PathAssets.iconBalance},
|
||||
]
|
||||
),
|
||||
RiskProfileResult(
|
||||
'Aggressive',
|
||||
PathAssets.imgLion,
|
||||
ColorPalette.purple500,
|
||||
'Investors with an aggressive risk profile are investors who are ready to accept high risks to get high returns. Therefore, mutual fund products that are suitable for aggressive investors are products that have high risk and high returns.',
|
||||
[
|
||||
{'desc': 'Equity Mutual Fund', 'icon': PathAssets.iconCoins},
|
||||
{'desc': 'Aggressive Balanced Fund', 'icon': PathAssets.iconBalance},
|
||||
]
|
||||
)
|
||||
];
|
||||
|
||||
void selectedAnswer(int index, int score) {
|
||||
listScore[index] = score;
|
||||
listRiskProfileQuestion[index].selectedScore = score;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setTypeResult(int totalScore) {
|
||||
if(totalScore <= 25){
|
||||
typeResult = listRiskProfileResult[0];
|
||||
}else if(totalScore <= 50){
|
||||
typeResult = listRiskProfileResult[1];
|
||||
}else{
|
||||
typeResult = listRiskProfileResult[2];
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@@ -1,9 +1,22 @@
|
||||
import 'package:cims_apps/application/component/button/back_button_view.dart';
|
||||
import 'package:cims_apps/application/component/button/button_view.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/auth/registration/view/submission_data/data_bank/confirm_bank_account.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/data_bank/submit_bank_account.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/risk_profile/risk_profile_view.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_data_id_card.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_photo_ktp.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_email.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_personal_data.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_photo_selfie.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_signature/initial_signature.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submit_signature/submit_signature.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:cims_apps/features/bottom_navigation_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubmissionParent extends StatefulWidget {
|
||||
static const routeName = '/SubmissionParent';
|
||||
@@ -14,21 +27,9 @@ class SubmissionParent extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SubmissionParentState extends State<SubmissionParent> {
|
||||
int _currentStep = 1;
|
||||
final int _stepAmount = 9;
|
||||
Widget _stepItem({bool isCurrentStep = false, bool isDone = false}) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (_currentStep > 1) {
|
||||
_currentStep--;
|
||||
} else if (_currentStep == 1) {
|
||||
_currentStep++;
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 4.0, left: 4.0),
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(right: 0.0, left: 4.0),
|
||||
height: 6,
|
||||
width: SizeConfig.width * .08,
|
||||
decoration: BoxDecoration(
|
||||
@@ -37,7 +38,6 @@ class _SubmissionParentState extends State<SubmissionParent> {
|
||||
: ColorPalette.greyBorderNeutrals,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,46 +48,75 @@ class _SubmissionParentState extends State<SubmissionParent> {
|
||||
case 2:
|
||||
return const SubmitEmail();
|
||||
case 3:
|
||||
return Container(
|
||||
child: Text("Step 3"),
|
||||
);
|
||||
return const SubmitPhotoKtp();
|
||||
case 4:
|
||||
return Container(
|
||||
child: Text("Step 4"),
|
||||
);
|
||||
return const SubmitPhotoSelfie();
|
||||
case 5:
|
||||
return Container(
|
||||
child: Text("Step 5"),
|
||||
);
|
||||
return const SubmitDataIdCard();
|
||||
case 6:
|
||||
return Container(
|
||||
child: Text("Step 6"),
|
||||
);
|
||||
return const SubmitBankAccount();
|
||||
case 7:
|
||||
return Container(
|
||||
child: Text("Step 7"),
|
||||
);
|
||||
return const InitialSignature();
|
||||
case 8:
|
||||
return Container(
|
||||
child: Text("Step 8"),
|
||||
);
|
||||
return const RiskProfileView();
|
||||
case 9:
|
||||
return Container(
|
||||
child: Text("Step 9"),
|
||||
);
|
||||
return const Text("Step 9");
|
||||
}
|
||||
}
|
||||
|
||||
_contentPush(int index) {
|
||||
switch (index) {
|
||||
case 6:
|
||||
routePush(context, page: const ConfirmBankAccount());
|
||||
case 7:
|
||||
routePush(context, page: const SubmitSignature());
|
||||
// case 8:
|
||||
// return const RiskProfileView();
|
||||
// case 9:
|
||||
// return Container(
|
||||
// child: Text("Step 9"),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
builder: (context, child) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
await routePush(context,
|
||||
page: const BottomNavigationView(),
|
||||
routeType: RouteType.pushReplace);
|
||||
return false;
|
||||
},
|
||||
child: Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Registration'),
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const BackButtonView(),
|
||||
const Text('Registration'),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * 0.1,
|
||||
)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
@@ -95,33 +124,43 @@ class _SubmissionParentState extends State<SubmissionParent> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: List.generate(
|
||||
_stepAmount,
|
||||
provider.stepAmount,
|
||||
(index) => _stepItem(
|
||||
isCurrentStep: _currentStep == index + 1,
|
||||
isCurrentStep:
|
||||
provider.getCurrentStep == index + 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: _content(_currentStep),
|
||||
)
|
||||
],
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: _content(provider.getCurrentStep),
|
||||
),
|
||||
Align(
|
||||
),
|
||||
provider.getCurrentStep == 3 ||
|
||||
provider.getCurrentStep == 4 ||
|
||||
provider.getCurrentStep == 8
|
||||
? const SizedBox()
|
||||
: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ButtonView(
|
||||
name: 'Next',
|
||||
marginVertical: 16.0,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_currentStep++;
|
||||
});
|
||||
_contentPush(provider.getCurrentStep);
|
||||
provider.nextSubmission(context);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,218 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/component/text_form/text_form_view.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/utils/size_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SubmitDataIdCard extends StatelessWidget {
|
||||
const SubmitDataIdCard({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List listImg = [
|
||||
{'urlImg': PathAssets.imgKtpClear, 'tag': 'ID Card'},
|
||||
{'urlImg': PathAssets.imgSelfieClear, 'tag': 'Selfie with ID Card'},
|
||||
];
|
||||
|
||||
bottomSheet() {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius:
|
||||
BorderRadius.vertical(top: Radius.circular(18))),
|
||||
height: SizeConfig.height * .32,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const ImageView(
|
||||
image: PathAssets.iconShield,
|
||||
width: 20,
|
||||
height: 22,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Will my data be safe?',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorPalette.slate400,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Text(
|
||||
'We only request data in accordance with OJK regulations. Your data is encrypted and will not be shared with third parties without your consent.',
|
||||
style: TextStyle(
|
||||
color: ColorPalette.slate400,
|
||||
),
|
||||
),
|
||||
ButtonView(
|
||||
name: 'OK',
|
||||
isOutlined: true,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget photoDocument() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
'Photo Document',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: listImg.map((e) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: SizeConfig.height * .18,
|
||||
width: SizeConfig.width * .45,
|
||||
child: ImageView(
|
||||
image: e['urlImg'],
|
||||
fit: BoxFit.fill,
|
||||
borderRadius: 12,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * .43,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
e['tag'],
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 38,
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(
|
||||
color: ColorPalette.slate200))),
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
Icons.camera_alt_outlined,
|
||||
color: ColorPalette.slate500,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextCaption(title: 'Check your ID card data for accuracy'),
|
||||
TextFormView(name: 'NIK'),
|
||||
TextFormView(name: 'Full Name'),
|
||||
TextFormView(
|
||||
name: 'Birth Date',
|
||||
suffixIcon: const Icon(
|
||||
Icons.calendar_today_rounded,
|
||||
color: ColorPalette.slate400,
|
||||
),
|
||||
),
|
||||
photoDocument(),
|
||||
Container(
|
||||
width: SizeConfig.width,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
margin: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorPalette.blue50,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: ColorPalette.greyLights,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const ImageView(
|
||||
image: PathAssets.iconShield,
|
||||
width: 20,
|
||||
height: 22,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Will my data be safe?',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
bottomSheet();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: ColorPalette.primary,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,21 +1,81 @@
|
||||
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_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/component/text_form/text_form_view.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubmitEmail extends StatelessWidget {
|
||||
const SubmitEmail({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget _emailVerify() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextCaption(title: 'Enter your e-mail'),
|
||||
TextFormView(
|
||||
name: 'E-mail Address',
|
||||
hintText: 'Input e-mail address',
|
||||
const ImageView(image: PathAssets.imgEmail),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(children: [
|
||||
const TextSpan(
|
||||
text:
|
||||
'We have sent a verification link to your e-mail. \nPlease check your email for ',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
print('object');
|
||||
},
|
||||
text: 'verification',
|
||||
style: const TextStyle(
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ' to \ncontinue registration.',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
builder: (context, child) {
|
||||
return Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
!provider.isEmailVerify
|
||||
? const TextCaption(title: 'Enter your e-mail')
|
||||
: const TextCaption(title: 'Check your e-mail '),
|
||||
!provider.isEmailVerify
|
||||
? TextFormView(
|
||||
name: 'E-mail Address',
|
||||
hintText: 'Input e-mail address',
|
||||
onTap: () {
|
||||
provider.submitEmail();
|
||||
},
|
||||
)
|
||||
: _emailVerify(),
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,56 @@
|
||||
import 'package:cims_apps/application/component/select_form/select_form_view.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubmitPersonalData extends StatelessWidget {
|
||||
const SubmitPersonalData({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
List<ItemSelectForm> listForm = [
|
||||
ItemSelectForm('key1', 'text'),
|
||||
ItemSelectForm('key2', 'text'),
|
||||
ItemSelectForm('key3', 'text'),
|
||||
ItemSelectForm('key4', 'text'),
|
||||
ItemSelectForm('key5', 'text'),
|
||||
];
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
builder: (context, child) {
|
||||
return Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
TextCaption(title: 'Your personal details'),
|
||||
const TextCaption(title: 'Your personal details'),
|
||||
SelectFormView(
|
||||
name: 'Occupation',
|
||||
hintText: 'Select occupation ',
|
||||
bottomSheetTitle: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('Occupation'),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(
|
||||
Icons.clear,
|
||||
size: 20,
|
||||
color: ColorPalette.greyBase,
|
||||
)),
|
||||
],
|
||||
),
|
||||
listItem: listForm,
|
||||
onSelect: (value) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,114 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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/take_picture_screen/take_picture_screen.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.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/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubmitPhotoKtp extends StatelessWidget {
|
||||
const SubmitPhotoKtp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List listImg = [
|
||||
{'urlImg': PathAssets.imgKtpBlur, 'tag': 'Blurry Photo'},
|
||||
{'urlImg': PathAssets.imgKtpLight, 'tag': 'Light Reflection'},
|
||||
{'urlImg': PathAssets.imgKtpCropped, 'tag': 'Cropped Photo'},
|
||||
{'urlImg': PathAssets.imgKtpClear, 'tag': 'Clear Photo'},
|
||||
];
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
),
|
||||
],
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: SizeConfig.height * .75,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const TextCaption(
|
||||
title: 'Take a photo your ID card',
|
||||
subtitle:
|
||||
'Make sure your photo is clearly legible for identity verification purposes',
|
||||
),
|
||||
SizedBox(
|
||||
width: SizeConfig.height,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: List.generate(listImg.length, (index) {
|
||||
final urlList = listImg[index]['urlImg'];
|
||||
final tag = listImg[index]['tag'];
|
||||
return Column(
|
||||
children: [
|
||||
ImageView(
|
||||
image: urlList,
|
||||
width: SizeConfig.width * .42,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
tag,
|
||||
style: const TextStyle(
|
||||
color: ColorPalette.slate800,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
// const Spacer(),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ImageView(
|
||||
image: PathAssets.iconShield,
|
||||
width: 20,
|
||||
height: 22,
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'In accordance with OJK regulations, an ID card is required to purchase mutual funds.',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return ButtonView(
|
||||
name: 'Take a Photo',
|
||||
marginVertical: 16.0,
|
||||
onPressed: () {
|
||||
provider.initCamera().then((cameras) {
|
||||
routePush(context,
|
||||
page: TakePictureScreen(
|
||||
camera: cameras[0],
|
||||
takeContent: 'ktp',
|
||||
));
|
||||
});
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.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/take_picture_screen/take_picture_screen.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.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/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SubmitPhotoSelfie extends StatelessWidget {
|
||||
const SubmitPhotoSelfie({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List listImg = [
|
||||
{
|
||||
'urlImg': PathAssets.imgSelfieBlur,
|
||||
'tag': 'Blurry and ID card is not visible'
|
||||
},
|
||||
{
|
||||
'urlImg': PathAssets.imgSelfieClear,
|
||||
'tag': 'ID card and face clearly visible'
|
||||
},
|
||||
];
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
)
|
||||
],
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: SizeConfig.height * .75,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const TextCaption(
|
||||
title: 'Take a selfie with your ID card',
|
||||
subtitle:
|
||||
'Make sure your face and identity are clearly visible to ensure your identity is correct.',
|
||||
),
|
||||
SizedBox(
|
||||
height: SizeConfig.height * .42,
|
||||
width: SizeConfig.width,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
spacing: 2,
|
||||
runSpacing: 2,
|
||||
children: List.generate(listImg.length, (index) {
|
||||
final urlList = listImg[index]['urlImg'];
|
||||
final tag = listImg[index]['tag'];
|
||||
return Column(
|
||||
children: [
|
||||
ImageView(
|
||||
image: urlList,
|
||||
width: SizeConfig.width * .35,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * .42,
|
||||
child: Text(
|
||||
tag,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: ColorPalette.slate800,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ImageView(
|
||||
image: PathAssets.iconShield,
|
||||
width: 20,
|
||||
height: 22,
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'In accordance with OJK regulations, a selfie with ID card is required to purchase mutual funds.',
|
||||
maxLines: 2,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return ButtonView(
|
||||
name: 'Take a Photo',
|
||||
marginVertical: 16.0,
|
||||
onPressed: () {
|
||||
provider.initCamera().then((cameras) {
|
||||
routePush(context,
|
||||
page: TakePictureScreen(
|
||||
camera: cameras[1],
|
||||
takeContent: 'selfie',
|
||||
));
|
||||
});
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
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/list_tile/list_tile_view.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class InitialSignature extends StatelessWidget {
|
||||
const InitialSignature({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
)
|
||||
],
|
||||
builder: (context, child) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextCaption(title: 'Draw your digital sign'),
|
||||
ImageView(image: PathAssets.frameSignature),
|
||||
ListTileView(
|
||||
title:
|
||||
'Make sure the sign you draw is match with your ID Card'),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cims_apps/application/component/button/back_button_view.dart';
|
||||
import 'package:cims_apps/application/component/button/button_view.dart';
|
||||
import 'package:cims_apps/application/component/list_tile/list_tile_view.dart';
|
||||
import 'package:cims_apps/application/component/text_caption/text_caption.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/auth/registration/view/submission_data/submission_parent.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
|
||||
|
||||
class SubmitSignature extends StatelessWidget {
|
||||
const SubmitSignature({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
GlobalKey<SfSignaturePadState> signaturePadKey = GlobalKey();
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
builder: (context, child) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 70,
|
||||
backgroundColor: Colors.white,
|
||||
surfaceTintColor: Colors.white,
|
||||
automaticallyImplyLeading: false,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const BackButtonView(),
|
||||
const Text('Registration'),
|
||||
SizedBox(
|
||||
width: SizeConfig.width * 0.1,
|
||||
)
|
||||
],
|
||||
),
|
||||
shape: const RoundedRectangleBorder(
|
||||
side: BorderSide(color: ColorPalette.slate200)),
|
||||
),
|
||||
body: Container(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const TextCaption(title: 'Draw your digital sign'),
|
||||
SizedBox(
|
||||
height: SizeConfig.height * .28,
|
||||
child: DottedBorder(
|
||||
color: ColorPalette.primary,
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(8),
|
||||
padding: const EdgeInsets.all(6),
|
||||
strokeWidth: 2.0,
|
||||
dashPattern: const [14, 0, 0, 8],
|
||||
child: SfSignaturePad(
|
||||
key: signaturePadKey,
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const ListTileView(
|
||||
title:
|
||||
'Make sure the sign you draw is match with your ID Card'),
|
||||
const Spacer(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ButtonView(
|
||||
name: 'Delete',
|
||||
isOutlined: true,
|
||||
width: SizeConfig.width * .42,
|
||||
onPressed: () {
|
||||
signaturePadKey.currentState?.clear();
|
||||
},
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Next',
|
||||
width: SizeConfig.width * .42,
|
||||
onPressed: () async {
|
||||
// ui.Image image = await _signaturePadKey.currentState!.toImage();
|
||||
routePush(context, page: const SubmissionParent());
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
import 'package:cims_apps/application/component/custom_app_bar/custom_app_bar.dart';
|
||||
import 'package:cims_apps/application/component/button/button_view.dart';
|
||||
import 'package:cims_apps/application/theme/color_palette.dart';
|
||||
import 'package:cims_apps/core/route/route.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/submission_data/submission_parent.dart';
|
||||
import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class TermsAndConditionView extends StatelessWidget {
|
||||
const TermsAndConditionView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<String> listRules = [
|
||||
'I have never committed nor been involved in any breach or violations of laws, especially that in financial terms such as corruption, manipulation, money laundering or terrorism',
|
||||
'I have received comprehensive description from mutual marketing officers and fully understood Mutual Fund’s characteristics and therefore is ready for any risks occurring from investing in mutual fund',
|
||||
'I have read and understood the content of prospectus, monthly report of mutual fun performance, products and other information related to the Mutual Fund that I am about to purchase',
|
||||
'I fully understand that the Mutual Fund is the investment product of PT Gemilang Indonesia Manajemen Investasi and not the product of any selling agent',
|
||||
'I fully understand that Investment Product is not included in Government Warranty or Deposit Warranty Institution and therefore such product is not guaranteed by government',
|
||||
'I agree to relieve PT Gemilang Indonesia Manajemen Investasi any claims, cost and expenses related to or occurred due to PT Gemilang Indonesia Manajemen Investasi’s actions with regards to its instructions of my mutual fun unit transactions',
|
||||
'I fully understand, consider and I am fully responsible for all the investment decision I have made without any influence of PT Gemilang Indonesia Manajemen Investasi or its employees, and;',
|
||||
'I declare that all data I have presented are true',
|
||||
'I am willing to comply with the provisions set forth in laws in the financial services sectors',
|
||||
'PT Gemilang Indonesia Manajemen Investasi may refuse and close business relationship, refuse to transac with prospective customers and/or customers under UJK regulation No. 12/POJK. 01/2017 on the Implementation of Anti Money Laundering and Terrorism Funding Prevention Program in Financial Service Sector',
|
||||
'I am willing to provide my data and information from PT Gemilang Indonesia Manajemen Investasi to groups'
|
||||
];
|
||||
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => SubmissionDataViewModel(),
|
||||
builder: (context, child) {
|
||||
return Scaffold(
|
||||
appBar:
|
||||
const CustomAppBar(height: 70, title: 'Terms And Condition'),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'In relevance with the data that i have submitted and in relation to the purchase of Mutual Fund Products, I hereby declare that:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.slate800),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...listRules.asMap().entries.map((e) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: e.key != 0 ? 12 : 0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('${e.key + 1}',
|
||||
style: const TextStyle(
|
||||
color: ColorPalette.slate500)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(e.value,
|
||||
style: const TextStyle(
|
||||
color: ColorPalette.slate500)))
|
||||
],
|
||||
),
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: Consumer<SubmissionDataViewModel>(
|
||||
builder: (context, provider, child) {
|
||||
return Container(
|
||||
height: 84,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ButtonView(
|
||||
name: 'Decline',
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
marginVertical: 16,
|
||||
backgroundColor: ColorPalette.white,
|
||||
textColor: ColorPalette.primary,
|
||||
isOutlined: true,
|
||||
borderColor: ColorPalette.primary,
|
||||
)),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ButtonView(
|
||||
name: 'Accept',
|
||||
onPressed: () {
|
||||
provider.nextSubmission(context);
|
||||
routePush(context,
|
||||
page: const SubmissionParent());
|
||||
},
|
||||
marginVertical: 16))
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RegistrationViewModel extends ChangeNotifier {
|
||||
TextEditingController passwordCtrl = TextEditingController();
|
||||
TextEditingController confirmPasswordCtrl = TextEditingController();
|
||||
TextEditingController phoneNumberCtrl = TextEditingController();
|
||||
var formKey = GlobalKey<FormState>();
|
||||
var formKeyPhone = GlobalKey<FormState>();
|
||||
bool showPassword = false;
|
||||
bool showPasswordConfirm = false;
|
||||
|
||||
void toggleVisibility() {
|
||||
showPassword = !showPassword;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void toggleVisibilityConfirm() {
|
||||
showPasswordConfirm = !showPasswordConfirm;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SubmissionDataViewModel extends ChangeNotifier {
|
||||
static int _currentStep = 1;
|
||||
int get getCurrentStep => _currentStep;
|
||||
int stepAmount = 9;
|
||||
bool _isEmailVerify = false;
|
||||
bool get isEmailVerify => _isEmailVerify;
|
||||
|
||||
Future<List<CameraDescription>> initCamera() async {
|
||||
final cameras = await availableCameras();
|
||||
final camerasDesc = cameras;
|
||||
return camerasDesc;
|
||||
}
|
||||
|
||||
submitEmail() {
|
||||
_isEmailVerify = !_isEmailVerify;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
onWillPopSubmission(BuildContext context) {
|
||||
if (getCurrentStep != 1) {
|
||||
_currentStep--;
|
||||
notifyListeners();
|
||||
} else {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
}
|
||||
|
||||
nextSubmission(BuildContext context) {
|
||||
if (getCurrentStep < stepAmount) {
|
||||
_currentStep++;
|
||||
} else {
|
||||
//ToDo : Go To next step after completing the submission
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
@@ -1,4 +1,7 @@
|
||||
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/plan/plan_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 +18,10 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
||||
Widget build(BuildContext context) {
|
||||
///TODO: masukan pagenya dilistWidget ini
|
||||
List<Widget> listWidget = [
|
||||
Container(
|
||||
color: Colors.amberAccent,
|
||||
),
|
||||
Container(
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
Container(),
|
||||
Container(),
|
||||
HomeView(),
|
||||
PlanView(),
|
||||
Container(),
|
||||
PortofolioView(),
|
||||
Container(),
|
||||
];
|
||||
|
||||
@@ -33,8 +31,8 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.search),
|
||||
label: 'Search',
|
||||
icon: Icon(Icons.file_open),
|
||||
label: 'Plan',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.compare_arrows),
|
||||
@@ -52,7 +50,10 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
||||
|
||||
return Scaffold(
|
||||
body: listWidget[_selectedIndex],
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: BottomNavigationBar(
|
||||
elevation: 0,
|
||||
onTap: (value) {
|
||||
setState(() {
|
||||
_selectedIndex = value;
|
||||
@@ -60,12 +61,14 @@ class _BottomNavigationViewState extends State<BottomNavigationView> {
|
||||
},
|
||||
currentIndex: _selectedIndex,
|
||||
items: listNavigation,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showUnselectedLabels: true,
|
||||
selectedItemColor: ColorPalette.primary,
|
||||
unselectedItemColor: Colors.black,
|
||||
selectedLabelStyle: const TextStyle(color: ColorPalette.primary),
|
||||
unselectedLabelStyle: const TextStyle(color: Colors.black),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,571 @@
|
||||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:cims_apps/application/assets/path_assets.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_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/auth/registration/view/initial_registration_step.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/registration_view.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(0, [
|
||||
'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,
|
||||
),
|
||||
if(listStepVerification.currentStep == 1)...[
|
||||
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)
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]else if(listStepVerification.currentStep == 0)...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorPalette.blue50,
|
||||
borderRadius: BorderRadius.circular(12)
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Let's start registering your data to start mutual fund investment at PT Gemilang Indonesia",
|
||||
style: TextStyle(
|
||||
color: ColorPalette.slate500
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Registration',
|
||||
width: SizeConfig.width,
|
||||
marginVertical: 0,
|
||||
heightWrapContent: true,
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
onPressed: () {
|
||||
routePush(context, page: InitialRegistrationStep());
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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.green100
|
||||
),
|
||||
child: Text(
|
||||
article.type,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: ColorPalette.green500
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/component/button/back_button_view.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 BackButtonView(),
|
||||
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,22 @@
|
||||
import 'package:cims_apps/application/component/custom_app_bar/custom_app_bar.dart';
|
||||
import 'package:cims_apps/application/component/goal_investing_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlanView extends StatelessWidget {
|
||||
const PlanView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(height: 70, title: 'Investment Plan'),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
GoalInvestingView()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,356 @@
|
||||
import 'dart:math';
|
||||
|
||||
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 InvestmentType {
|
||||
String name;
|
||||
int value;
|
||||
int mutualFunds;
|
||||
|
||||
InvestmentType(this.value, this.name, this.mutualFunds);
|
||||
}
|
||||
|
||||
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<InvestmentType> listInvestmentType = [
|
||||
InvestmentType(20, 'Money Market', 2),
|
||||
InvestmentType(15, 'Shares', 5),
|
||||
InvestmentType(8, 'Bonds', 3),
|
||||
InvestmentType(50, 'Sharia', 4),
|
||||
];
|
||||
|
||||
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: 16),
|
||||
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: 110,
|
||||
sections: sectionsDataPortofolio())),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
const Text('Total Mutual Fund',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 18,
|
||||
color: ColorPalette.slate400
|
||||
),
|
||||
),
|
||||
Text(listInvestmentType.map((e) => e.mutualFunds).reduce((value, element) => value + element).toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 44,
|
||||
fontWeight: FontWeight.w700
|
||||
)
|
||||
)
|
||||
,
|
||||
],
|
||||
)
|
||||
]),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
menuPortofolio(),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
...listColumnPortofolio(),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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 listInvestmentType.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: listInvestmentType.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 listInvestmentType.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.mutualFunds} Mutual Funds',
|
||||
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,316 @@
|
||||
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.only(top: 48, bottom: 24),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 2.5,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
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,794 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cims_apps/application/assets/path_assets.dart';
|
||||
import 'package:cims_apps/application/component/button/back_button_view.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(
|
||||
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: [
|
||||
BackButtonView(),
|
||||
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: Container(
|
||||
height: SizeConfig.height * .1,
|
||||
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: 32,
|
||||
),
|
||||
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
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
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.calendar_month_rounded),
|
||||
backgroundColor: ColorPalette.blue50,
|
||||
sizeBorderRadius: 8,
|
||||
isSecondaryColor: false,
|
||||
width: SizeConfig.width * .5,
|
||||
marginVertical: 0,
|
||||
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,
|
||||
marginVertical: 0,
|
||||
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,
|
||||
keyboardType: TextInputType.number,
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
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();
|
||||
},
|
||||
),
|
||||
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
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@@ -4,6 +4,8 @@ import 'package:cims_apps/application/component/image/image_view.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/auth/login/view/login_view.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/initial_registration_step.dart';
|
||||
import 'package:cims_apps/features/auth/registration/view/registration_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -37,9 +39,11 @@ class DashboardPublicView extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 32.0,
|
||||
horizontal: 24.0,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 32.0,
|
||||
bottom: 8.0,
|
||||
left: 24.0,
|
||||
right: 24.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -68,7 +72,9 @@ class DashboardPublicView extends StatelessWidget {
|
||||
isOutlined: true,
|
||||
width: SizeConfig.width * .43,
|
||||
height: SizeConfig.height * .06,
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
routePush(context, page: const LoginView());
|
||||
},
|
||||
),
|
||||
ButtonView(
|
||||
name: 'Sign Up',
|
||||
@@ -89,7 +95,9 @@ class DashboardPublicView extends StatelessWidget {
|
||||
image: PathAssets.iconGoogle,
|
||||
width: 26,
|
||||
),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
routePush(context, page: const InitialRegistrationStep());
|
||||
},
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@@ -17,7 +17,7 @@ class _SplashScreenState extends State<SplashScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(const Duration(seconds: 3)).then(
|
||||
(value) => routePush(context, page: const DashboardPublicView()),
|
||||
(value) => routePush(context, page: const DashboardPublicView(), routeType: RouteType.pushRemove),
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|