Skip to content

Commit

Permalink
feat: add maximum recording duration to designer
Browse files Browse the repository at this point in the history
  • Loading branch information
hig-dev committed Feb 7, 2024
1 parent 8d1332d commit c601e0b
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ part 'audio_recording_question.g.dart';
class AudioRecordingQuestion extends Question<AudioRecordingQuestion> {
static const String questionType = 'AudioRecordingQuestion';

AudioRecordingQuestion() : super(questionType);
@JsonKey(name: 'maxRecordingDurationSeconds')
final int maxRecordingDurationSeconds;

AudioRecordingQuestion.withId() : super.withId(questionType);
AudioRecordingQuestion({required this.maxRecordingDurationSeconds}) : super(questionType);

AudioRecordingQuestion.withId(this.maxRecordingDurationSeconds) : super.withId(questionType);

factory AudioRecordingQuestion.fromJson(Map<String, dynamic> json) => _$AudioRecordingQuestionFromJson(json);
@override
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,14 @@ class QuestionFormViewModel extends ManagedFormViewModel<QuestionFormData>
late final FormArray<String> imageResponseOptionsArray = FormArray(imageOptions);

// Audio
static const int kDefaultMaxRecordingDurationSeconds = 60;
static const int kMaxRecordingDurationSeconds = 3600;
List<AbstractControl<String>> get audioOptions =>
AudioQuestionFormData.kResponseOptions.keys.map((e) => FormControl(value: e, disabled: true)).toList();
late final FormArray<String> audioResponseOptionsArray = FormArray(audioOptions);
final FormControl<int> maxRecordingDurationSecondsControl = FormControl(
value: kDefaultMaxRecordingDurationSeconds,
validators: [Validators.number, Validators.min(1), Validators.max(kMaxRecordingDurationSeconds)]);

// Scale
static const int kDefaultScaleMinValue = 0;
Expand Down Expand Up @@ -285,6 +290,7 @@ class QuestionFormViewModel extends ManagedFormViewModel<QuestionFormData>
}),
SurveyQuestionType.audio: FormGroup({
'audioOptionsArray': audioResponseOptionsArray,
'maxRecordingDurationSeconds': maxRecordingDurationSecondsControl,
}),
SurveyQuestionType.freeText: FormGroup({
'freeTextOptionsArray': freeTextResponseOptionsArray,
Expand All @@ -305,6 +311,10 @@ class QuestionFormViewModel extends ManagedFormViewModel<QuestionFormData>
StudyFormValidationSet.draft: [scaleRangeValid],
StudyFormValidationSet.publish: [scaleRangeValid],
},
SurveyQuestionType.audio: {
StudyFormValidationSet.draft: [maxRecordingDurationValid],
StudyFormValidationSet.publish: [maxRecordingDurationValid],
},
};

@override
Expand Down Expand Up @@ -350,6 +360,19 @@ class QuestionFormViewModel extends ManagedFormViewModel<QuestionFormData>
});
}

get maxRecordingDurationValid {
return FormControlValidation(control: maxRecordingDurationSecondsControl, validators: [
Validators.number,
Validators.min(1),
Validators.max(kMaxRecordingDurationSeconds),
], validationMessages: {
ValidationMessage.min: (error) => tr.audio_recording_max_duration_rangevalid_min,
ValidationMessage.max: (error) =>
tr.audio_recording_max_duration_rangevalid_max(QuestionFormViewModel.kMaxRecordingDurationSeconds),
ValidationMessage.number: (error) => tr.free_text_validation_number,
});
}

/// The form containing the controls for the currently selected
/// [SurveyQuestionType]
///
Expand Down Expand Up @@ -403,6 +426,8 @@ class QuestionFormViewModel extends ManagedFormViewModel<QuestionFormData>
case SurveyQuestionType.image:
break;
case SurveyQuestionType.audio:
data = data as AudioQuestionFormData;
maxRecordingDurationSecondsControl.value = data.maxRecordingDurationSeconds;
break;
case SurveyQuestionType.choice:
data = data as ChoiceQuestionFormData;
Expand Down Expand Up @@ -461,6 +486,7 @@ class QuestionFormViewModel extends ManagedFormViewModel<QuestionFormData>
questionText: questionTextControl.value!, // required
questionType: questionTypeControl.value!, // required
questionInfoText: questionInfoTextControl.value,
maxRecordingDurationSeconds: maxRecordingDurationSecondsControl.value!,
);
case SurveyQuestionType.choice:
return ChoiceQuestionFormData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,11 @@ class AudioQuestionFormData extends QuestionFormData {
required super.questionText,
required super.questionType,
super.questionInfoText,
required this.maxRecordingDurationSeconds,
});

final int maxRecordingDurationSeconds;

static Map<String, String> get kResponseOptions => {tr.form_field_response_audio: 'audio'};

@override
Expand All @@ -339,14 +342,15 @@ class AudioQuestionFormData extends QuestionFormData {
questionType: SurveyQuestionType.audio,
questionText: question.prompt ?? '',
questionInfoText: question.rationale ?? '',
maxRecordingDurationSeconds: question.maxRecordingDurationSeconds,
);
data.setResponseOptionsValidityFrom(eligibilityCriteria);
return data;
}

@override
Question toQuestion() {
final question = AudioRecordingQuestion();
final question = AudioRecordingQuestion(maxRecordingDurationSeconds: maxRecordingDurationSeconds);
question.id = questionId;
question.prompt = questionText;
question.rationale = questionInfoText;
Expand All @@ -360,6 +364,7 @@ class AudioQuestionFormData extends QuestionFormData {
questionType: questionType,
questionText: questionText.withDuplicateLabel(),
questionInfoText: questionInfoText,
maxRecordingDurationSeconds: maxRecordingDurationSeconds,
);
data.responseOptionsValidity = responseOptionsValidity;
return data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:reactive_forms/reactive_forms.dart';
import 'package:studyu_designer_v2/common_views/form_table_layout.dart';
import 'package:studyu_designer_v2/features/design/shared/questionnaire/question/question_form_controller.dart';
import 'package:studyu_designer_v2/localization/app_translation.dart';

class AudioRecordingQuestionFormView extends ConsumerWidget {
const AudioRecordingQuestionFormView({required this.formViewModel, super.key});
Expand All @@ -9,11 +12,26 @@ class AudioRecordingQuestionFormView extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
return const Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
FormTableLayout(
rowLayout: FormTableRowLayout.vertical,
rows: [
FormTableRow(
label: tr.form_field_response_audio_max_duration_label,
input: ReactiveTextField(
formControl: formViewModel.maxRecordingDurationSecondsControl,
keyboardType: TextInputType.number,
validationMessages: {
ValidationMessage.min: (error) => tr.audio_recording_max_duration_rangevalid_min,
ValidationMessage.max: (error) => tr
.audio_recording_max_duration_rangevalid_max(QuestionFormViewModel.kMaxRecordingDurationSeconds),
ValidationMessage.number: (error) => tr.free_text_validation_number,
},
),
),
],
),
],
);
Expand Down
8 changes: 8 additions & 0 deletions designer_v2/lib/localization/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"form_array_response_options_bool_yes": "Ja",
"form_array_response_options_bool_no": "No",
"form_field_response_image": "Bild",
"form_field_response_audio_max_duration_label": "Maximale Aufnahmedauer in Sekunden",
"form_field_response_choice_multiple": "Mehrfachauswahl",
"form_field_response_choice_multiple_tooltip": "Erlaubt die Auswahl von mehreren Antwortoptionen gleichzeitig,\nansonsten kann nur eine einzige Option ausgewählt werden",
"form_array_response_options_choice_new": "Antwortoption hinzufügen",
Expand Down Expand Up @@ -184,6 +185,13 @@
"count": {}
}
},
"audio_recording_max_duration_rangevalid_min": "Die minimale Aufnahmedauer beträgt 1 Sekunde",
"audio_recording_max_duration_rangevalid_max": "Die maximale Aufnahmedauer beträgt {count} Sekunden",
"@audio_recording_max_duration_rangevalid_max": {
"placeholders": {
"count": {}
}
},
"free_text_question_logic_not_supported": "Logik ist für Freitext-Fragen noch nicht verfügbar",
"free_text_question_type_any": "Beliebig",
"free_text_question_type_alphanumeric": "Alphanumerisch",
Expand Down
8 changes: 8 additions & 0 deletions designer_v2/lib/localization/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"form_field_response_choice_multiple": "Select multiple",
"form_field_response_image": "Provide directions for capturing image",
"form_field_response_audio": "Provide directions for recording audio",
"form_field_response_audio_max_duration_label": "Maximum recording duration in seconds",
"form_field_response_choice_multiple_tooltip": "Allow the participant to select multiple response options. Otherwise only a single option can be selected.",
"form_array_response_options_choice_new": "Add option",
"form_array_response_options_choice_hint": "Option",
Expand Down Expand Up @@ -186,6 +187,13 @@
"count": {}
}
},
"audio_recording_max_duration_rangevalid_min": "The minimum recording duration is 1 second",
"audio_recording_max_duration_rangevalid_max": "The maximum recording duration is {count} seconds",
"@audio_recording_max_duration_rangevalid_max": {
"placeholders": {
"count": {}
}
},
"free_text_question_logic_not_supported": "The screener question logic is not yet supported for free text questions.",
"free_text_question_type_any": "Any text",
"free_text_question_type_alphanumeric": "Alphanumeric",
Expand Down

0 comments on commit c601e0b

Please sign in to comment.