Skip to content

Commit 771ee64

Browse files
feat(cat-voices): PlaceholderRichText and TokenValue helper handles nullable range (#1489)
* feat: PlaceholderRichText and TokenValue helper handles nullable range * chore: clean up * chore: fix import * chore: add or equal to loc file * chore: adjust strings
1 parent 4908eeb commit 771ee64

File tree

6 files changed

+137
-31
lines changed

6 files changed

+137
-31
lines changed

catalyst_voices/apps/voices/lib/widgets/document_builder/agreement_confirmation_widget.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'package:catalyst_voices/widgets/rich_text/markdown_text.dart';
21
import 'package:catalyst_voices/widgets/widgets.dart';
32
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
43
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
@@ -36,7 +35,9 @@ class _DocumentCheckboxBuilderWidgetState
3635
late bool _currentEditValue;
3736

3837
DocumentNodeId get _nodeId => widget.nodeId;
38+
3939
MarkdownData get _description => MarkdownData(widget.description);
40+
4041
bool get _defaultValue => widget.definition.defaultValue;
4142

4243
@override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import 'package:flutter/material.dart';
2+
3+
final _kPlaceholderRegExp = RegExp(r'{(\w*)}');
4+
5+
typedef PlaceholderSpanBuilder = InlineSpan Function(
6+
BuildContext context,
7+
String placeholder,
8+
);
9+
10+
class PlaceholderRichText extends StatefulWidget {
11+
final String text;
12+
final PlaceholderSpanBuilder placeholderSpanBuilder;
13+
final TextStyle? style;
14+
final TextAlign textAlign;
15+
16+
const PlaceholderRichText(
17+
this.text, {
18+
super.key,
19+
required this.placeholderSpanBuilder,
20+
this.style,
21+
this.textAlign = TextAlign.start,
22+
});
23+
24+
@override
25+
State<PlaceholderRichText> createState() => _PlaceholderRichTextState();
26+
}
27+
28+
class _PlaceholderRichTextState extends State<PlaceholderRichText> {
29+
List<InlineSpan> _spans = [];
30+
31+
@override
32+
void didChangeDependencies() {
33+
super.didChangeDependencies();
34+
35+
// Context colors may have changed, rebuild it.
36+
_spans = _calculateSpans();
37+
}
38+
39+
@override
40+
void didUpdateWidget(covariant PlaceholderRichText oldWidget) {
41+
super.didUpdateWidget(oldWidget);
42+
43+
if (widget.text != oldWidget.text) {
44+
_spans = _calculateSpans();
45+
}
46+
}
47+
48+
@override
49+
Widget build(BuildContext context) {
50+
return Text.rich(
51+
TextSpan(children: _spans),
52+
textAlign: widget.textAlign,
53+
style: widget.style,
54+
);
55+
}
56+
57+
List<InlineSpan> _calculateSpans() {
58+
final text = widget.text;
59+
final spans = <InlineSpan>[];
60+
61+
text.splitMapJoin(
62+
_kPlaceholderRegExp,
63+
onMatch: (match) {
64+
final placeholder = match.group(0)!;
65+
// removes "{" and "}"
66+
final key = placeholder.substring(1, placeholder.length - 1);
67+
68+
final span = widget.placeholderSpanBuilder(context, key);
69+
70+
spans.add(span);
71+
72+
return '';
73+
},
74+
onNonMatch: (match) {
75+
spans.add(TextSpan(text: match));
76+
return '';
77+
},
78+
);
79+
80+
return spans;
81+
}
82+
}

catalyst_voices/apps/voices/lib/widgets/text_field/token_field.dart

+46-25
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class TokenField extends StatelessWidget {
5353
filled: true,
5454
helper: range != null && showHelper
5555
? _Helper(
56-
symbol: currency.symbol,
56+
currency: currency,
5757
range: range,
5858
)
5959
: null,
@@ -87,37 +87,58 @@ class TokenField extends StatelessWidget {
8787
}
8888

8989
class _Helper extends StatelessWidget {
90-
final String symbol;
90+
final Currency currency;
9191
final Range<int> range;
9292

9393
const _Helper({
94-
required this.symbol,
94+
required this.currency,
9595
required this.range,
9696
});
9797

9898
@override
9999
Widget build(BuildContext context) {
100-
// TODO(damian-molinski): Range can accept null as min/max
101-
// meaning they are unconstrained, handle it
102-
// TODO(damian-molinski): Refactor text formatting with smarter syntax
103-
return Text.rich(
104-
TextSpan(
105-
children: [
106-
TextSpan(text: context.l10n.requestedAmountShouldBeBetween),
107-
const TextSpan(text: ' '),
108-
TextSpan(
109-
text: '$symbol${range.min}',
110-
style: const TextStyle(fontWeight: FontWeight.bold),
111-
),
112-
const TextSpan(text: ' '),
113-
TextSpan(text: context.l10n.and),
114-
const TextSpan(text: ' '),
115-
TextSpan(
116-
text: '$symbol${range.max}',
117-
style: const TextStyle(fontWeight: FontWeight.bold),
118-
),
119-
],
120-
),
121-
);
100+
final min = range.min;
101+
final max = range.max;
102+
103+
const boldStyle = TextStyle(fontWeight: FontWeight.bold);
104+
105+
if (min != null && max != null) {
106+
return PlaceholderRichText(
107+
context.l10n.requestedAmountShouldBeBetweenMinAndMax,
108+
placeholderSpanBuilder: (context, placeholder) {
109+
return switch (placeholder) {
110+
'min' => TextSpan(text: currency.format(min), style: boldStyle),
111+
'max' => TextSpan(text: currency.format(max), style: boldStyle),
112+
_ => throw ArgumentError('Unknown placeholder[$placeholder]'),
113+
};
114+
},
115+
);
116+
}
117+
118+
if (min != null) {
119+
return PlaceholderRichText(
120+
context.l10n.requestedAmountShouldBeMoreThan,
121+
placeholderSpanBuilder: (context, placeholder) {
122+
return switch (placeholder) {
123+
'min' => TextSpan(text: currency.format(min), style: boldStyle),
124+
_ => throw ArgumentError('Unknown placeholder[$placeholder]'),
125+
};
126+
},
127+
);
128+
}
129+
130+
if (max != null) {
131+
return PlaceholderRichText(
132+
context.l10n.requestedAmountShouldBeLessThan,
133+
placeholderSpanBuilder: (context, placeholder) {
134+
return switch (placeholder) {
135+
'max' => TextSpan(text: currency.format(max), style: boldStyle),
136+
_ => throw ArgumentError('Unknown placeholder[$placeholder]'),
137+
};
138+
},
139+
);
140+
}
141+
142+
return const Text('');
122143
}
123144
}

catalyst_voices/apps/voices/lib/widgets/widgets.dart

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export 'navigation/sections_controller.dart';
6060
export 'navigation/sections_list_view.dart';
6161
export 'navigation/sections_list_view_builder.dart';
6262
export 'navigation/sections_menu.dart';
63+
export 'rich_text/markdown_text.dart';
64+
export 'rich_text/placeholder_rich_text.dart';
6365
export 'scrollbar/voices_scrollbar.dart';
6466
export 'seed_phrase/seed_phrases_completer.dart';
6567
export 'seed_phrase/seed_phrases_picker.dart';

catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb

+3-5
Original file line numberDiff line numberDiff line change
@@ -1180,11 +1180,9 @@
11801180
"searchProposals": "Search Proposals",
11811181
"search": "Search…",
11821182
"agree": "I Agree",
1183-
"requestedAmountShouldBeBetween": "Requested amount should be between",
1184-
"and": "and",
1185-
"@and": {
1186-
"description": "General text. May be used in context of A4000 and $5000"
1187-
},
1183+
"requestedAmountShouldBeBetweenMinAndMax": "Requested amount should be between {min} and {max}",
1184+
"requestedAmountShouldBeMoreThan": "Requested amount should be at least {min}",
1185+
"requestedAmountShouldBeLessThan": "Requested amount should be at most {max}",
11881186
"errorValidationTokenNotParsed": "Invalid input. Could not parse parse.",
11891187
"@errorValidationTokenNotParsed": {
11901188
"description": "A validation error when user enters input which cannot be parsed into token value."

catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/currency.dart

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ final class Currency extends Equatable {
1616
symbol: '₳',
1717
);
1818

19+
String format(num money) => '$symbol$money';
20+
1921
@override
2022
List<Object?> get props => [
2123
name,

0 commit comments

Comments
 (0)