feat: risk profile view

This commit is contained in:
2024-02-10 16:29:00 +07:00
parent 7a1cddee03
commit 6977c8166d
21 changed files with 615 additions and 5 deletions

View File

@@ -0,0 +1,164 @@
import 'package:cims_apps/application/assets/path_assets.dart';
import 'package:cims_apps/application/component/button/button_back.dart';
import 'package:cims_apps/application/component/button/button_view.dart';
import 'package:cims_apps/application/component/image/image_view.dart';
import 'package:cims_apps/application/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/risk_profile/results_view.dart';
import 'package:cims_apps/features/auth/registration/view/risk_profile/risk_profile_view.dart';
import 'package:cims_apps/features/auth/registration/view/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: [
ButtonBack(),
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,187 @@
import 'package:cims_apps/application/assets/path_assets.dart';
import 'package:cims_apps/application/component/button/button_back.dart';
import 'package:cims_apps/application/component/button/button_view.dart';
import 'package:cims_apps/application/component/image/image_view.dart';
import 'package:cims_apps/application/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:cims_apps/features/auth/registration/view/risk_profile/risk_profile_view_model/risk_profile_view_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.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: [
ButtonBack(),
const Text('Risk Profile', textAlign: TextAlign.center),
SizedBox(
width: SizeConfig.width * 0.1
)
],
),
shape: const RoundedRectangleBorder(
side: BorderSide(color: ColorPalette.slate200)
),
),
body: SingleChildScrollView(
padding: 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: EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
typeResult.type,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
color: ColorPalette.white
),
),
SizedBox(height: 16,),
Text('Total Score :',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: ColorPalette.white
),
),
Text(totalScore,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 28,
color: ColorPalette.white
),
)
],
),
),
],
),
),
),
SizedBox(
height: 24,
),
Text(
typeResult.desc,
style: TextStyle(
color: ColorPalette.slate500,
fontSize: 16
)
),
SizedBox(
height: 24,
),
Text(
'Suitable Product',
style: TextStyle(
color: ColorPalette.slate800,
fontWeight: FontWeight.bold,
fontSize: 16
),
),
SizedBox(
height: 16,
),
Wrap(
runSpacing: 16,
children: typeResult.suitableProduct.map((e) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(color: ColorPalette.slate200),
),
child: Row(
children: [
Container(
padding: 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)
),
SizedBox(
width: 12,
),
Expanded(
child: Text(e['desc'],
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: ColorPalette.slate800
),
),
)
],
),
);
}).toList(),
),
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: EdgeInsets.all(16),
width: SizeConfig.width,
),
SizedBox(
height: 16,
),
ButtonView(
name: 'Confirm',
onPressed: () {
routePush(context, page: DialogSuccess());
},
marginVertical: 0,
textSize: 16,
heightWrapContent: true,
contentPadding: EdgeInsets.all(16),
width: SizeConfig.width,
)
],
),
),
);
}
}

View File

@@ -0,0 +1,112 @@
import 'package:cims_apps/application/assets/path_assets.dart';
import 'package:cims_apps/application/component/button/button_back.dart';
import 'package:cims_apps/application/component/button/button_view.dart';
import 'package:cims_apps/application/component/image/image_view.dart';
import 'package:cims_apps/application/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/risk_profile/question_view.dart';
import 'package:flutter/material.dart';
class RiskProfileView extends StatelessWidget {
const RiskProfileView({super.key});
@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: [
ButtonBack(),
const Text('Risk Profile', textAlign: TextAlign.center),
SizedBox(
width: SizeConfig.width * 0.1
)
],
),
shape: const RoundedRectangleBorder(
side: BorderSide(color: ColorPalette.slate200)
),
),
body: 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();
}
}