diff --git a/assets/images/select/btn_bg_generate_1.png b/assets/images/select/btn_bg_generate_1.png new file mode 100644 index 0000000..c51aa2a Binary files /dev/null and b/assets/images/select/btn_bg_generate_1.png differ diff --git a/assets/images/select/btn_bg_reflect_1.png b/assets/images/select/btn_bg_reflect_1.png new file mode 100644 index 0000000..6399add Binary files /dev/null and b/assets/images/select/btn_bg_reflect_1.png differ diff --git a/assets/images/select/btn_icon_begin.png b/assets/images/select/btn_icon_begin.png new file mode 100644 index 0000000..d973a51 Binary files /dev/null and b/assets/images/select/btn_icon_begin.png differ diff --git a/assets/images/select/label_bg_answer.png b/assets/images/select/label_bg_answer.png new file mode 100644 index 0000000..79cc785 Binary files /dev/null and b/assets/images/select/label_bg_answer.png differ diff --git a/assets/images/select/label_bg_career_1.png b/assets/images/select/label_bg_career_1.png new file mode 100644 index 0000000..cc4dbde Binary files /dev/null and b/assets/images/select/label_bg_career_1.png differ diff --git a/assets/images/select/pic_dialogue_type.png b/assets/images/select/pic_dialogue_type.png new file mode 100644 index 0000000..8da561d Binary files /dev/null and b/assets/images/select/pic_dialogue_type.png differ diff --git a/lib/app/models/models.dart b/lib/app/models/models.dart index 8fea70d..5ec3685 100644 --- a/lib/app/models/models.dart +++ b/lib/app/models/models.dart @@ -3,4 +3,5 @@ export 'user_model.dart'; export 'base_response_model.dart'; export 'token_model.dart'; export 'page_result_model.dart'; -export 'goal_model.dart'; \ No newline at end of file +export 'goal_model.dart'; +export 'question_answer_model.dart'; \ No newline at end of file diff --git a/lib/app/modules/question/models/question_answer_model.dart b/lib/app/models/question_answer_model.dart similarity index 100% rename from lib/app/modules/question/models/question_answer_model.dart rename to lib/app/models/question_answer_model.dart diff --git a/lib/app/modules/question/controllers/question_controller.dart b/lib/app/modules/question/controllers/question_controller.dart index 13175bc..f3ea600 100644 --- a/lib/app/modules/question/controllers/question_controller.dart +++ b/lib/app/modules/question/controllers/question_controller.dart @@ -1,11 +1,10 @@ // ignore_for_file: unnecessary_overrides import 'dart:async'; +import 'package:dreampad/app/models/models.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../models/question_answer_model.dart'; - class QuestionController extends GetxController { final step = 0.obs; final ipLeft = 149.obs; diff --git a/lib/app/modules/select/controllers/select_controller.dart b/lib/app/modules/select/controllers/select_controller.dart index e21324d..8523897 100644 --- a/lib/app/modules/select/controllers/select_controller.dart +++ b/lib/app/modules/select/controllers/select_controller.dart @@ -1,8 +1,10 @@ // ignore_for_file: unnecessary_overrides +import 'package:dreampad/app/models/models.dart'; import 'package:dreampad/app/routes/app_pages.dart'; import 'package:dreampad/app/shared/shared.dart'; import 'package:flustars_flutter3/flustars_flutter3.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../models/occupation_model.dart'; @@ -11,11 +13,22 @@ class SelectController extends GetxController { late int gender = 2; late int count = 4; late int page = 0; + final step = 0.obs; + final question = ''.obs; + final guide = ''.obs; + final btnTxt = '我要回答'.obs; + final allAnswer = false.obs; + final showQuestionDialog = false.obs; late List occupations = []; final confirm = false.obs; final confirmTitle = '为你推荐的梦想职业是'.obs; + final recommend = false.obs; + final showBtn = false.obs; + final showOccupationName = false.obs; final selectOccupation = Rx(null); final selectOccupations = RxList>([]); + final questionAnswers = RxList>([]); + final TextEditingController textController = TextEditingController(); @override void onInit() { super.onInit(); @@ -26,6 +39,24 @@ class SelectController extends GetxController { initMaleOccupation(); } changeOccupation(); + questionAnswers.add(QuestionAnswer( + index: 1, + left: 110.0, + top: 147.0, + question: '很酷的选择!能告诉我为什么选择这个梦想的职业吗?', + display: false, + answer: '', + guide: '我想成为下一个“爱因斯坦”,他被称为最聪明的人', + ).obs); + questionAnswers.add(QuestionAnswer( + index: 2, + left: 720.0, + top: 425.0, + question: '还有很多选择也非常精彩哦!你还有其他想选择的职业吗?', + display: false, + answer: '', + guide: '我只想做科学家', + ).obs); } @override @@ -208,8 +239,18 @@ class SelectController extends GetxController { } } + Future confimSelected() async { + confirm.value = true; + recommend.value = false; + confirmTitle.value = '你的梦想职业是'; + showOccupationName.value = true; + await Future.delayed(const Duration(milliseconds: 1000)); + showOccupationName.value = false; + } + Future selectedOccupation(Occupation occupation) async { selectOccupation.value = occupation; + await setStep(); for (var occupation in selectOccupations) { occupation.update((val) { val!.selected = false; @@ -221,6 +262,7 @@ class SelectController extends GetxController { var occupation = selectOccupations.firstWhere((t) => t.value.id == id); selectOccupation.value = occupation.value; confirm.value = true; + recommend.value = true; for (var occupation in selectOccupations) { occupation.update((val) { val!.selected = false; @@ -228,6 +270,63 @@ class SelectController extends GetxController { } } + Future setStep() async { + btnTxt.value = '我要回答'; + bool hasQuestion = false; + for (var questionAnswer in questionAnswers) { + if (!questionAnswer.value.display!) { + if (questionAnswer.value.index != step.value) { + showQuestionDialog.value = false; + } else { + showQuestionDialog.value = true; + } + step.value = questionAnswer.value.index!; + question.value = questionAnswer.value.question!; + guide.value = questionAnswer.value.guide; + hasQuestion = true; + allAnswer.value = false; + break; + } + } + if (!hasQuestion) { + showQuestionDialog.value = false; + allAnswer.value = true; + } + } + + Future updateAnswer(String answer) async { + var questionAnswer = + questionAnswers.firstWhere((t) => t.value.index == step.value); + questionAnswer.update((val) { + val!.answer = answer; + val.display = true; + }); + await setStep(); + textController.text = ''; + if (allAnswer.value) { + await Future.delayed(const Duration(milliseconds: 1000)); + for (var questionAnswer in questionAnswers) { + questionAnswer.update((val) { + val!.display = false; + }); + } + showBtn.value = true; + showOccupationName.value = true; + } + } + + Future reflect() async { + confirm.value = false; + for (var questionAnswer in questionAnswers) { + questionAnswer.update((val) { + val!.display = false; + }); + } + showBtn.value = false; + allAnswer.value = false; + showOccupationName.value = false; + } + Future openDream() async { SpUtil.putString(Constant.occupationName, selectOccupation.value!.name!); SpUtil.putInt(Constant.occupationId, selectOccupation.value!.id!); diff --git a/lib/app/modules/select/views/select_view.dart b/lib/app/modules/select/views/select_view.dart index c520354..42884d4 100644 --- a/lib/app/modules/select/views/select_view.dart +++ b/lib/app/modules/select/views/select_view.dart @@ -1,3 +1,4 @@ +import 'package:animated_text_kit/animated_text_kit.dart'; import 'package:dreampad/app/modules/select/views/animated_horizon_column_widget.dart'; import 'package:dreampad/app/routes/app_pages.dart'; import 'package:dreampad/app/shared/shared.dart'; @@ -82,8 +83,10 @@ class SelectView extends GetView { () => AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: OccupationListWidget( - key: ValueKey(controller.selectOccupations.first.value.name ?? ''), - keyBuilder: (index) => controller.selectOccupations[index].value.name ?? '', + key: ValueKey( + controller.selectOccupations.first.value.name ?? ''), + keyBuilder: (index) => + controller.selectOccupations[index].value.name ?? '', children: controller.selectOccupations.map((t) { return GestureDetector( key: ValueKey(t.value.id), @@ -190,20 +193,19 @@ class SelectView extends GetView { ), ), const RSizedBox(width: 34.0), - AnimatedVisibilityWidget( - isVisible: controller.selectOccupation.value != null, - child: ImageTxtButton( - text: '决定了', - imgName: 'select/btn_icon_confirm', - textStyle: TextStyles.mediumWhiteShadow28_022, - width: 264.0, - height: 80.0, - onPressed: () { - controller.confirm.value = true; - controller.confirmTitle.value = '你的梦想职业是'; - }, - ), - ), + AnimatedVisibilityWidget( + isVisible: controller.selectOccupation.value != null, + child: ImageTxtButton( + text: '决定了', + imgName: 'select/btn_icon_confirm', + textStyle: TextStyles.mediumWhiteShadow28_022, + width: 264.0, + height: 80.0, + onPressed: () async { + controller.confimSelected(); + }, + ), + ), ], ), ); @@ -285,14 +287,60 @@ class SelectView extends GetView { ), ), ), - buildConfirmBtn(context, controller.selectOccupation.value!.name!), + controller.recommend.value + ? buildRecommendConfirmBtn( + context, controller.selectOccupation.value!.name!) + : buildConfirmBtn( + context, controller.selectOccupation.value!.name!), + controller.showOccupationName.value + ? Container() + : Positioned( + left: 90.w, + top: 62.h, + child: Container( + width: 999.w, + height: 558.h, + color: const Color(0x8C02184B), + ), + ), + RSizedBox( + child: Stack( + children: controller.questionAnswers.map((element) { + if (element.value.display!) { + return Positioned( + top: element.value.top!.h, + left: element.value.left!.w, + child: _ShowUp( + key: ValueKey(element.value.answer), + child: buildAnswer( + context, + element.value.index!, + element.value.answer!, + ), + ), + ); + } else { + return Container(); + } + }).toList()), + ), + Positioned( + top: 207.h, + left: 378.w, + child: AnimatedVisibilityWidget( + animationWidgetBuilder: + AnimatedVisibilityWidget.fadeAnimationWidgetBuilder, + isVisible: !controller.allAnswer.value && + !controller.showOccupationName.value, + child: buildDialog(context)), + ), ], ), ), ); } - Widget buildConfirmBtn(BuildContext context, String name) { + Widget buildRecommendConfirmBtn(BuildContext context, String name) { return Positioned( bottom: 12.h, left: 243.w, @@ -380,10 +428,246 @@ class SelectView extends GetView { ), ); } + + Widget buildConfirmBtn(BuildContext context, String name) { + return Positioned( + bottom: 18.h, + left: 243.w, + child: Container( + width: 713.w, + height: 214.h, + decoration: const BoxDecoration( + image: DecorationImage( + image: Images.selectBgShadow, + fit: BoxFit.fill, + ), + ), + child: Obx( + () => Stack( + children: [ + controller.showBtn.value + ? Positioned( + left: 88.w, + top: 77.h, + child: ImageTxtButton( + text: '我再想想', + imgName: 'select/btn_bg_reflect_1', + textStyle: TextStyles.mediumWhiteShadow28_013, + width: 264.0, + height: 80.0, + onPressed: () async { + await controller.reflect(); + }, + ), + ) + : Container(), + controller.showBtn.value + ? Positioned( + right: 66.w, + top: 77.h, + child: ImageTxtButton( + text: '生成梦之建木', + imgName: 'select/btn_bg_generate_1', + textStyle: TextStyles.mediumWhiteShadow28_013, + width: 264.0, + height: 80.0, + onPressed: () async { + await controller.openDream(); + }, + ), + ) + : Container(), + controller.showOccupationName.value + ? Positioned( + left: 208.w, + top: 19.h, + child: ImageTxtButton( + text: name, + imgName: 'select/label_bg_career_1', + textStyle: TextStyles.mediumWhiteShadow28_113, + width: 324.0, + height: 45.0, + ), + ) + : Container(), + ], + ), + ), + ), + ); + } + + Widget buildAnswer(BuildContext context, int index, String answer) { + return Container( + width: 369.w, + height: 160.h, + decoration: const BoxDecoration( + image: DecorationImage( + image: Images.selectAnswer, + fit: BoxFit.fill, + ), + ), + child: Stack( + children: [ + Positioned( + top: 23.h, + left: 68.w, + child: Text( + index.toString(), + style: TextStyles.boldColor24, + ), + ), + Positioned( + top: 25.h, + left: 105.w, + child: Text( + '问题回答', + style: TextStyles.boldColor20, + ), + ), + Positioned( + top: 65.h, + left: 85.w, + child: RSizedBox( + height: 64.h, + width: 230.w, + child: Text( + answer, + style: TextStyles.mediumWhite18, + ), + ), + ), + ], + ), + ); + } + + Widget buildDialog(BuildContext context) { + return Obx( + () => AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: KeyedSubtree( + key: ValueKey(controller.question.value), + child: HookBuilder(builder: (context) { + final isReady = useState(false); + final isDialogShown = useState(false); + final isTextShown = useState(false); + useMemoized(() async { + await Future.delayed(const Duration(milliseconds: 500)); + isReady.value = true; + }); + return AnimatedVisibilityWidget( + isVisible: isReady.value, + isInitAnimated: true, + animationWidgetBuilder: + AnimatedVisibilityWidget.fadeAnimationWidgetBuilder, + duration: const Duration(milliseconds: 500), + onDone: (_) async { + await Future.delayed(const Duration(milliseconds: 500)); + isDialogShown.value = true; + }, + child: Container( + width: 444.w, + height: 248.h, + decoration: const BoxDecoration( + image: DecorationImage( + image: Images.selectDialog, + fit: BoxFit.fill, + ), + ), + padding: REdgeInsets.only(top: 28.0, left: 27.0, right: 63.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + width: double.infinity, + child: Visibility( + visible: isDialogShown.value, + child: AnimatedTextKit( + totalRepeatCount: 1, + pause: Duration.zero, + onFinished: () { + isTextShown.value = true; + }, + animatedTexts: [ + TypewriterAnimatedText( + controller.question.value, + textStyle: TextStyles.mediumWhiteShadow18_034, + cursor: '', + speed: const Duration(milliseconds: 150), + textAlign: TextAlign.start, + ) + ], + ), + ), + ), + ), + SizedBox(height: 16.h), + AnimatedVisibilityWidget( + isVisible: isTextShown.value, + animationWidgetBuilder: + AnimatedVisibilityWidget.fadeAnimationWidgetBuilder, + child: ImageTxtButton( + text: controller.btnTxt.value, + imgName: 'question/btn_icon_begin', + textStyle: TextStyles.boldWhite20_014, + width: 186.0, + height: 54.0, + onPressed: () async { + await controller.setStep(); + if (controller.showQuestionDialog.value) { + SmartDialog.show( + alignment: Alignment.topCenter, + onDismiss: () async { + FocusScope.of(context).requestFocus(); + if (controller.textController.text.isNotEmpty) { + await controller.updateAnswer( + controller.textController.text); + } + }, + maskColor: + const Color(0xFF080F3D).withOpacity(0.8), + builder: (_) { + return HookBuilder(builder: (context) { + useMemoized(() async { + final guide = controller.guide.value; + for (int i = 0; i < guide.length + 1; i++) { + controller.textController.text = + guide.substring(0, i); + await Future.delayed( + const Duration(milliseconds: 80)); + } + }); + return QuestionDialog( + question: controller.question.value, + controller: controller.textController, + ); + }); + }, + ); + } + }, + ), + ), + const RSizedBox( + height: 64, + ), + ], + ), + ), + ); + }), + ), + ), + ); + } } class _ShowUp extends HookWidget { const _ShowUp({ + super.key, required this.child, }); @@ -392,16 +676,13 @@ class _ShowUp extends HookWidget { @override Widget build(BuildContext context) { final controller = - useAnimationController(duration: const Duration(milliseconds: 300)); + useAnimationController(duration: const Duration(milliseconds: 300)); useMemoized(() async { await Future.delayed(const Duration(milliseconds: 300)); controller.forward(); }); return FadeTransition( - opacity: CurvedAnimation( - curve: Curves.easeIn, - parent: controller - ), + opacity: CurvedAnimation(curve: Curves.easeIn, parent: controller), child: child, ); } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index c0e340c..8d7c3c9 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -18,7 +18,7 @@ part 'app_routes.dart'; class AppPages { AppPages._(); - static const INITIAL = Routes.HOME; + static const INITIAL = Routes.SELECT; static final routes = [ GetPage( diff --git a/lib/app/shared/constants/images.dart b/lib/app/shared/constants/images.dart index e83bd2d..5c6b4e5 100644 --- a/lib/app/shared/constants/images.dart +++ b/lib/app/shared/constants/images.dart @@ -369,6 +369,10 @@ class Images { AssetImage("assets/images/select/btn_bg_generate.png"); static const AssetImage selectBgCareer = AssetImage("assets/images/select/label_bg_career.png"); + static const AssetImage selectDialog = + AssetImage("assets/images/select/pic_dialogue_type.png"); + static const AssetImage selectAnswer = + AssetImage("assets/images/select/label_bg_answer.png"); static const AssetImage questionBg = AssetImage("assets/images/question/bg_pic_answer_page.png"); diff --git a/lib/app/shared/constants/styles.dart b/lib/app/shared/constants/styles.dart index ecb5265..d0aa66b 100644 --- a/lib/app/shared/constants/styles.dart +++ b/lib/app/shared/constants/styles.dart @@ -95,6 +95,16 @@ class Shadows { offset: Offset(1.w, 0.h), blurRadius: 4.r, ); + static Shadow txtShadow113 = Shadow( + color: const Color(0xFF513892), + offset: Offset(1.w, 1.h), + blurRadius: 3.r, + ); + static Shadow txtShadow013 = Shadow( + color: const Color(0xA30D0C0A), + offset: Offset(0.w, 1.h), + blurRadius: 3.r, + ); } class TextStyles { @@ -121,6 +131,13 @@ class TextStyles { shadows: [Shadows.txtShadow022], ); + static TextStyle mediumWhiteShadow28_013 = TextStyle( + fontSize: Dimens.font_sp28.sp, + color: Colors.white, + fontWeight: FontWeight.w500, + shadows: [Shadows.txtShadow013], + ); + static TextStyle mediumWhiteShadow26_034 = TextStyle( fontSize: Dimens.font_sp26.sp, color: Colors.white, @@ -218,6 +235,14 @@ class TextStyles { shadows: [Shadows.txtShadow100], ); + static TextStyle mediumWhiteShadow28_113 = TextStyle( + fontSize: Dimens.font_sp28.sp, + fontFamily: 'alph-b', + color: Colors.white, + fontWeight: FontWeight.w500, + shadows: [Shadows.txtShadow113], + ); + static TextStyle boldWhiteShadow28_110 = TextStyle( fontSize: Dimens.font_sp28.sp, fontFamily: 'alph-b',