Skip to content

Commit

Permalink
feat(cat-voices): Yes/No choice component (#1508)
Browse files Browse the repository at this point in the history
* feat: create ui layout

* feat: adding logic to widget

* feat: creating segmented button formfield

* feat: changing type of yes no choice form field

* chore: ading main axis size to min
  • Loading branch information
LynxLynxx authored Jan 14, 2025
1 parent eb15954 commit 4fe321d
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import 'package:catalyst_voices/common/ext/document_property_ext.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';

class YesNoChoiceWidget extends StatefulWidget {
final DocumentProperty<bool> property;
final ValueChanged<DocumentChange> onChanged;
final bool isEditMode;
final bool isRequired;

const YesNoChoiceWidget({
super.key,
required this.property,
required this.onChanged,
required this.isEditMode,
required this.isRequired,
});

@override
State<YesNoChoiceWidget> createState() => _YesNoChoiceWidgetState();
}

class _YesNoChoiceWidgetState extends State<YesNoChoiceWidget> {
late bool? selectedValue;

String get _description => widget.property.formattedDescription;

@override
void initState() {
super.initState();

_handleInitialValue();
}

@override
void didUpdateWidget(covariant YesNoChoiceWidget oldWidget) {
super.didUpdateWidget(oldWidget);

if (oldWidget.property.value != widget.property.value) {
_handleInitialValue();
}

if (oldWidget.isEditMode != widget.isEditMode &&
widget.isEditMode == false) {
_handleInitialValue();
}
}

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (_description.isNotEmpty) ...[
Text(
_description,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
],
_YesNoChoiceSegmentButton(
context,
value: selectedValue,
enabled: widget.isEditMode,
onChanged: _handleValueChanged,
validator: (value) {
// TODO(dtscalac): add validation
final result = widget.property.schema.validatePropertyValue(value);

return LocalizedDocumentValidationResult.from(result)
.message(context);
},
),
],
);
}

void _handleInitialValue() {
selectedValue = widget.property.value;
}

void _handleValueChanged(bool? value) {
setState(() {
selectedValue = value;
});
if (value == null && widget.property.value != value) {
_notifyChangeListener(value);
}
}

void _notifyChangeListener(bool? value) {
widget.onChanged(
DocumentChange(
nodeId: widget.property.schema.nodeId,
value: value,
),
);
}
}

class _YesNoChoiceSegmentButton extends FormField<bool?> {
final bool? value;
final ValueChanged<bool?>? onChanged;

_YesNoChoiceSegmentButton(
BuildContext context, {
super.key,
required this.value,
required this.onChanged,
super.validator,
super.enabled,
AutovalidateMode autovalidateMode = AutovalidateMode.onUserInteraction,
}) : super(
initialValue: value,
autovalidateMode: autovalidateMode,
builder: (field) {
void onChangedHandler(Set<bool> selected) {
final newValue = selected.isEmpty ? null : selected.first;
field.didChange(newValue);
onChanged?.call(newValue);
}

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
IgnorePointer(
ignoring: !enabled,
child: VoicesSegmentedButton<bool>(
key: key,
segments: [
ButtonSegment(
value: true,
label: Text(context.l10n.yes),
),
ButtonSegment(
value: false,
label: Text(context.l10n.no),
),
],
selected: value != null ? {value} : {},
onChanged: onChangedHandler,
emptySelectionAllowed: true,
style: _getButtonStyle(field),
),
),
if (field.hasError)
Text(
field.errorText ?? context.l10n.snackbarErrorLabelText,
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: Theme.of(context).colorScheme.error),
),
],
);
},
);

static ButtonStyle? _getButtonStyle(FormFieldState<bool?> field) {
if (field.errorText == null) return null;

return ButtonStyle(
side: WidgetStatePropertyAll(
BorderSide(
color: Theme.of(field.context).colorScheme.error,
),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:catalyst_voices/widgets/document_builder/document_token_value_wi
import 'package:catalyst_voices/widgets/document_builder/single_dropdown_selection_widget.dart';
import 'package:catalyst_voices/widgets/document_builder/single_grouped_tag_selector_widget.dart';
import 'package:catalyst_voices/widgets/document_builder/single_line_https_url_widget.dart.dart';
import 'package:catalyst_voices/widgets/document_builder/yes_no_choice_widget.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
Expand Down Expand Up @@ -113,7 +114,11 @@ class _DocumentBuilderSectionTileState
void _toggleEditMode() {
setState(() {
_isEditMode = !_isEditMode;
_pendingChanges.clear();
if (!_isEditMode) {
_pendingChanges.clear();
_editedSection = widget.section;
_builder = _editedSection.toBuilder();
}
});
}

Expand Down Expand Up @@ -218,7 +223,6 @@ class _PropertyBuilder extends StatelessWidget {
case TagGroupDefinition():
case TagSelectionDefinition():
case DurationInMonthsDefinition():
case YesNoChoiceDefinition():
case SPDXLicenceOrUrlDefinition():
case LanguageCodeDefinition():
throw UnimplementedError('${definition.type} not implemented');
Expand Down Expand Up @@ -269,6 +273,14 @@ class _PropertyBuilder extends StatelessWidget {
isEditMode: isEditMode,
onChanged: onChanged,
);
case YesNoChoiceDefinition():
final castProperty = definition.castProperty(property);
return YesNoChoiceWidget(
property: castProperty,
onChanged: onChanged,
isEditMode: isEditMode,
isRequired: castProperty.schema.isRequired,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class YesNoChoiceDefinition extends BaseDocumentDefinition<bool> {
DocumentSchemaProperty<bool> schema,
bool? value,
) {
// TODO(dtscalac): validate yes no choice
return DocumentValidator.validateBool(schema, value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,16 @@ final class DocumentValidator {
// ignore: avoid_positional_boolean_parameters
bool? value,
) {
return validateBasic(schema, value);
final result = validateBasic(schema, value);

if (result.isInvalid) {
return result;
}

if (value == null) {
return MissingRequiredDocumentValue(invalidNodeId: schema.nodeId);
}
return const SuccessfulDocumentValidation();
}

static DocumentValidationResult validatePattern(
Expand Down

0 comments on commit 4fe321d

Please sign in to comment.