51 Commits

Author SHA1 Message Date
5cb76fca7f feat: registration success page 2024-02-16 18:03:36 +07:00
e86e67b9c9 fix: term condition page 2024-02-16 17:35:58 +07:00
a6248520ef Merge branch 'dev' of https://git.empatnusabangsa.com/nugrohob825/cims_apps into bayu/dev 2024-02-16 17:17:32 +07:00
eb1eb83d52 fix: risk profile 2024-02-16 17:17:19 +07:00
7706fe4387 Merge pull request 'yoga' (#10) from yoga into dev
Reviewed-on: #10
2024-02-16 17:14:45 +07:00
9da1675250 feat: submit signature 2024-02-16 17:00:19 +07:00
298d7f46d2 fix: submit data bank account 2024-02-16 16:03:06 +07:00
368f326123 merge conflict 2024-02-16 11:46:28 +07:00
a574f30424 fix: submit data bank account 2024-02-16 11:39:16 +07:00
3c1f7e210a fix: assets local history 2024-02-15 20:24:19 +07:00
b3a68b4436 feat: terms and condition view 2024-02-15 20:00:15 +07:00
ce2bf8a777 Merge pull request 'feat: wip plan view, and component numeric pad' (#9) from yoga into dev
Reviewed-on: #9
2024-02-15 19:56:43 +07:00
7e9c109fa2 feat: wip plan view, and component numeric pad 2024-02-15 19:55:21 +07:00
8537045d74 Merge pull request 'bayu/dev' (#8) from bayu/dev into dev
Reviewed-on: #8
2024-02-15 19:25:38 +07:00
23d189c288 feat: submit data bank account 2024-02-15 19:23:57 +07:00
4bad9cd18c feat: submit data id card 2024-02-15 17:34:40 +07:00
3dca727a5e fix: submit photo selfie 2024-02-15 14:14:31 +07:00
4c1cc7422b feat: submit photo selfie 2024-02-15 14:07:32 +07:00
219339f577 Merge pull request 'fix: padding text form view' (#7) from yoga into dev
Reviewed-on: #7
2024-02-12 17:07:20 +07:00
0c2441091f fix: padding text form view 2024-02-12 17:07:04 +07:00
d86820ec98 Merge pull request 'yoga' (#6) from yoga into dev
Reviewed-on: #6
2024-02-12 17:04:28 +07:00
ececa5e541 fix: pull origin dev 2024-02-12 17:04:01 +07:00
b0de8c255e feat: apk build 2024-02-12 17:00:20 +07:00
6977c8166d feat: risk profile view 2024-02-10 16:29:00 +07:00
7a1cddee03 fix: more detailing product view 2024-02-07 18:34:46 +07:00
db6e4d543d Merge pull request 'bayu/dev' (#4) from bayu/dev into dev
Reviewed-on: #4
2024-02-07 18:31:32 +07:00
d672a23564 fix: stepper submission data 2024-02-07 17:17:32 +07:00
5e97154100 rename file 2024-02-07 15:57:06 +07:00
59e6e82d13 fix: content padding text field 2024-02-07 15:54:53 +07:00
477eb5d2b1 feat: preview photo 2024-02-07 15:53:00 +07:00
370db229de feat: take photo 2024-02-07 15:52:50 +07:00
9475767021 feat: login view 2024-02-07 14:44:40 +07:00
0a347f5e6d Merge pull request 'yoga' (#3) from yoga into dev
Reviewed-on: #3
2024-02-06 21:51:04 +07:00
4d58a7dced fix: continued product view 2024-02-06 21:49:39 +07:00
81231505b1 fix: continued product view 2024-02-06 21:45:25 +07:00
6e2516d9c8 Merge pull request 'bayu/dev' (#2) from bayu/dev into dev
Reviewed-on: #2
2024-02-06 19:25:23 +07:00
e538fa5927 fix: sign up feature 2024-02-06 19:21:41 +07:00
7ec266cded fix: otp component 2024-02-06 18:38:34 +07:00
96c676ac4c feat: state time machine product view 2024-02-06 18:28:11 +07:00
e1cabe0a09 feat: product chart view 2024-02-06 17:48:49 +07:00
4b4b42beae add otp component 2024-02-06 17:02:12 +07:00
dacf5461f3 add registration password page 2024-02-06 14:40:10 +07:00
5d4bc47adf styling components 2024-02-06 14:39:16 +07:00
80e4657240 add string utils 2024-02-06 14:38:55 +07:00
d82d427bcc Merge remote-tracking branch 'origin/dev' into yoga
# Conflicts:
#	lib/application/assets/path_assets.dart
#	pubspec.yaml
2024-02-06 11:14:51 +07:00
e63e5588fb Merge pull request 'bayu/dev' (#1) from bayu/dev into dev
Reviewed-on: #1
2024-02-06 10:54:29 +07:00
f407eca735 feat: product view wip 2024-02-05 22:36:16 +07:00
ff1886cec1 fix: submission data regis 2024-02-05 18:11:16 +07:00
1616f22925 add initial take photo 2024-02-05 18:10:28 +07:00
0b754bf939 add plugin provider 2024-02-05 18:09:41 +07:00
0e7ad81345 feat: home view, portfolio view, invest type view 2024-02-05 15:49:02 +07:00
103 changed files with 6364 additions and 363 deletions

View File

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

View File

@@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
assets/icons/icon-cake.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/icons/icon-coins.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/icons/icon-flag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/icons/icon-house.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icons/icon-ktp1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
assets/icons/icon-ktp2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
assets/icons/icon-ktp3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
assets/icons/icon-ktp4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/icons/icon-toga.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

BIN
assets/images/img-cat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
assets/images/img-deer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
assets/images/img-lion.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

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

View 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)
),
);
}
}

View File

@@ -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 {
],
),
),
),
);
}
}

View 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);
}

View 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()
);
}
}

View 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(),
],
),
);
}
}

View 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,
)
);
}
}

View 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,
),
))
],
);
}),
),
);
});
}
}

View 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();
}
}

View 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,
),
);
}
}

View File

@@ -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());
},
),
),
],
)
],
)),
);
});
});
}
}

View File

@@ -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());
}
},
),
);
}
}

View File

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

View File

@@ -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,
)),
)
],
),
);
}
}

View 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,
),
);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
import 'package:intl/intl.dart';
class NumberFormatter {
NumberFormatter._();
static String numberCurrency(dynamic value, String symbol, String locale, {int? decimalDigits = 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;
}
}

View 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);
}
}

View 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());
}
}
}

View 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,
)
],
),
);
}
);
}
}

View 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
),
)
)
],
),
],
),
);
}
}

View File

@@ -0,0 +1,10 @@
import 'package:flutter/foundation.dart';
class LoginViewModel extends ChangeNotifier {
bool showPassword = false;
void changeShowPassword() {
showPassword = !showPassword;
notifyListeners();
}
}

View File

@@ -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);
},
)
],
),
),
);
}
}

View File

@@ -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());
},
)
],
),
),
);
}
}

View File

@@ -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 Weve 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 {
)
],
),
);
}),
),
);
});
}
}

View File

@@ -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());
},
),
],
)
],
),
),
),
);
}
}

View File

@@ -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),
)
],
),
),
);
}
}

View File

@@ -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,
),
),
],
);
}),
);
});
}
}

View File

@@ -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(),
)
],
),
);
}
}

View File

@@ -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,
)
],
),
),
);
}
}

View File

@@ -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,
)
],
)
],
),
);
}
}

View File

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

View File

@@ -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);
},
),
)
],
),
],
),
);
}),
);
});
}
}

View File

@@ -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,
),
)
],
),
)
],
),
);
}
}

View File

@@ -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(),
],
);
});
});
}
}

View File

@@ -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) {},
),
],
),
);
});
});
}
}

View File

@@ -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',
));
});
},
);
})
],
),
);
});
}
}

View File

@@ -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',
));
});
},
);
})
],
),
);
});
}
}

View File

@@ -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'),
],
);
});
}
}

View File

@@ -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());
},
),
],
)
],
);
}),
),
);
});
}
}

View File

@@ -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 Funds 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 Investasis 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))
],
),
);
}),
);
});
}
}

View File

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

View File

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

View File

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

View File

@@ -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
),
),
),
],
)
)
],
),
);
}
}

View File

@@ -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
),
),
],
)
],
)
],
)
],
),
);
}
}

View File

@@ -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()
],
),
),
);
}
}

View File

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

View File

@@ -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),
),
);
},
),
),
),
],
);
}
}

View File

@@ -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
),
),
)
],
),
)
],
)
],
);
}
}

View File

@@ -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,

View File

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

Some files were not shown because too many files have changed in this diff Show More