Skip to content

Commit 9c701cc

Browse files
authored
feat(cat-voices): getting title range from template (#2766)
* feat: getting title range from template * fix: getPropretySchema implemnetation * chore: update comment * fix: format
1 parent 90bc760 commit 9c701cc

File tree

11 files changed

+469
-345
lines changed

11 files changed

+469
-345
lines changed

catalyst_voices/apps/voices/lib/widgets/modals/proposals/category_brief_dialog.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class _CategoryBriefDialogState extends State<CategoryBriefDialog> {
3636
Widget build(BuildContext context) {
3737
return VoicesScrollbar(
3838
controller: _scrollController,
39+
alwaysVisible: true,
3940
child: SingleChildScrollView(
4041
controller: _scrollController,
4142
child: CategoryCompactDetailView(category: widget.category),

catalyst_voices/apps/voices/lib/widgets/modals/proposals/create_new_proposal_action_buttons.dart

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
34
import 'package:catalyst_voices/routes/routing/proposal_builder_route.dart';
45
import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart';
56
import 'package:catalyst_voices/widgets/buttons/voices_outlined_button.dart';
@@ -19,32 +20,42 @@ class CreateNewProposalActionButtons extends StatelessWidget {
1920

2021
@override
2122
Widget build(BuildContext context) {
22-
return switch (step) {
23-
CreateProposalWithPreselectedCategoryStep() => const Row(
24-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
25-
children: [
26-
_AgreementCheckboxes(),
27-
_StartProposalButton(),
28-
],
29-
),
30-
CreateProposalWithoutPreselectedCategoryStep(:final stage)
31-
when stage == CreateProposalStage.selectCategory =>
32-
const Row(
33-
children: [
34-
_AgreementCheckboxes(),
35-
Spacer(),
36-
_BackButton(),
37-
SizedBox(width: 8),
38-
_StartProposalButton(),
39-
],
40-
),
41-
CreateProposalWithoutPreselectedCategoryStep() => const Row(
42-
children: [
43-
Spacer(),
44-
_SelectCategoryButton(),
45-
],
23+
return Container(
24+
decoration: BoxDecoration(
25+
border: Border(
26+
top: BorderSide(
27+
color: context.colors.outlineBorderVariant,
28+
),
4629
),
47-
};
30+
),
31+
padding: const EdgeInsets.fromLTRB(24, 16, 24, 24),
32+
child: switch (step) {
33+
CreateProposalWithPreselectedCategoryStep() => const Row(
34+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
35+
children: [
36+
_AgreementCheckboxes(),
37+
_StartProposalButton(),
38+
],
39+
),
40+
CreateProposalWithoutPreselectedCategoryStep(:final stage)
41+
when stage == CreateProposalStage.selectCategory =>
42+
const Row(
43+
children: [
44+
_AgreementCheckboxes(),
45+
Spacer(),
46+
_BackButton(),
47+
SizedBox(width: 8),
48+
_StartProposalButton(),
49+
],
50+
),
51+
CreateProposalWithoutPreselectedCategoryStep() => const Row(
52+
children: [
53+
Spacer(),
54+
_SelectCategoryButton(),
55+
],
56+
),
57+
},
58+
);
4859
}
4960
}
5061

catalyst_voices/apps/voices/lib/widgets/modals/proposals/create_new_proposal_category_selection.dart

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
22
import 'package:catalyst_voices/pages/category/category_compact_detail_view.dart';
3+
import 'package:catalyst_voices/widgets/widgets.dart';
34
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
45
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
56
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
67
import 'package:collection/collection.dart';
78
import 'package:flutter/material.dart';
89

9-
class CreateNewProposalCategorySelection extends StatelessWidget {
10+
class CreateNewProposalCategorySelection extends StatefulWidget {
1011
final List<CampaignCategoryDetailsViewModel> categories;
1112
final SignedDocumentRef? selectedCategory;
1213
final ValueChanged<SignedDocumentRef?> onCategorySelected;
@@ -18,45 +19,9 @@ class CreateNewProposalCategorySelection extends StatelessWidget {
1819
required this.onCategorySelected,
1920
});
2021

21-
CampaignCategoryDetailsViewModel? get _selectedCategory {
22-
return categories.firstWhereOrNull((element) => element.id == selectedCategory);
23-
}
24-
2522
@override
26-
Widget build(BuildContext context) {
27-
return Expanded(
28-
child: Padding(
29-
padding: const EdgeInsets.only(bottom: 50),
30-
child: Row(
31-
crossAxisAlignment: CrossAxisAlignment.start,
32-
children: [
33-
Expanded(
34-
child: ListView.separated(
35-
itemBuilder: (context, index) => _CategoryCard(
36-
name: categories[index].formattedName,
37-
description: categories[index].shortDescription,
38-
ref: categories[index].id,
39-
isSelected: categories[index].id == selectedCategory,
40-
onCategorySelected: onCategorySelected,
41-
),
42-
separatorBuilder: (context, index) => const SizedBox(height: 16),
43-
itemCount: categories.length,
44-
),
45-
),
46-
const SizedBox(width: 16),
47-
Expanded(
48-
flex: 2,
49-
child: _selectedCategory != null
50-
? SingleChildScrollView(
51-
child: CategoryCompactDetailView(category: _selectedCategory!),
52-
)
53-
: const _NoneCategorySelected(),
54-
),
55-
],
56-
),
57-
),
58-
);
59-
}
23+
State<CreateNewProposalCategorySelection> createState() =>
24+
_CreateNewProposalCategorySelectionState();
6025
}
6126

6227
class _CategoryCard extends StatelessWidget {
@@ -118,6 +83,67 @@ class _CategoryCard extends StatelessWidget {
11883
}
11984
}
12085

86+
class _CreateNewProposalCategorySelectionState extends State<CreateNewProposalCategorySelection> {
87+
late final ScrollController _scrollController;
88+
89+
CampaignCategoryDetailsViewModel? get _selectedCategory {
90+
return widget.categories.firstWhereOrNull((element) => element.id == widget.selectedCategory);
91+
}
92+
93+
@override
94+
Widget build(BuildContext context) {
95+
return Expanded(
96+
child: Padding(
97+
padding: const EdgeInsets.only(bottom: 68),
98+
child: Row(
99+
crossAxisAlignment: CrossAxisAlignment.start,
100+
children: [
101+
Expanded(
102+
child: ListView.separated(
103+
itemBuilder: (context, index) => _CategoryCard(
104+
name: widget.categories[index].formattedName,
105+
description: widget.categories[index].shortDescription,
106+
ref: widget.categories[index].id,
107+
isSelected: widget.categories[index].id == widget.selectedCategory,
108+
onCategorySelected: widget.onCategorySelected,
109+
),
110+
separatorBuilder: (context, index) => const SizedBox(height: 16),
111+
itemCount: widget.categories.length,
112+
),
113+
),
114+
const SizedBox(width: 16),
115+
Expanded(
116+
flex: 2,
117+
child: _selectedCategory != null
118+
? VoicesScrollbar(
119+
controller: _scrollController,
120+
alwaysVisible: true,
121+
child: SingleChildScrollView(
122+
controller: _scrollController,
123+
child: CategoryCompactDetailView(category: _selectedCategory!),
124+
),
125+
)
126+
: const _NoneCategorySelected(),
127+
),
128+
],
129+
),
130+
),
131+
);
132+
}
133+
134+
@override
135+
void dispose() {
136+
_scrollController.dispose();
137+
super.dispose();
138+
}
139+
140+
@override
141+
void initState() {
142+
super.initState();
143+
_scrollController = ScrollController();
144+
}
145+
}
146+
121147
class _NoneCategorySelected extends StatelessWidget {
122148
const _NoneCategorySelected();
123149

catalyst_voices/apps/voices/lib/widgets/modals/proposals/create_new_proposal_dialog.dart

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart';
1212
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
1313
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
1414
import 'package:flutter/material.dart';
15+
import 'package:flutter/services.dart';
1516

1617
typedef _SelectedCategoryData = ({
1718
List<CampaignCategoryDetailsViewModel> categories,
@@ -91,17 +92,17 @@ class _ContentView extends StatelessWidget {
9192

9293
@override
9394
Widget build(BuildContext context) {
94-
return Padding(
95-
padding: const EdgeInsets.fromLTRB(24, 16, 24, 24),
96-
child: Stack(
97-
children: [
98-
child,
99-
Align(
100-
alignment: Alignment.bottomCenter,
101-
child: CreateNewProposalActionButtons(step: step),
102-
),
103-
],
104-
),
95+
return Stack(
96+
children: [
97+
Padding(
98+
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
99+
child: child,
100+
),
101+
Align(
102+
alignment: Alignment.bottomCenter,
103+
child: CreateNewProposalActionButtons(step: step),
104+
),
105+
],
105106
);
106107
}
107108
}
@@ -114,7 +115,8 @@ class _CreateNewProposalDialogState extends State<CreateNewProposalDialog>
114115
constraints: const BoxConstraints.tightFor(height: 800, width: 1200),
115116
header: VoicesAlignTitleHeader(
116117
title: _getTitle(),
117-
padding: const EdgeInsets.all(24),
118+
titleStyle: context.textTheme.titleLarge,
119+
padding: const EdgeInsets.all(20),
118120
),
119121
body: const _Content(),
120122
);
@@ -243,8 +245,7 @@ class _TitleTextField extends StatelessWidget {
243245
return VoicesTextField(
244246
initialText: title.value,
245247
onFieldSubmitted: (_) {},
246-
onChanged: (value) =>
247-
context.read<NewProposalCubit>().updateTitle(ProposalTitle.dirty(value ?? '')),
248+
onChanged: (value) => context.read<NewProposalCubit>().updateTitle(value ?? ''),
248249
decoration: VoicesTextFieldDecoration(
249250
borderRadius: BorderRadius.circular(8),
250251
filled: false,
@@ -256,6 +257,8 @@ class _TitleTextField extends StatelessWidget {
256257
errorText: title.displayError?.message(context),
257258
helperText: context.l10n.required.starred().toLowerCase(),
258259
),
260+
maxLength: title.titleLengthRange?.max,
261+
maxLengthEnforcement: MaxLengthEnforcement.none,
259262
);
260263
},
261264
);

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/new_proposal/new_proposal_cubit.dart

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,25 @@ class NewProposalCubit extends Cubit<NewProposalState>
7474
final step = categoryRef == null
7575
? const CreateProposalWithoutPreselectedCategoryStep()
7676
: const CreateProposalWithPreselectedCategoryStep();
77+
final categoriesModels = await _campaignService.getCampaignCategories();
78+
final templateRef = await _proposalService.getProposalTemplate(
79+
// TODO(LynxLynxx): when we have separate proposal template for generic questions use it here
80+
// right now user can start creating proposal without selecting category.
81+
// Right now every category have the same requirements for title so we can do a fallback for
82+
// first category from the list.
83+
ref: categoryRef ?? categoriesModels.first.proposalTemplateRef,
84+
);
7785

78-
emit(state.copyWith(step: step, categoryRef: Optional(categoryRef)));
79-
80-
final categories = await _getCategories();
86+
final titlePropertySchema = templateRef.schema
87+
.getPropertySchema(ProposalDocument.titleNodeId)! as DocumentStringSchema;
88+
final titleRange = titlePropertySchema.strLengthRange;
8189

90+
final categories = categoriesModels.map(CampaignCategoryDetailsViewModel.fromModel).toList();
8291
final newState = state.copyWith(
8392
isLoading: false,
93+
step: step,
94+
categoryRef: Optional(categoryRef),
95+
titleLengthRange: Optional(titleRange),
8496
categories: categories,
8597
);
8698

@@ -119,17 +131,12 @@ class NewProposalCubit extends Cubit<NewProposalState>
119131
);
120132
}
121133

122-
void updateTitle(ProposalTitle title) {
123-
emit(state.copyWith(title: title));
134+
void updateTitle(String title) {
135+
emit(state.copyWith(title: ProposalTitle.dirty(title, state.titleLengthRange)));
124136
}
125137

126138
void updateTitleStage() {
127139
const stage = CreateProposalWithoutPreselectedCategoryStep();
128140
emit(state.copyWith(step: stage));
129141
}
130-
131-
Future<List<CampaignCategoryDetailsViewModel>> _getCategories() async {
132-
final categories = await _campaignService.getCampaignCategories();
133-
return categories.map(CampaignCategoryDetailsViewModel.fromModel).toList();
134-
}
135142
}

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/new_proposal/new_proposal_state.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
2+
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
23
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
34
import 'package:collection/collection.dart';
45
import 'package:equatable/equatable.dart';
@@ -10,6 +11,7 @@ class NewProposalState extends Equatable {
1011
final bool isAgreeToNoFurtherCategoryChange;
1112
final ProposalCreationStep step;
1213
final ProposalTitle title;
14+
final NumRange<int>? titleLengthRange;
1315
final SignedDocumentRef? categoryRef;
1416
final List<CampaignCategoryDetailsViewModel> categories;
1517

@@ -20,6 +22,7 @@ class NewProposalState extends Equatable {
2022
this.isAgreeToNoFurtherCategoryChange = false,
2123
this.step = const CreateProposalWithoutPreselectedCategoryStep(),
2224
required this.title,
25+
this.titleLengthRange,
2326
this.categoryRef,
2427
this.categories = const [],
2528
});
@@ -41,6 +44,7 @@ class NewProposalState extends Equatable {
4144
isAgreeToNoFurtherCategoryChange,
4245
step,
4346
title,
47+
titleLengthRange,
4448
categoryRef,
4549
categories,
4650
];
@@ -57,6 +61,7 @@ class NewProposalState extends Equatable {
5761
bool? isAgreeToNoFurtherCategoryChange,
5862
ProposalCreationStep? step,
5963
ProposalTitle? title,
64+
Optional<NumRange<int>>? titleLengthRange,
6065
Optional<SignedDocumentRef>? categoryRef,
6166
List<CampaignCategoryDetailsViewModel>? categories,
6267
}) {
@@ -68,6 +73,7 @@ class NewProposalState extends Equatable {
6873
isAgreeToNoFurtherCategoryChange ?? this.isAgreeToNoFurtherCategoryChange,
6974
step: step ?? this.step,
7075
title: title ?? this.title,
76+
titleLengthRange: titleLengthRange.dataOr(this.titleLengthRange),
7177
categoryRef: categoryRef.dataOr(this.categoryRef),
7278
categories: categories ?? this.categories,
7379
);

0 commit comments

Comments
 (0)