From 3450b39c0faa5ff43cfbe3c42cdc78c0e10ae5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=A0upej?= <117984211+adamsupej@users.noreply.github.com> Date: Tue, 3 Jan 2023 18:20:40 +0100 Subject: [PATCH] Feat/fe formular dotaz do odborne poradny#467 (#476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip: Formular_pridanieFormScreen#467 * wip: Formular-note_text_field_with_variable_length#467 * wip: Formular-updated-UI#467 * Formular-added_new_text_fields#467 * wip: Added_finalized_UI_with_email_fetching#467 * wip: updated_to_use_with_form#467 * wip: Formular-widget_extraction_and_decomposition#467 * wip: Formular-support_for_choosing_state_in_advance#467 * Formular-Added_error_and_snackbar_text#467 * Formular-Added_errorText_support#467 * wip: Formular-Validation_and_snackbar#467 * wip: Formular-removed-errorTextParam#467 * Formular-TextStyle_extraction#467 * Formular-chip_text_extraction_to_localization#467 * Formular-Added_gesture_detector#467 * Formular-code_refactoring#467 * Formular-class_variables_made_private#467 * Formular-final_checks#467 * Formular-renamed_enum_and_removed_underscores#467 * Formular-removed_force_unwrap#467 * Formular-fix_format#467 Co-authored-by: Adam Šupej --- lib/constants.dart | 23 +++ lib/l10n/intl_cs.arb | 42 ++++ lib/ui/screens/form/form_screen.dart | 40 ++++ lib/ui/widgets/form/form_content.dart | 182 ++++++++++++++++++ .../form/form_question_types_wrapper.dart | 84 ++++++++ lib/ui/widgets/note_text_field.dart | 19 +- 6 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 lib/ui/screens/form/form_screen.dart create mode 100644 lib/ui/widgets/form/form_content.dart create mode 100644 lib/ui/widgets/form/form_question_types_wrapper.dart diff --git a/lib/constants.dart b/lib/constants.dart index 1afeba1c..fe0158e1 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -240,6 +240,29 @@ class LoonoFonts { fontWeight: FontWeight.bold, ); + static const snackbarStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + ); + + static const errorMessageStyle = TextStyle( + color: LoonoColors.red, + fontSize: 12, + fontWeight: FontWeight.w400, + ); + + static const chipStyleActive = TextStyle( + color: LoonoColors.primaryEnabled, + fontSize: 14, + fontWeight: FontWeight.w600, + ); + + static const chipStyleDefault = TextStyle( + color: LoonoColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ); + static const cardDescription = TextStyle( fontSize: 14, color: LoonoColors.grey, diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index a5c055b8..05746916 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1548,6 +1548,48 @@ "@prevention_age_limit": {}, "not_ordered": "Neobjednáno", "@not_ordered": {}, + "form_specialist_question": "Dotaz do poradny", + "@form_specialist_question": {}, + "form_question_answer": "Na tvůj dotaz ohledně zdraví a prevence odpoví naši odborníci z řad lékařů. Odpověď ti zašleme do týdne na e-mail {email}.", + "@form_question_answer": { + "placeholders": { + "email": { + "type": "String" + } + } + }, + "form_question_field": "Jaké oblasti se dotaz týká?", + "@form_question_field": {}, + "form_wrapper_error": "Vyber kategorii dotazu.", + "@form_wrapper_error": {}, + "form_self_exam": "Samovyšetření prsou/varlat", + "@form_self_exam": {}, + "form_mentalHealth": "Duševní zdraví", + "@form_mentalHealth": {}, + "form_preventionAndHealthStyle": "Prevence a životní styl", + "@form_preventionAndHealthStyle": {}, + "form_heartAndVessel": "Zdravé srdce a cévy", + "@form_heartAndVessel": {}, + "form_reproductionalHealth": "Reprodukční zdraví", + "@form_reproductionalHealth": {}, + "form_sexualHealth": "Sexuální zdraví", + "@form_sexualHealth": {}, + "form_preventiveExamAndScreening": "Preventivní prohlídky a screeningy", + "@form_preventiveExamAndScreening": {}, + "form_other": "Jiné", + "@form_other": {}, + "form_question_label": "Tvůj dotaz", + "@form_question_label": {}, + "form_question_hint": "Prostor pro tvůj dotaz ...", + "@form_question_hint": {}, + "form_input_error": "Doplň prosím tvůj dotaz.", + "@form_input_error": {}, + "form_send_question": "Odeslat dotaz", + "@form_send_question": {}, + "form_snack_error": "Vyplň prosím všechny povinná pole.", + "@form_snack_error": {}, + "form_snack_success": "Tvůj dotaz byl odeslán.", + "@form_snack_success": {}, "consultancy_card_title": "Máš dotaz ohledně prevence?", "@consultancy_card_title": {}, "consultancy_card_description": "Zeptej se odborníků v naší poradně.", diff --git a/lib/ui/screens/form/form_screen.dart b/lib/ui/screens/form/form_screen.dart new file mode 100644 index 00000000..49e8239f --- /dev/null +++ b/lib/ui/screens/form/form_screen.dart @@ -0,0 +1,40 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import 'package:loono/ui/widgets/form/form_content.dart'; +import 'package:loono/utils/hidekeyboard_util.dart'; + +class FormScreen extends StatelessWidget { + const FormScreen({ + super.key, + this.initializedType = FormQuestionType.uninitialized, + }); + + final FormQuestionType initializedType; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color.fromRGBO(255, 255, 255, 1), + appBar: AppBar( + elevation: 0.0, + backgroundColor: Colors.white24, + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors.black, + ), + onPressed: () { + AutoRouter.of(context).pop(); + }, + ), + ), + body: SafeArea( + child: GestureDetector( + onTap: () => hideKeyboard(context), + child: FormContent(initializedType), + ), + ), + ); + } +} diff --git a/lib/ui/widgets/form/form_content.dart b/lib/ui/widgets/form/form_content.dart new file mode 100644 index 00000000..845e2c54 --- /dev/null +++ b/lib/ui/widgets/form/form_content.dart @@ -0,0 +1,182 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; + +import 'package:loono/constants.dart'; +import 'package:loono/l10n/ext.dart'; +import 'package:loono/services/database_service.dart'; +import 'package:loono/ui/widgets/button.dart'; +import 'package:loono/ui/widgets/form/form_question_types_wrapper.dart'; +import 'package:loono/ui/widgets/note_text_field.dart'; +import 'package:loono/ui/widgets/space.dart'; +import 'package:loono/utils/registry.dart'; + +enum FormQuestionType { + uninitialized, + selfExam, + mentalHealth, + preventionAndHealthStyle, + heartAndVessel, + reproductionalHealth, + sexualHealth, + preventiveExamAndScreening, + other, +} + +class FormContent extends StatefulWidget { + const FormContent( + this.questionType, { + super.key, + }); + + final FormQuestionType questionType; + + @override + State createState() => _FormContentState(); +} + +class _FormContentState extends State { + final _currentUser = registry.get().users.user; + final _textFieldController = TextEditingController(); + late FormQuestionType _questionType; + bool _wrapperError = false; + bool _textInputError = false; + + @override + void initState() { + super.initState(); + _questionType = widget.questionType; + } + + void _updateQuestionType(int index) { + _questionType = FormQuestionType.values[index + 1]; + } + + void _showErrorSnackBar() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n.form_snack_error, + style: LoonoFonts.snackbarStyle, + ), + backgroundColor: LoonoColors.errorColor, + ), + ); + } + + void _validateFormFields() { + setState(() { + if (_questionType == FormQuestionType.uninitialized) { + _wrapperError = true; + _showErrorSnackBar(); + } else { + _wrapperError = false; + } + if (_textFieldController.text.isEmpty) { + _textInputError = true; + _showErrorSnackBar(); + } else { + _textInputError = false; + } + }); + } + + void _sendForm() { + _validateFormFields(); + if (_questionType != FormQuestionType.uninitialized && _textFieldController.text.isNotEmpty) { + // TODO: SEND FORM + // final name = _currentUser?.nickname; + // final sex = _currentUser?.sex; + // final age = _currentUser?.dateOfBirth; + // final message = _textFieldController.text; + // print(name); + // print(sex); + // print(age); + // print(message); + // print(_questionType); + AutoRouter.of(context).popUntilRoot(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n.form_snack_success, + style: LoonoFonts.snackbarStyle, + ), + backgroundColor: LoonoColors.greenSuccess, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 18, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.form_specialist_question, + style: LoonoFonts.headerFontStyle, + ), + const CustomSpacer.vertical(30), + Text( + context.l10n.form_question_answer('"${_currentUser?.email}"'), + style: LoonoFonts.paragraphFontStyle, + ), + const Divider( + height: 60, + ), + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Text( + context.l10n.form_question_field, + style: LoonoFonts.subtitleFontStyle, + ), + ), + FormQuestionTypesWrapper( + _questionType.index == 0 ? null : _questionType.index - 1, + _updateQuestionType, + ), + if (_wrapperError) + Padding( + padding: const EdgeInsets.only(left: 15), + child: Text( + context.l10n.form_wrapper_error, + style: LoonoFonts.errorMessageStyle, + ), + ), + const CustomSpacer.vertical(30), + noteTextField( + context, + noteController: _textFieldController, + onNoteChange: null, + maxLength: 700, + isForm: true, + error: _textInputError, + ), + ], + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 30, + bottom: 70, + left: 18, + right: 18, + ), + child: LoonoButton( + onTap: _sendForm, + text: context.l10n.form_send_question, + ), + ) + ], + ); + } +} diff --git a/lib/ui/widgets/form/form_question_types_wrapper.dart b/lib/ui/widgets/form/form_question_types_wrapper.dart new file mode 100644 index 00000000..fd9359c3 --- /dev/null +++ b/lib/ui/widgets/form/form_question_types_wrapper.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; + +import 'package:loono/constants.dart'; +import 'package:loono/l10n/ext.dart'; + +class FormQuestionTypesWrapper extends StatefulWidget { + const FormQuestionTypesWrapper( + this.defaultChipIndex, + this.updateQuestionType, { + super.key, + }); + + final Function updateQuestionType; + final int? defaultChipIndex; + + @override + State createState() => _FormQuestionTypesWrapperState(); +} + +class _FormQuestionTypesWrapperState extends State { + late List _choices; + late Function _updateQuestionType; + int? _activeChipIndex; + bool _initialized = false; + + @override + void didChangeDependencies() { + if (!_initialized) { + _initialized = true; + _choices = [ + context.l10n.form_self_exam, + context.l10n.form_mentalHealth, + context.l10n.form_preventionAndHealthStyle, + context.l10n.form_heartAndVessel, + context.l10n.form_reproductionalHealth, + context.l10n.form_sexualHealth, + context.l10n.form_preventiveExamAndScreening, + context.l10n.form_other, + ]; + _updateQuestionType = widget.updateQuestionType; + _activeChipIndex = widget.defaultChipIndex; + } + super.didChangeDependencies(); + } + + void _updateState(bool value, int index) { + if (value) { + setState(() { + _updateQuestionType(index); + _activeChipIndex = index; + }); + } + } + + @override + Widget build(BuildContext context) { + return Wrap( + children: List.generate(_choices.length, (index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: ChoiceChip( + avatar: index == _activeChipIndex + ? const Icon( + Icons.check, + color: LoonoColors.primaryEnabled, + ) + : null, + label: Text( + _choices[index], + style: index == _activeChipIndex + ? LoonoFonts.chipStyleActive + : LoonoFonts.chipStyleDefault, + ), + selected: index == _activeChipIndex, + selectedColor: LoonoColors.beigeLight, + onSelected: (value) { + _updateState(value, index); + }, + ), + ); + }), + ); + } +} diff --git a/lib/ui/widgets/note_text_field.dart b/lib/ui/widgets/note_text_field.dart index f5e3d452..eeba27d3 100644 --- a/lib/ui/widgets/note_text_field.dart +++ b/lib/ui/widgets/note_text_field.dart @@ -8,12 +8,15 @@ Widget noteTextField( bool? enable = true, required void Function(String)? onNoteChange, FocusNode? focusNode, + int maxLength = 256, + bool isForm = false, + bool error = false, }) { return TextFormField( controller: noteController, minLines: 5, maxLines: 10, - maxLength: 256, + maxLength: maxLength, keyboardType: TextInputType.multiline, enabled: enable, focusNode: focusNode, @@ -21,9 +24,19 @@ Widget noteTextField( decoration: InputDecoration( filled: true, fillColor: Colors.white, - hintText: context.l10n.note_visiting_description, - label: noteController.text.isEmpty ? null : Text(context.l10n.note_visiting), + hintText: isForm ? context.l10n.form_question_hint : context.l10n.note_visiting_description, + label: noteController.text.isEmpty + ? null + : (isForm + ? Text( + context.l10n.form_question_label, + style: const TextStyle( + color: LoonoColors.primaryEnabled, + ), + ) + : Text(context.l10n.note_visiting)), hintStyle: const TextStyle(color: Colors.grey, fontSize: 14.0), + errorText: error ? context.l10n.form_input_error : null, focusedBorder: const OutlineInputBorder( borderSide: BorderSide(color: LoonoColors.primaryEnabled), ),