From 0d5e4af611b743bc225064199a705053e26ff228 Mon Sep 17 00:00:00 2001 From: Adar Date: Thu, 18 Jan 2024 13:19:34 +0600 Subject: [PATCH] This feature will add agreement view for the survey (#140) * added missing MultipleChoiceQuestionResult toJson * Updated PlatformAppbar with default AppBar widget * Updated IconButton -> BackButton * Added other field in Multiple choice answers * added Multiple choice autocomplete * Customized the builder * added comment * DRY refactor * Updated other field label behavior * Added clear button and added tick mark check on dropdown * Updated example * Removed comment * json file updated * added default value for text question * Boolean Answer defaultValue * put back actual example_json.json * Fixed consistent focus in non textfield views There was a problem that keyboard was not hiding after navigating to next question when the view is not text/double answer view * added showCancelButton flag * added fluter_markdown and url_launcher packge * url_launcher * added Agreement feature/view * SingleCheckbox -> Agreement * Fixed tapping text to set the agreement to true * added result parsing for AgreementQuestionResult * update build_runner dependency * update flutter changes for android directory * update flutter changes for ios directory * update example pubspec.lock * add missing JsonSerializable annotation for ImageQuestionResult * re-run build_runner latest verson * remove duplicate imports * refactor: add flutter generated files * refactor: update example_json with proper ids * refactor: update styles for agreement answer view * refactor: update pubspec.lock * fix: remove duplicate ids for steps * refactor: add toggling radio button state for agreement --- .../plugins/GeneratedPluginRegistrant.java | 2 + android/local.properties | 4 +- example/assets/example_json.json | 28 ++-- example/ios/Podfile.lock | 8 +- example/lib/main.dart | 16 ++- .../flutter/generated_plugin_registrant.cc | 4 + example/linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + example/pubspec.lock | 90 +++++++++++- .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + ios/Runner/GeneratedPluginRegistrant.m | 7 + .../agreement_answer_format.dart | 24 ++++ .../agreement_answer_format.g.dart | 33 +++++ lib/src/answer_format/answer_format.dart | 3 + .../question/agreement_question_result.dart | 32 +++++ .../question/agreement_question_result.g.dart | 33 +++++ lib/src/result/step_result.dart | 5 + .../steps/predefined_steps/question_step.dart | 9 ++ lib/src/views/agreement_answer_view.dart | 133 ++++++++++++++++++ pubspec.lock | 80 +++++++++++ pubspec.yaml | 2 + 22 files changed, 504 insertions(+), 16 deletions(-) create mode 100644 lib/src/answer_format/agreement_answer_format.dart create mode 100644 lib/src/answer_format/agreement_answer_format.g.dart create mode 100644 lib/src/result/question/agreement_question_result.dart create mode 100644 lib/src/result/question/agreement_question_result.g.dart create mode 100644 lib/src/views/agreement_answer_view.dart diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index 8eca143a..5387d08d 100644 --- a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -5,6 +5,7 @@ import io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin; import io.flutter.plugins.imagepicker.ImagePickerPlugin; import dev.flutter.plugins.integration_test.IntegrationTestPlugin; +import io.flutter.plugins.urllauncher.UrlLauncherPlugin; import io.flutter.plugins.videoplayer.VideoPlayerPlugin; /** @@ -19,6 +20,7 @@ public static void registerWith(PluginRegistry registry) { FlutterAndroidLifecyclePlugin.registerWith(registry.registrarFor("io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin")); ImagePickerPlugin.registerWith(registry.registrarFor("io.flutter.plugins.imagepicker.ImagePickerPlugin")); IntegrationTestPlugin.registerWith(registry.registrarFor("dev.flutter.plugins.integration_test.IntegrationTestPlugin")); + UrlLauncherPlugin.registerWith(registry.registrarFor("io.flutter.plugins.urllauncher.UrlLauncherPlugin")); VideoPlayerPlugin.registerWith(registry.registrarFor("io.flutter.plugins.videoplayer.VideoPlayerPlugin")); } diff --git a/android/local.properties b/android/local.properties index 59210e88..b86818b8 100644 --- a/android/local.properties +++ b/android/local.properties @@ -1,2 +1,2 @@ -sdk.dir=/Users/marvin/Library/Android/sdk -flutter.sdk=/Users/marvin/flutter \ No newline at end of file +sdk.dir=/Users/saifulislam/Library/Android/sdk +flutter.sdk=/Users/saifulislam/Development/flutter_sdk/flutter \ No newline at end of file diff --git a/example/assets/example_json.json b/example/assets/example_json.json index ac4bbd28..8437a3b5 100644 --- a/example/assets/example_json.json +++ b/example/assets/example_json.json @@ -17,7 +17,7 @@ "id": "1" }, "destinationStepIdentifier": { - "id": "3" + "id": "14" } }, { @@ -61,6 +61,21 @@ "text": "Get ready for a bunch of super random questions!", "buttonText": "Let's go!" }, + { + "stepIdentifier": { + "id": "14" + }, + "type": "question", + "isOptional": true, + "title": "Before we begin", + "text": "In order to provide our services, this app will collect and use your sensitive health data based on your consent.", + "answerFormat": { + "type": "agreement", + "defaultValue": "NEGATIVE", + "markdownDescription": "Please read our [Privacy Notice](https://www.healthylongevity.cafe/privacy) carefully to ensure that you understand it fully.", + "markdownAgreementText": "By clicking this option you consent to the collection and processing of your personal data according to our [Privacy Notice](https://www.healthylongevity.cafe/privacy)" + } + }, { "stepIdentifier": { "id": "2" @@ -199,8 +214,7 @@ "answerFormat": { "type": "multiple", "otherField": true, - "textChoices": [ - { + "textChoices": [{ "text": "Penicillin", "value": "Penicillin" }, @@ -228,8 +242,7 @@ "answerFormat": { "type": "multiple_auto_complete", "otherField": true, - "textChoices": [ - { + "textChoices": [{ "text": "Penicillin", "value": "Penicillin" }, @@ -238,8 +251,7 @@ "value": "Latex" } ], - "suggestions": [ - { + "suggestions": [{ "text": "Pet", "value": "Pet" }, @@ -303,4 +315,4 @@ } } ] -} +} \ No newline at end of file diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index cd9ff83c..caa74aed 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -6,6 +6,8 @@ PODS: - Flutter - integration_test (0.0.1): - Flutter + - url_launcher_ios (0.0.1): + - Flutter - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS @@ -15,6 +17,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) EXTERNAL SOURCES: @@ -26,6 +29,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" @@ -34,8 +39,9 @@ SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 integration_test: 13825b8a9334a850581300559b8839134b124670 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b video_player_avfoundation: e9e6f9cae7d7a6d9b43519b0aab382bca60fcfd1 PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 -COCOAPODS: 1.14.3 +COCOAPODS: 1.13.0 diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e5a5e51..03c8bd3d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -130,6 +130,10 @@ class _MyAppState extends State { fontSize: 18.0, color: Colors.black, ), + bodySmall: TextStyle( + fontSize: 14.0, + color: Colors.black, + ), titleMedium: TextStyle( fontSize: 18.0, color: Colors.black, @@ -284,10 +288,14 @@ class _MyAppState extends State { } Future getJsonTask() async { - final String taskJson = - await rootBundle.loadString('assets/example_json.json'); - final Map taskMap = json.decode(taskJson); + try { + final String taskJson = + await rootBundle.loadString('assets/example_json.json'); + final Map taskMap = json.decode(taskJson); - return Task.fromJson(taskMap); + return Task.fromJson(taskMap); + } catch (e) { + rethrow; + } } } diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index 64a0ecea..7299b5cf 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index 2db3c22a..786ff5c2 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 1f5db172..81a08b9c 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,11 @@ import FlutterMacOS import Foundation import file_selector_macos +import url_launcher_macos import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/example/pubspec.lock b/example/pubspec.lock index d96701f3..5e496074 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.9" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" async: dependency: transitive description: @@ -211,6 +219,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: transitive + description: + name: flutter_markdown + sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" + url: "https://pub.dev" + source: hosted + version: "0.6.18+2" flutter_platform_widgets: dependency: transitive description: @@ -383,6 +399,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd + url: "https://pub.dev" + source: hosted + version: "7.1.1" matcher: dependency: transitive description: @@ -538,7 +562,7 @@ packages: path: ".." relative: true source: path - version: "0.1.2" + version: "0.2.0" sync_http: dependency: transitive description: @@ -571,6 +595,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + url: "https://pub.dev" + source: hosted + version: "6.2.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" uuid: dependency: transitive description: diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index 77ab7a09..043a96f0 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index a423a024..a95e2673 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/ios/Runner/GeneratedPluginRegistrant.m b/ios/Runner/GeneratedPluginRegistrant.m index ae9a248b..b95c4eef 100644 --- a/ios/Runner/GeneratedPluginRegistrant.m +++ b/ios/Runner/GeneratedPluginRegistrant.m @@ -24,6 +24,12 @@ @import integration_test; #endif +#if __has_include() +#import +#else +@import url_launcher_ios; +#endif + #if __has_include() #import #else @@ -36,6 +42,7 @@ + (void)registerWithRegistry:(NSObject*)registry { [CameraPlugin registerWithRegistrar:[registry registrarForPlugin:@"CameraPlugin"]]; [FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]]; [IntegrationTestPlugin registerWithRegistrar:[registry registrarForPlugin:@"IntegrationTestPlugin"]]; + [URLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"URLLauncherPlugin"]]; [FVPVideoPlayerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FVPVideoPlayerPlugin"]]; } diff --git a/lib/src/answer_format/agreement_answer_format.dart b/lib/src/answer_format/agreement_answer_format.dart new file mode 100644 index 00000000..21ebc6df --- /dev/null +++ b/lib/src/answer_format/agreement_answer_format.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:survey_kit/src/answer_format/answer_format.dart'; +import 'package:survey_kit/src/answer_format/boolean_answer_format.dart'; + +part 'agreement_answer_format.g.dart'; + +@JsonSerializable() +class AgreementAnswerFormat implements AnswerFormat { + final BooleanResult result; + final BooleanResult? defaultValue; + final String? markdownDescription; + final String? markdownAgreementText; + + const AgreementAnswerFormat({ + this.result = BooleanResult.NEGATIVE, + this.defaultValue, + this.markdownDescription, + this.markdownAgreementText, + }) : super(); + + factory AgreementAnswerFormat.fromJson(Map json) => + _$AgreementAnswerFormatFromJson(json); + Map toJson() => _$AgreementAnswerFormatToJson(this); +} diff --git a/lib/src/answer_format/agreement_answer_format.g.dart b/lib/src/answer_format/agreement_answer_format.g.dart new file mode 100644 index 00000000..0fd11104 --- /dev/null +++ b/lib/src/answer_format/agreement_answer_format.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'agreement_answer_format.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AgreementAnswerFormat _$AgreementAnswerFormatFromJson( + Map json) => + AgreementAnswerFormat( + result: $enumDecodeNullable(_$BooleanResultEnumMap, json['result']) ?? + BooleanResult.NEGATIVE, + defaultValue: + $enumDecodeNullable(_$BooleanResultEnumMap, json['defaultValue']), + markdownDescription: json['markdownDescription'] as String?, + markdownAgreementText: json['markdownAgreementText'] as String?, + ); + +Map _$AgreementAnswerFormatToJson( + AgreementAnswerFormat instance) => + { + 'result': _$BooleanResultEnumMap[instance.result], + 'defaultValue': _$BooleanResultEnumMap[instance.defaultValue], + 'markdownDescription': instance.markdownDescription, + 'markdownAgreementText': instance.markdownAgreementText, + }; + +const _$BooleanResultEnumMap = { + BooleanResult.NONE: 'NONE', + BooleanResult.POSITIVE: 'POSITIVE', + BooleanResult.NEGATIVE: 'NEGATIVE', +}; diff --git a/lib/src/answer_format/answer_format.dart b/lib/src/answer_format/answer_format.dart index 97042128..e612a90c 100644 --- a/lib/src/answer_format/answer_format.dart +++ b/lib/src/answer_format/answer_format.dart @@ -9,6 +9,7 @@ import 'package:survey_kit/src/answer_format/image_answer_format.dart'; import 'package:survey_kit/src/answer_format/integer_answer_format.dart'; import 'package:survey_kit/src/answer_format/multiple_choice_answer_format.dart'; import 'package:survey_kit/src/answer_format/scale_answer_format.dart'; +import 'package:survey_kit/src/answer_format/agreement_answer_format.dart'; import 'package:survey_kit/src/answer_format/single_choice_answer_format.dart'; import 'package:survey_kit/src/answer_format/text_answer_format.dart'; import 'package:survey_kit/src/answer_format/time_answer_formart.dart'; @@ -41,6 +42,8 @@ abstract class AnswerFormat { return ScaleAnswerFormat.fromJson(json); case 'time': return TimeAnswerFormat.fromJson(json); + case 'agreement': + return AgreementAnswerFormat.fromJson(json); case 'file': return ImageAnswerFormat.fromJson(json); default: diff --git a/lib/src/result/question/agreement_question_result.dart b/lib/src/result/question/agreement_question_result.dart new file mode 100644 index 00000000..937eb655 --- /dev/null +++ b/lib/src/result/question/agreement_question_result.dart @@ -0,0 +1,32 @@ +import 'package:survey_kit/src/answer_format/boolean_answer_format.dart'; +import 'package:survey_kit/src/steps/identifier/identifier.dart'; +import 'package:survey_kit/src/result/question_result.dart'; + +import 'package:json_annotation/json_annotation.dart'; + +part 'agreement_question_result.g.dart'; + +@JsonSerializable(explicitToJson: true) +class AgreementQuestionResult extends QuestionResult { + AgreementQuestionResult({ + required Identifier id, + required DateTime startDate, + required DateTime endDate, + required String valueIdentifier, + required BooleanResult? result, + }) : super( + id: id, + startDate: startDate, + endDate: endDate, + valueIdentifier: valueIdentifier, + result: result, + ); + + factory AgreementQuestionResult.fromJson(Map json) => + _$AgreementQuestionResultFromJson(json); + + Map toJson() => _$AgreementQuestionResultToJson(this); + + @override + List get props => [id, startDate, endDate, valueIdentifier, result]; +} diff --git a/lib/src/result/question/agreement_question_result.g.dart b/lib/src/result/question/agreement_question_result.g.dart new file mode 100644 index 00000000..192b8623 --- /dev/null +++ b/lib/src/result/question/agreement_question_result.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'agreement_question_result.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AgreementQuestionResult _$AgreementQuestionResultFromJson( + Map json) => + AgreementQuestionResult( + id: Identifier.fromJson(json['id'] as Map), + startDate: DateTime.parse(json['startDate'] as String), + endDate: DateTime.parse(json['endDate'] as String), + valueIdentifier: json['valueIdentifier'] as String, + result: $enumDecodeNullable(_$BooleanResultEnumMap, json['result']), + ); + +Map _$AgreementQuestionResultToJson( + AgreementQuestionResult instance) => + { + 'id': instance.id?.toJson(), + 'startDate': instance.startDate.toIso8601String(), + 'endDate': instance.endDate.toIso8601String(), + 'result': _$BooleanResultEnumMap[instance.result], + 'valueIdentifier': instance.valueIdentifier, + }; + +const _$BooleanResultEnumMap = { + BooleanResult.NONE: 'NONE', + BooleanResult.POSITIVE: 'POSITIVE', + BooleanResult.NEGATIVE: 'NEGATIVE', +}; diff --git a/lib/src/result/step_result.dart b/lib/src/result/step_result.dart index e9360be1..539e75ca 100644 --- a/lib/src/result/step_result.dart +++ b/lib/src/result/step_result.dart @@ -1,3 +1,4 @@ +import 'package:survey_kit/src/result/question/agreement_question_result.dart'; import 'package:survey_kit/src/result/question/boolean_question_result.dart'; import 'package:survey_kit/src/result/question/date_question_result.dart'; import 'package:survey_kit/src/result/question/double_question_result.dart'; @@ -101,6 +102,10 @@ class _Converter implements JsonConverter, Object> { final qrJson = qr.toJson(); qrJson['type'] = (TimeQuestionResult).toString(); allQuestionResultsEncoded.add(qrJson); + } else if (qr is AgreementQuestionResult) { + final qrJson = qr.toJson(); + qrJson['type'] = (AgreementQuestionResult).toString(); + allQuestionResultsEncoded.add(qrJson); } else if (qr is InstructionStepResult) { final qrJson = qr.toJson(); qrJson['type'] = (InstructionStepResult).toString(); diff --git a/lib/src/steps/predefined_steps/question_step.dart b/lib/src/steps/predefined_steps/question_step.dart index ed8d3830..06d96494 100644 --- a/lib/src/steps/predefined_steps/question_step.dart +++ b/lib/src/steps/predefined_steps/question_step.dart @@ -10,6 +10,7 @@ import 'package:survey_kit/src/answer_format/multiple_choice_answer_format.dart' import 'package:survey_kit/src/answer_format/multiple_choice_auto_complete_answer_format.dart'; import 'package:survey_kit/src/answer_format/multiple_double_answer_format.dart'; import 'package:survey_kit/src/answer_format/scale_answer_format.dart'; +import 'package:survey_kit/src/answer_format/agreement_answer_format.dart'; import 'package:survey_kit/src/answer_format/single_choice_answer_format.dart'; import 'package:survey_kit/src/answer_format/text_answer_format.dart'; import 'package:survey_kit/src/answer_format/time_answer_formart.dart'; @@ -21,6 +22,7 @@ import 'package:survey_kit/src/result/question/integer_question_result.dart'; import 'package:survey_kit/src/result/question/multiple_choice_question_result.dart'; import 'package:survey_kit/src/result/question/multiple_double_question_result.dart'; import 'package:survey_kit/src/result/question/scale_question_result.dart'; +import 'package:survey_kit/src/result/question/agreement_question_result.dart'; import 'package:survey_kit/src/result/question/single_choice_question_result.dart'; import 'package:survey_kit/src/result/question/text_question_result.dart'; import 'package:survey_kit/src/result/question/time_question_result.dart'; @@ -37,6 +39,7 @@ import 'package:survey_kit/src/views/integer_answer_view.dart'; import 'package:survey_kit/src/views/multiple_choice_answer_view.dart'; import 'package:survey_kit/src/views/multiple_double_answer_view.dart'; import 'package:survey_kit/src/views/scale_answer_view.dart'; +import 'package:survey_kit/src/views/agreement_answer_view.dart'; import 'package:survey_kit/src/views/single_choice_answer_view.dart'; import 'package:survey_kit/src/views/text_answer_view.dart'; import 'package:survey_kit/src/views/time_answer_view.dart'; @@ -141,6 +144,12 @@ class QuestionStep extends Step { questionStep: this, result: questionResult as MultipleChoiceQuestionResult?, ); + case AgreementAnswerFormat: + return AgreementAnswerView( + key: key, + questionStep: this, + result: questionResult as AgreementQuestionResult?, + ); case ImageAnswerFormat: return ImageAnswerView( key: key, diff --git a/lib/src/views/agreement_answer_view.dart b/lib/src/views/agreement_answer_view.dart new file mode 100644 index 00000000..4fb8a942 --- /dev/null +++ b/lib/src/views/agreement_answer_view.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:survey_kit/src/answer_format/boolean_answer_format.dart'; +import 'package:survey_kit/src/answer_format/agreement_answer_format.dart'; +import 'package:survey_kit/src/result/question/agreement_question_result.dart'; +import 'package:survey_kit/src/steps/predefined_steps/question_step.dart'; +import 'package:survey_kit/src/views/widget/step_view.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AgreementAnswerView extends StatefulWidget { + final QuestionStep questionStep; + final AgreementQuestionResult? result; + + const AgreementAnswerView({ + Key? key, + required this.questionStep, + required this.result, + }) : super(key: key); + + @override + _AgreementAnswerViewState createState() => _AgreementAnswerViewState(); +} + +class _AgreementAnswerViewState extends State { + late final DateTime _startDate; + late final AgreementAnswerFormat _agreementAnswerFormat; + BooleanResult? _result; + + @override + void initState() { + super.initState(); + _agreementAnswerFormat = + widget.questionStep.answerFormat as AgreementAnswerFormat; + _result = widget.result?.result ?? + _agreementAnswerFormat.defaultValue ?? + BooleanResult.NEGATIVE; + _startDate = DateTime.now(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final markDownStyleSheet = MarkdownStyleSheet.fromTheme(theme); + + return StepView( + step: widget.questionStep, + resultFunction: () => AgreementQuestionResult( + id: widget.questionStep.stepIdentifier, + startDate: _startDate, + endDate: DateTime.now(), + valueIdentifier: _result != null ? _result.toString() : '', + result: _result, + ), + isValid: widget.questionStep.isOptional || + (_result != null && _result == BooleanResult.POSITIVE), + title: widget.questionStep.title.isNotEmpty + ? Text( + widget.questionStep.title, + style: Theme.of(context).textTheme.displayMedium, + textAlign: TextAlign.center, + ) + : widget.questionStep.content, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 32.0), + child: Text( + widget.questionStep.text, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ), + Column( + children: [ + if (_agreementAnswerFormat.markdownDescription != null) + Padding( + padding: const EdgeInsets.only(bottom: 32.0), + child: MarkdownBody( + data: _agreementAnswerFormat.markdownDescription!, + styleSheet: markDownStyleSheet.copyWith( + textAlign: WrapAlignment.center, + ), + onTapLink: (text, href, title) => + href != null ? launchUrl(Uri.parse(href)) : null, + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Radio( + groupValue: _result, + value: BooleanResult.POSITIVE, + onChanged: (v) { + setState(() { + _result = v; + }); + }), + SizedBox( + width: 16, + ), + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + if (_result == BooleanResult.POSITIVE) { + _result = BooleanResult.NEGATIVE; + } else { + _result = BooleanResult.POSITIVE; + } + }); + }, + child: MarkdownBody( + styleSheet: markDownStyleSheet.copyWith( + p: theme.textTheme.bodySmall, + ), + data: + _agreementAnswerFormat.markdownAgreementText ?? '', + onTapLink: (text, href, title) => + href != null ? launchUrl(Uri.parse(href)) : null, + ), + )), + ], + ) + ], + ), + ], + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 15f1c27c..64e369a5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -331,6 +331,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "35108526a233cc0755664d445f8a6b4b61e6f8fe993b3658b80b4a26827fc196" + url: "https://pub.dev" + source: hosted + version: "0.6.18+2" flutter_platform_widgets: dependency: "direct main" description: @@ -551,6 +559,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: acf35edccc0463a9d7384e437c015a3535772e09714cf60e07eeef3a15870dcd + url: "https://pub.dev" + source: hosted + version: "7.1.1" matcher: dependency: transitive description: @@ -804,6 +820,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + url: "https://pub.dev" + source: hosted + version: "6.2.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" uuid: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 7db09fe3..0a944d34 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: flutter: sdk: flutter flutter_bloc: ^8.0.1 + flutter_markdown: ^0.6.18+2 flutter_platform_widgets: ^6.0.2 go_router: ^12.1.1 image_picker: ^1.0.4 @@ -25,6 +26,7 @@ dependencies: json_annotation: ^4.0.1 lottie: ^2.2.0 provider: ^6.0.2 + url_launcher: ^6.1.5 uuid: ^4.2.1 video_player: ^2.1.6