Skip to content

Commit d8babcf

Browse files
authored
feat(cat-voices): integrate user proposals into drawer and overall spaces (#2159)
* feat(cat-voices): integrate user proposal selectors with enhanced proposal handling in overview pages * fix(user-proposal-selectors): correct typo in comment; add SmallProposalCard tests for rendering and draft visibility * fix: review
1 parent 01d02ad commit d8babcf

File tree

17 files changed

+492
-208
lines changed

17 files changed

+492
-208
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
22

3+
typedef DataVisibilityState<T> = ({bool show, T data});
4+
35
typedef VisibilityState = ({bool show, LocalizedException? error});

catalyst_voices/apps/voices/lib/pages/overall_spaces/space/discovery_overview.dart

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
2-
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
31
import 'package:catalyst_voices/pages/overall_spaces/space/space_overview_header.dart';
42
import 'package:catalyst_voices/pages/overall_spaces/space/space_overview_nav_tile.dart';
3+
import 'package:catalyst_voices/pages/overall_spaces/space/user_proposal_selectors/user_proposal_selectors.dart';
54
import 'package:catalyst_voices/pages/overall_spaces/space_overview_container.dart';
65
import 'package:catalyst_voices/routes/routes.dart';
7-
import 'package:catalyst_voices/widgets/cards/small_proposal_card.dart';
86
import 'package:catalyst_voices/widgets/widgets.dart';
97
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
108
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
@@ -21,14 +19,15 @@ class DiscoveryOverview extends StatelessWidget {
2119
Widget build(BuildContext context) {
2220
return const SpaceOverviewContainer(
2321
child: Column(
22+
crossAxisAlignment: CrossAxisAlignment.start,
2423
children: [
2524
SpaceOverviewHeader(Space.discovery),
2625
_DiscoveryDashboardTile(),
2726
_FeedbackTile(),
2827
VoicesDivider(indent: 0, endIndent: 0, height: 16),
2928
Expanded(
3029
child: SingleChildScrollView(
31-
child: _PublishedProposalSelector(1, 5),
30+
child: _PublishedProposalSelector(),
3231
),
3332
),
3433
],
@@ -81,93 +80,8 @@ class _FeedbackTile extends StatelessWidget {
8180
}
8281
}
8382

84-
class _Header extends StatelessWidget {
85-
final int currentProposals;
86-
final int maxProposals;
87-
const _Header({
88-
required this.maxProposals,
89-
required this.currentProposals,
90-
});
91-
92-
@override
93-
Widget build(BuildContext context) {
94-
return Padding(
95-
padding: const EdgeInsets.symmetric(vertical: 18)
96-
..add(
97-
const EdgeInsets.only(left: 16),
98-
),
99-
child: Text(
100-
context.l10n.noPublishedProposalsOnMaxCount(
101-
currentProposals,
102-
maxProposals,
103-
),
104-
style: context.textTheme.titleMedium?.copyWith(
105-
color: context.colors.textOnPrimaryLevel1,
106-
),
107-
),
108-
);
109-
}
110-
}
111-
112-
class _PublishedProposals extends StatelessWidget {
113-
final int currentProposals;
114-
final int maxProposals;
115-
const _PublishedProposals(
116-
this.currentProposals,
117-
this.maxProposals,
118-
);
119-
120-
@override
121-
Widget build(BuildContext context) {
122-
// TODO(LynxLynxx): replace with real data
123-
final proposal = Proposal(
124-
selfRef: SignedDocumentRef.generateFirstRef(),
125-
title: 'Latest proposal that is making its rounds.',
126-
category: 'F14: Cardano Use Cases: Concept',
127-
categoryId: const SignedDocumentRef(id: 'dummy_category_id'),
128-
description: 'Lorem ipsum dolor sit ',
129-
fundsRequested: const Coin(100000),
130-
status: ProposalStatus.draft,
131-
publish: ProposalPublish.localDraft,
132-
commentsCount: 0,
133-
duration: 6,
134-
author: 'Alex Wells',
135-
updateDate: DateTime.now(),
136-
versions: const [],
137-
);
138-
return Column(
139-
mainAxisSize: MainAxisSize.min,
140-
crossAxisAlignment: CrossAxisAlignment.start,
141-
children: [
142-
_Header(
143-
maxProposals: maxProposals,
144-
currentProposals: currentProposals,
145-
),
146-
SmallProposalCard(
147-
proposal: proposal.copyWith(
148-
publish: ProposalPublish.submittedProposal,
149-
commentsCount: 1,
150-
),
151-
),
152-
const SizedBox(height: 12),
153-
SmallProposalCard(
154-
proposal: proposal.copyWith(
155-
publish: ProposalPublish.publishedDraft,
156-
commentsCount: 12,
157-
),
158-
),
159-
],
160-
);
161-
}
162-
}
163-
16483
class _PublishedProposalSelector extends StatelessWidget {
165-
final int currentProposals;
166-
final int maxProposals;
167-
const _PublishedProposalSelector(
168-
this.currentProposals,
169-
this.maxProposals,
170-
);
84+
const _PublishedProposalSelector();
17185

17286
@override
17387
Widget build(BuildContext context) {
@@ -178,10 +92,7 @@ class _PublishedProposalSelector extends StatelessWidget {
17892
builder: (context, state) {
17993
return Offstage(
18094
offstage: state,
181-
child: _PublishedProposals(
182-
currentProposals,
183-
maxProposals,
184-
),
95+
child: const DiscoveryOverviewProposalSelector(),
18596
);
18697
},
18798
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
part of 'user_proposal_selectors.dart';
2+
3+
class DiscoveryOverviewProposalSelector extends StatelessWidget {
4+
const DiscoveryOverviewProposalSelector({super.key});
5+
6+
@override
7+
Widget build(BuildContext context) {
8+
return const Stack(
9+
children: [
10+
_LoadingProposalSelector(),
11+
_ErrorProposalSelector(),
12+
_DataProposalSelector(),
13+
],
14+
);
15+
}
16+
}
17+
18+
class _DataProposalSelector extends StatelessWidget {
19+
const _DataProposalSelector();
20+
21+
@override
22+
Widget build(BuildContext context) {
23+
return SingleChildScrollView(
24+
child: BlocSelector<WorkspaceBloc, WorkspaceState,
25+
DataVisibilityState<List<Proposal>>>(
26+
selector: (state) {
27+
return (data: state.published, show: state.showProposals);
28+
},
29+
builder: (context, state) {
30+
return Column(
31+
mainAxisSize: MainAxisSize.min,
32+
crossAxisAlignment: CrossAxisAlignment.start,
33+
children: [
34+
_Header(
35+
title: context.l10n.noPublishedProposalsOnMaxCount(
36+
state.data.length,
37+
5,
38+
),
39+
),
40+
Offstage(
41+
offstage: !state.show,
42+
child: _DataProposalWidget(
43+
proposals: state.data,
44+
emptyMessage: context.l10n.noPublishedProposals,
45+
),
46+
),
47+
const SizedBox(height: 12),
48+
],
49+
);
50+
},
51+
),
52+
);
53+
}
54+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
2+
import 'package:catalyst_voices/common/typedefs.dart';
3+
import 'package:catalyst_voices/widgets/cards/small_proposal_card.dart';
4+
import 'package:catalyst_voices/widgets/indicators/voices_circular_progress_indicator.dart';
5+
import 'package:catalyst_voices/widgets/indicators/voices_error_indicator.dart';
6+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
7+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
8+
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
9+
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
10+
import 'package:flutter/material.dart';
11+
import 'package:flutter_bloc/flutter_bloc.dart';
12+
13+
// As widget across two overviews are almost similar and are not reusable
14+
// anywhere else it useful to use as part
15+
part 'discovery_overview_proposal_selector.dart';
16+
part 'user_proposals_selector_widgets.dart';
17+
part 'workspace_overview_proposal_selector.dart';
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
part of 'user_proposal_selectors.dart';
2+
3+
class _DataProposalWidget extends StatelessWidget {
4+
final List<Proposal> proposals;
5+
final String emptyMessage;
6+
final bool showLatestLocal;
7+
8+
const _DataProposalWidget({
9+
required this.proposals,
10+
required this.emptyMessage,
11+
this.showLatestLocal = false,
12+
});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
if (proposals.isEmpty) {
17+
return Padding(
18+
padding: const EdgeInsets.symmetric(horizontal: 20),
19+
child: Text(
20+
emptyMessage,
21+
style: context.textTheme.bodyMedium?.copyWith(
22+
color: context.colors.textOnPrimaryLevel1,
23+
),
24+
),
25+
);
26+
}
27+
return Column(
28+
spacing: 12,
29+
children: proposals
30+
.map(
31+
(e) => SmallProposalCard(
32+
proposal: e,
33+
showLatestLocal: showLatestLocal,
34+
),
35+
)
36+
.toList(),
37+
);
38+
}
39+
}
40+
41+
class _ErrorProposalSelector extends StatelessWidget {
42+
const _ErrorProposalSelector();
43+
44+
@override
45+
Widget build(BuildContext context) {
46+
return BlocSelector<WorkspaceBloc, WorkspaceState, VisibilityState>(
47+
selector: (state) {
48+
return (error: state.error, show: state.showError);
49+
},
50+
builder: (context, state) {
51+
return Offstage(
52+
offstage: !state.show,
53+
child: Padding(
54+
padding: const EdgeInsets.only(top: 60),
55+
child: VoicesErrorIndicator(
56+
message: state.error?.message(context) ??
57+
const LocalizedUnknownException().message(context),
58+
onRetry: () => context
59+
.read<WorkspaceBloc>()
60+
.add(const WatchUserProposalsEvent()),
61+
),
62+
),
63+
);
64+
},
65+
);
66+
}
67+
}
68+
69+
class _Header extends StatelessWidget {
70+
final String title;
71+
72+
const _Header({
73+
required this.title,
74+
});
75+
76+
@override
77+
Widget build(BuildContext context) {
78+
return Padding(
79+
padding: const EdgeInsets.only(
80+
left: 20,
81+
top: 18,
82+
bottom: 18,
83+
),
84+
child: Text(
85+
title,
86+
style: context.textTheme.titleMedium?.copyWith(
87+
color: context.colors.textOnPrimaryLevel1,
88+
),
89+
),
90+
);
91+
}
92+
}
93+
94+
class _LoadingProposalSelector extends StatelessWidget {
95+
const _LoadingProposalSelector();
96+
97+
@override
98+
Widget build(BuildContext context) {
99+
return BlocSelector<WorkspaceBloc, WorkspaceState, bool>(
100+
selector: (state) {
101+
return state.isLoading;
102+
},
103+
builder: (context, isLoading) => Offstage(
104+
offstage: !isLoading,
105+
child: Padding(
106+
padding: const EdgeInsets.only(top: 60),
107+
child: Center(
108+
child: TickerMode(
109+
enabled: isLoading,
110+
child: const VoicesCircularProgressIndicator(),
111+
),
112+
),
113+
),
114+
),
115+
);
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
part of 'user_proposal_selectors.dart';
2+
3+
class WorkspaceOverviewProposalSelector extends StatelessWidget {
4+
const WorkspaceOverviewProposalSelector({super.key});
5+
6+
@override
7+
Widget build(BuildContext context) {
8+
return const Stack(
9+
children: [
10+
_LoadingProposalSelector(),
11+
_ErrorProposalSelector(),
12+
_WorkspaceDataProposalSelector(),
13+
],
14+
);
15+
}
16+
}
17+
18+
class _WorkspaceDataProposalSelector extends StatelessWidget {
19+
const _WorkspaceDataProposalSelector();
20+
21+
@override
22+
Widget build(BuildContext context) {
23+
return Column(
24+
mainAxisSize: MainAxisSize.min,
25+
crossAxisAlignment: CrossAxisAlignment.start,
26+
children: [
27+
_Header(title: context.l10n.notPublishedProposals),
28+
BlocSelector<WorkspaceBloc, WorkspaceState,
29+
DataVisibilityState<List<Proposal>>>(
30+
selector: (state) {
31+
return (data: state.notPublished, show: state.showProposals);
32+
},
33+
builder: (context, state) {
34+
return Offstage(
35+
offstage: !state.show,
36+
child: _DataProposalWidget(
37+
proposals: state.data,
38+
emptyMessage: context.l10n.noProposalsToPublish,
39+
showLatestLocal: true,
40+
),
41+
);
42+
},
43+
),
44+
],
45+
);
46+
}
47+
}

0 commit comments

Comments
 (0)