Skip to content

Commit 04c04c4

Browse files
authored
feat(cat-voices): Overall spaces UI (#1958)
* refactor BrandsNavigation to improve layout; add new SpacesTile and OpportunitiesTile components; update localization strings * feat: add MyProposalsRoute and update tiles in Discovery and Workspace overview pages * feat: enhance Discovery and Workspace overviews with published proposals and small proposal cards functionality * feat: add localization strings for unpublished proposals and iterations in the workspace overview section * fix: update proposal metadata to use DraftRef for document in overall and workspace overview spaces * feat: define dark and light brand assets for the catalyst theme in the theme data configuration * fix: update proposal references to use new selfRef and versionCount attributes across various files * fix: remove redundant newline in _NotPublishedProposals * fix: remove unused GoRouter import and update navigation method in WorkspaceOverview; tidy up widget constructors with newlines
1 parent 3ff3fbe commit 04c04c4

File tree

17 files changed

+765
-174
lines changed

17 files changed

+765
-174
lines changed

catalyst_voices/apps/voices/lib/pages/overall_spaces/brands_navigation.dart

+62-52
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import 'package:catalyst_voices/common/ext/brand_ext.dart';
2+
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
3+
import 'package:catalyst_voices/widgets/common/affix_decorator.dart';
24
import 'package:catalyst_voices/widgets/widgets.dart';
35
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
46
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
@@ -20,6 +22,7 @@ class BrandsNavigation extends StatelessWidget {
2022
padding: const EdgeInsets.symmetric(vertical: 8),
2123
child: Column(
2224
mainAxisSize: MainAxisSize.max,
25+
crossAxisAlignment: CrossAxisAlignment.start,
2326
children: [
2427
...Brand.values.map(
2528
(brand) {
@@ -35,59 +38,26 @@ class BrandsNavigation extends StatelessWidget {
3538
indent: 0,
3639
endIndent: 0,
3740
),
38-
const _SearchTile(),
39-
const _TasksTile(),
41+
const _SpacesTile(),
42+
const _OpportunitiesTile(),
4043
],
4144
),
4245
);
4346
}
4447
}
4548

46-
class _BrandTile extends StatelessWidget {
47-
final Brand brand;
48-
final VoidCallback? onTap;
49-
50-
const _BrandTile(
51-
this.brand, {
52-
super.key,
53-
this.onTap,
54-
});
55-
56-
@override
57-
Widget build(BuildContext context) {
58-
final theme = Theme.of(context);
59-
final isCurrent = theme.brandAssets.brand == brand;
60-
61-
return _BrandsNavigationTile(
62-
onTap: onTap,
63-
isSelected: isCurrent,
64-
leading: brand.logoIcon(context).buildIcon(allowColorFilter: false),
65-
content: Text(brand.localizedName(context.l10n)),
66-
);
67-
}
68-
}
49+
final class _BackgroundColor implements WidgetStateProperty<Color?> {
50+
final VoicesColorScheme colors;
6951

70-
class _SearchTile extends StatelessWidget {
71-
const _SearchTile();
52+
_BackgroundColor(this.colors);
7253

7354
@override
74-
Widget build(BuildContext context) {
75-
return _BrandsNavigationTile(
76-
leading: VoicesAssets.icons.search.buildIcon(),
77-
content: Text(context.l10n.overallSpacesSearchBrands),
78-
);
79-
}
80-
}
81-
82-
class _TasksTile extends StatelessWidget {
83-
const _TasksTile();
55+
Color? resolve(Set<WidgetState> states) {
56+
if (states.contains(WidgetState.selected)) {
57+
return colors.onSurfacePrimaryContainer.withValues(alpha: 0.12);
58+
}
8459

85-
@override
86-
Widget build(BuildContext context) {
87-
return _BrandsNavigationTile(
88-
leading: VoicesAssets.icons.collection.buildIcon(),
89-
content: Text(context.l10n.overallSpacesTasks),
90-
);
60+
return Colors.transparent;
9161
}
9262
}
9363

@@ -158,18 +128,32 @@ class _BrandsNavigationTile extends StatelessWidget {
158128
}
159129
}
160130

161-
final class _BackgroundColor implements WidgetStateProperty<Color?> {
162-
final VoicesColorScheme colors;
131+
class _BrandTile extends StatelessWidget {
132+
final Brand brand;
133+
final VoidCallback? onTap;
163134

164-
_BackgroundColor(this.colors);
135+
const _BrandTile(
136+
this.brand, {
137+
super.key,
138+
this.onTap,
139+
});
165140

166141
@override
167-
Color? resolve(Set<WidgetState> states) {
168-
if (states.contains(WidgetState.selected)) {
169-
return colors.onSurfacePrimaryContainer.withValues(alpha: 0.12);
170-
}
171-
172-
return Colors.transparent;
142+
Widget build(BuildContext context) {
143+
return Padding(
144+
padding: const EdgeInsets.symmetric(
145+
horizontal: 12,
146+
vertical: 8,
147+
),
148+
child: AffixDecorator(
149+
prefix: brand.logoIcon(context).buildIcon(allowColorFilter: false),
150+
child: Text(
151+
brand.localizedName(context.l10n),
152+
style: context.textTheme.titleMedium
153+
?.copyWith(color: context.colorScheme.primary),
154+
),
155+
),
156+
);
173157
}
174158
}
175159

@@ -187,3 +171,29 @@ final class _ForegroundColor implements WidgetStateProperty<Color?> {
187171
return colors.textOnPrimaryLevel0;
188172
}
189173
}
174+
175+
class _OpportunitiesTile extends StatelessWidget {
176+
const _OpportunitiesTile();
177+
178+
@override
179+
Widget build(BuildContext context) {
180+
return _BrandsNavigationTile(
181+
leading: VoicesAssets.icons.collection.buildIcon(),
182+
content: Text(context.l10n.opportunities),
183+
);
184+
}
185+
}
186+
187+
class _SpacesTile extends StatelessWidget {
188+
const _SpacesTile();
189+
190+
@override
191+
Widget build(BuildContext context) {
192+
return _BrandsNavigationTile(
193+
isSelected: true,
194+
leading: VoicesAssets.icons.viewGrid.buildIcon(),
195+
content: Text(context.l10n.spaces),
196+
onTap: () {},
197+
);
198+
}
199+
}

catalyst_voices/apps/voices/lib/pages/overall_spaces/overall_spaces_page.dart

-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:catalyst_voices/pages/overall_spaces/back_fab.dart';
22
import 'package:catalyst_voices/pages/overall_spaces/brands_navigation.dart';
33
import 'package:catalyst_voices/pages/overall_spaces/spaces_overview_list_view.dart';
4-
import 'package:catalyst_voices/pages/overall_spaces/update_ready.dart';
54
import 'package:flutter/material.dart';
65

76
class OverallSpacesPage extends StatelessWidget {
@@ -38,8 +37,6 @@ class _Navigation extends StatelessWidget {
3837
children: [
3938
BrandsNavigation(),
4039
Spacer(),
41-
UpdateReady(),
42-
SizedBox(height: 20),
4340
Align(
4441
alignment: Alignment.centerLeft,
4542
child: BackFab(),
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import 'package:catalyst_voices/common/ext/space_ext.dart';
1+
import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
2+
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
23
import 'package:catalyst_voices/pages/overall_spaces/space/space_overview_header.dart';
34
import 'package:catalyst_voices/pages/overall_spaces/space/space_overview_nav_tile.dart';
45
import 'package:catalyst_voices/pages/overall_spaces/space_overview_container.dart';
6+
import 'package:catalyst_voices/routes/routes.dart';
7+
import 'package:catalyst_voices/widgets/cards/small_proposal_card.dart';
58
import 'package:catalyst_voices/widgets/widgets.dart';
69
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
10+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
11+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
712
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
813
import 'package:flutter/material.dart';
14+
import 'package:flutter_bloc/flutter_bloc.dart';
15+
import 'package:go_router/go_router.dart';
916

1017
class DiscoveryOverview extends StatelessWidget {
1118
const DiscoveryOverview({super.key});
@@ -17,11 +24,13 @@ class DiscoveryOverview extends StatelessWidget {
1724
children: [
1825
SpaceOverviewHeader(Space.discovery),
1926
_DiscoveryDashboardTile(),
20-
VoicesDivider(indent: 0, endIndent: 0, height: 16),
21-
_RolesTile(),
2227
_FeedbackTile(),
2328
VoicesDivider(indent: 0, endIndent: 0, height: 16),
24-
_DocumentationTile(),
29+
Expanded(
30+
child: SingleChildScrollView(
31+
child: _PublishedProposalSelector(1, 5),
32+
),
33+
),
2534
],
2635
),
2736
);
@@ -36,46 +45,144 @@ class _DiscoveryDashboardTile extends StatelessWidget {
3645
return SpaceOverviewNavTile(
3746
leading: VoicesAssets.icons.home.buildIcon(),
3847
title: Text(
39-
'Discovery Dashboard',
48+
context.l10n.discoveryHomepage,
4049
style: Theme.of(context).textTheme.bodyLarge,
4150
),
42-
backgroundColor: Space.discovery.backgroundColor(context),
51+
onTap: () => GoRouter.of(context).go(const DiscoveryRoute().location),
4352
);
4453
}
4554
}
4655

47-
class _RolesTile extends StatelessWidget {
48-
const _RolesTile();
56+
class _FeedbackTile extends StatelessWidget {
57+
const _FeedbackTile();
4958

5059
@override
5160
Widget build(BuildContext context) {
52-
return SpaceOverviewNavTile(
53-
leading: VoicesAssets.icons.user.buildIcon(),
54-
title: const Text('Catalyst Roles'),
61+
return BlocSelector<SessionCubit, SessionState, bool>(
62+
selector: (state) {
63+
return state.account?.isProposer ?? false;
64+
},
65+
builder: (context, state) {
66+
return Visibility.maintain(
67+
visible: state,
68+
child: SpaceOverviewNavTile(
69+
leading: VoicesAssets.icons.documentText.buildIcon(),
70+
title: Text(
71+
context.l10n.feedbackOnProposals,
72+
style: Theme.of(context).textTheme.bodyLarge,
73+
),
74+
onTap: state
75+
? () => GoRouter.of(context).go(const ProposalsRoute().location)
76+
: null,
77+
),
78+
);
79+
},
5580
);
5681
}
5782
}
5883

59-
class _FeedbackTile extends StatelessWidget {
60-
const _FeedbackTile();
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+
});
6191

6292
@override
6393
Widget build(BuildContext context) {
64-
return SpaceOverviewNavTile(
65-
leading: VoicesAssets.icons.annotation.buildIcon(),
66-
title: const Text('Feedback'),
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+
),
67108
);
68109
}
69110
}
70111

71-
class _DocumentationTile extends StatelessWidget {
72-
const _DocumentationTile();
112+
class _PublishedProposals extends StatelessWidget {
113+
final int currentProposals;
114+
final int maxProposals;
115+
const _PublishedProposals(
116+
this.currentProposals,
117+
this.maxProposals,
118+
);
73119

74120
@override
75121
Widget build(BuildContext context) {
76-
return SpaceOverviewNavTile(
77-
leading: VoicesAssets.icons.arrowRight.buildIcon(),
78-
title: const Text('Catalyst Gitbook documentation'),
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+
description: 'Lorem ipsum dolor sit ',
128+
fundsRequested: const Coin(100000),
129+
status: ProposalStatus.draft,
130+
publish: ProposalPublish.localDraft,
131+
commentsCount: 0,
132+
duration: 6,
133+
author: 'Alex Wells',
134+
updateDate: DateTime.now(),
135+
versionCount: 1,
136+
);
137+
return Column(
138+
mainAxisSize: MainAxisSize.min,
139+
crossAxisAlignment: CrossAxisAlignment.start,
140+
children: [
141+
_Header(
142+
maxProposals: maxProposals,
143+
currentProposals: currentProposals,
144+
),
145+
SmallProposalCard(
146+
proposal: proposal.copyWith(
147+
publish: ProposalPublish.submittedProposal,
148+
commentsCount: 1,
149+
),
150+
),
151+
const SizedBox(height: 12),
152+
SmallProposalCard(
153+
proposal: proposal.copyWith(
154+
publish: ProposalPublish.publishedDraft,
155+
commentsCount: 12,
156+
),
157+
),
158+
],
159+
);
160+
}
161+
}
162+
163+
class _PublishedProposalSelector extends StatelessWidget {
164+
final int currentProposals;
165+
final int maxProposals;
166+
const _PublishedProposalSelector(
167+
this.currentProposals,
168+
this.maxProposals,
169+
);
170+
171+
@override
172+
Widget build(BuildContext context) {
173+
return BlocSelector<SessionCubit, SessionState, bool>(
174+
selector: (state) {
175+
return !(state.account?.isProposer ?? false);
176+
},
177+
builder: (context, state) {
178+
return Offstage(
179+
offstage: state,
180+
child: _PublishedProposals(
181+
currentProposals,
182+
maxProposals,
183+
),
184+
);
185+
},
79186
);
80187
}
81188
}

0 commit comments

Comments
 (0)