Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cat-voices): plain text component for single and multiline #1517

Merged
merged 9 commits into from
Jan 15, 2025
2 changes: 1 addition & 1 deletion .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ lovelace
lovelaces
LTRB
Lynxx
LynxLynxx
mdlint
metadatum
metadatums
Expand Down Expand Up @@ -259,7 +260,6 @@ rustflags
rustfmt
rustls
rxdart
ryszard-schossler
saibatizoku
Schemathesis
Scripthash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension GuidanceExt on GuidanceType {
GuidanceType.tips => localizations.tipsGuidanceType,
};

// TODO(ryszard-schossler): when designers will
// TODO(LynxLynxx): when designers will
// provide us with icon, change here accordingly
SvgGenImage get icon {
return switch (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ final class Dependencies extends DependencyProvider {
get<AdminTools>(),
);
})
// TODO(ryszard-schossler): add repository for campaign management
// TODO(LynxLynxx): add repository for campaign management
..registerLazySingleton<CampaignBuilderCubit>(
CampaignBuilderCubit.new,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ class CampaignStageCard extends StatelessWidget {
if (campaign.stage == CampaignStage.live) ...[
const SizedBox(height: 16),
OutlinedButton(
// TODO(ryszard-schossler): add logic
// TODO(LynxLynxx): add logic
onPressed: () {},
child: Text(context.l10n.viewProposals),
),
] else if (campaign.stage == CampaignStage.completed) ...[
const SizedBox(height: 16),
OutlinedButton(
// TODO(ryszard-schossler): add logic
// TODO(LynxLynxx): add logic
onPressed: () {},
child: Text(context.l10n.viewVotingResults),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ResizableBoxParent extends StatelessWidget {
final Widget child;
final double minWidth;
final double minHeight;
final double iconBottomSpacing;

const ResizableBoxParent({
super.key,
Expand All @@ -18,6 +19,7 @@ class ResizableBoxParent extends StatelessWidget {
required this.child,
this.minWidth = 40,
this.minHeight = 40,
this.iconBottomSpacing = 0,
});

@override
Expand All @@ -34,6 +36,7 @@ class ResizableBoxParent extends StatelessWidget {
minHeight: minHeight,
resizableHorizontally: resizableHorizontally,
resizableVertically: resizableVertically,
iconBottomSpacing: iconBottomSpacing,
child: child,
);
},
Expand All @@ -48,6 +51,7 @@ class _ResizableBox extends StatefulWidget {
final double minHeight;
final bool resizableVertically;
final bool resizableHorizontally;
final double iconBottomSpacing;

const _ResizableBox({
required this.constraints,
Expand All @@ -56,6 +60,7 @@ class _ResizableBox extends StatefulWidget {
required this.minHeight,
required this.resizableVertically,
required this.resizableHorizontally,
required this.iconBottomSpacing,
});

@override
Expand Down Expand Up @@ -91,7 +96,7 @@ class _ResizableBoxState extends State<_ResizableBox> {
child: widget.child,
),
Positioned(
bottom: 0,
bottom: widget.iconBottomSpacing,
right: 0,
child: MouseRegion(
cursor: SystemMouseCursors.resizeDownRight,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import 'package:catalyst_voices/common/ext/document_property_ext.dart';
import 'package:catalyst_voices/widgets/widgets.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';
import 'package:flutter/services.dart';

class SimpleTextEntryWidget extends StatefulWidget {
final DocumentProperty<String> property;
final bool isEditMode;
final ValueChanged<DocumentChange> onChanged;

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

@override
State<SimpleTextEntryWidget> createState() => _SimpleTextEntryWidgetState();
}

class _SimpleTextEntryWidgetState extends State<SimpleTextEntryWidget> {
late final TextEditingController _controller;
late final FocusNode _focusNode;

String get _description => widget.property.formattedDescription;
int? get _maxLength => widget.property.schema.strLengthRange?.max;
bool get _resizable =>
widget.property.schema.definition is MultiLineTextEntryDefinition;

@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.property.value);
_controller.addListener(_handleValueChange);
_focusNode = FocusNode(canRequestFocus: widget.isEditMode);
}

@override
void didUpdateWidget(SimpleTextEntryWidget oldWidget) {
super.didUpdateWidget(oldWidget);

if (oldWidget.isEditMode != widget.isEditMode) {
_handleEditModeChanged();
if (!widget.isEditMode) {
_controller.text = widget.property.value ?? '';
}
}

if (widget.property.value != oldWidget.property.value) {
_controller.text = widget.property.value ?? '';
}
}

@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}

@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),
],
_SimpleDocumentTextField(
controller: _controller,
focusNode: _focusNode,
onFieldSubmitted: _notifyChangeListener,
validator: _validate,
enabled: widget.isEditMode,
// TODO(LynxLynxx): check if this is right after schema is finalized
hintText: widget.property.schema.defaultValue,
resizable: _resizable,
maxLength: _maxLength,
),
],
);
}

void _handleEditModeChanged() {
_focusNode.canRequestFocus = widget.isEditMode;

if (widget.isEditMode) {
_focusNode.requestFocus();
} else {
_focusNode.unfocus();
}
}

void _handleValueChange() {
final controllerValue = _controller.text;
if (widget.property.value != controllerValue &&
controllerValue.isNotEmpty) {
_notifyChangeListener(controllerValue);
}
}

void _notifyChangeListener(String? value) {
final change = DocumentChange(
nodeId: widget.property.schema.nodeId,
value: value,
);

widget.onChanged(change);
}

VoicesTextFieldValidationResult _validate(String? value) {
if (!widget.isEditMode) {
return const VoicesTextFieldValidationResult.none();
}
final schema = widget.property.schema;
final result = schema.validatePropertyValue(value);
if (result.isValid) {
return const VoicesTextFieldValidationResult.none();
} else {
final localized = LocalizedDocumentValidationResult.from(result);
return VoicesTextFieldValidationResult.error(localized.message(context));
}
}
}

class _SimpleDocumentTextField extends StatelessWidget {
final TextEditingController? controller;
final ValueChanged<String>? onFieldSubmitted;
final VoicesTextFieldValidator? validator;
final FocusNode? focusNode;
final String? hintText;
final bool enabled;
final bool resizable;
final int? maxLength;

const _SimpleDocumentTextField({
this.controller,
this.onFieldSubmitted,
this.validator,
this.focusNode,
this.hintText,
this.enabled = false,
this.resizable = false,
this.maxLength,
});

@override
Widget build(BuildContext context) {
return VoicesTextField(
controller: controller,
focusNode: focusNode,
onFieldSubmitted: onFieldSubmitted,
validator: validator,
decoration: VoicesTextFieldDecoration(
hintText: hintText,
),
enabled: enabled,
resizable: resizable,
maxLengthEnforcement: MaxLengthEnforcement.none,
autovalidateMode: AutovalidateMode.disabled,
maxLines: resizable ? null : 1,
maxLength: maxLength,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class VoicesTextField extends StatefulWidget {
/// [AutovalidateMode]
final AutovalidateMode? autovalidateMode;

/// [MaxLengthEnforcement]
final MaxLengthEnforcement? maxLengthEnforcement;

final ValueChanged<VoicesTextFieldStatus>? onStatusChanged;

const VoicesTextField({
Expand Down Expand Up @@ -113,6 +116,7 @@ class VoicesTextField extends StatefulWidget {
this.onSaved,
this.inputFormatters,
this.autovalidateMode,
this.maxLengthEnforcement,
this.onStatusChanged,
});

Expand Down Expand Up @@ -209,6 +213,8 @@ class _VoicesTextFieldState extends State<VoicesTextField> {
ResizableBoxParent(
resizableHorizontally: resizable,
resizableVertically: resizable,
minHeight: widget.maxLines == null ? 65 : 48,
iconBottomSpacing: widget.maxLines == null ? 18 : 0,
child: TextFormField(
textAlignVertical: TextAlignVertical.top,
autofocus: widget.autofocus,
Expand All @@ -229,6 +235,7 @@ class _VoicesTextFieldState extends State<VoicesTextField> {
maxLines: widget.maxLines,
minLines: widget.minLines,
maxLength: widget.maxLength,
maxLengthEnforcement: widget.maxLengthEnforcement,
readOnly: widget.readOnly,
ignorePointers: widget.ignorePointers,
enabled: widget.enabled,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:catalyst_voices/widgets/document_builder/agreement_confirmation_widget.dart';
import 'package:catalyst_voices/widgets/document_builder/document_token_value_widget.dart';
import 'package:catalyst_voices/widgets/document_builder/simple_text_entry_widget.dart';
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';
Expand Down Expand Up @@ -211,8 +212,7 @@ class _PropertyBuilder extends StatelessWidget {
'${property.schema.definition} unsupported '
'by $DocumentBuilderSectionTile',
);
case SingleLineTextEntryDefinition():
case MultiLineTextEntryDefinition():

case MultiLineTextEntryMarkdownDefinition():
case MultiSelectDefinition():
case SingleLineTextEntryListDefinition():
Expand Down Expand Up @@ -267,12 +267,21 @@ class _PropertyBuilder extends StatelessWidget {
onChanged: onChanged,
);
case TokenValueCardanoADADefinition():
final castProperty = definition.castProperty(property);
return DocumentTokenValueWidget(
property: definition.castProperty(property),
property: castProperty,
currency: const Currency.ada(),
isEditMode: isEditMode,
onChanged: onChanged,
);
case SingleLineTextEntryDefinition():
case MultiLineTextEntryDefinition():
final castProperty = definition.castProperty(property);
return SimpleTextEntryWidget(
property: castProperty as DocumentProperty<String>,
isEditMode: isEditMode,
onChanged: onChanged,
);
case YesNoChoiceDefinition():
final castProperty = definition.castProperty(property);
return YesNoChoiceWidget(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class CampaignBuilderCubit extends Cubit<CampaignBuilderState> {
void updateCampaignPublish(CampaignPublish publish) {
emit(state.copyWith(isLoading: true));

// TODO(ryszard-schossler): call backend to update campaign status
// TODO(LynxLynxx): call backend to update campaign status

emit(
state.copyWith(
Expand All @@ -37,7 +37,7 @@ class CampaignBuilderCubit extends Cubit<CampaignBuilderState> {
}) {
emit(state.copyWith(isLoading: true));

// TODO(ryszard-schossler): call backend to update campaign dates
// TODO(LynxLynxx): call backend to update campaign dates

emit(
state.copyWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ enum Browser {
case Browser.chrome:
return 'https://clients2.google.com/service/update2/crx?response=redirect&os=win&arch=x64&os_arch=x86_64&nacl_arch=x86-64&prod=chromiumcrx&prodchannel=beta&prodversion=79.0.3945.53&lang=ru&acceptformat=crx3&x=id%3D$extensionId%26installsource%3Dondemand%26uc';
case Browser.brave:
return 'https://brave.com/extension/$extensionId'; // TODO(ryszard-schossler): add brave store url
return 'https://brave.com/extension/$extensionId'; // TODO(LynxLynxx): add brave store url
case Browser.firefox:
return 'https://addons.mozilla.org/en-US/firefox/addon/$extensionId'; // TODO(ryszard-schossler): add firefox store url
return 'https://addons.mozilla.org/en-US/firefox/addon/$extensionId'; // TODO(LynxLynxx): add firefox store url
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
part of '../document_definitions.dart';

// TODO(ryszard-schossler): Verify BaseDocumentDefinition type
// TODO(LynxLynxx): Verify BaseDocumentDefinition type
final class NestedQuestionsDefinition
extends BaseDocumentDefinition<List<String>> {
final DocumentDefinitionsFormat format;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class DocumentSchemaPropertyDto {
final int? minItems;
final int? maxItems;

// TODO(ryszard-schossler): return to this
// TODO(LynxLynxx): return to this
final Map<String, dynamic>? items;

/// Logical boolean algebra conditions.
Expand Down
Loading
Loading