Skip to content

Commit 696a0fc

Browse files
authored
feat(cat-voices): My Opportunities (#2085)
* feat: setting up UI * chore: leaving todos * fix: review * fix: review * fix: remove new line
1 parent 8bc22bf commit 696a0fc

File tree

9 files changed

+320
-5
lines changed

9 files changed

+320
-5
lines changed

catalyst_voices/apps/voices/lib/pages/spaces/appbar/account_popup/session_account_popup_menu.dart

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ class _Account extends StatelessWidget {
3838
MenuItemTile(
3939
leading: VoicesAssets.icons.userCircle.buildIcon(),
4040
key: const Key('ProfileAndKeychain'),
41-
title: Text(
42-
context.l10n.profileAndKeychain,
43-
),
41+
title: Text(context.l10n.profileAndKeychain),
4442
trailing: VoicesAssets.icons.chevronRight.buildIcon(),
4543
onTap: () => Navigator.pop(context, const _OpenAccountDetails()),
4644
),
@@ -119,10 +117,28 @@ sealed class _MenuItemEvent {
119117
const _MenuItemEvent();
120118
}
121119

120+
final class _MyOpportunities extends _MenuItemEvent {
121+
const _MyOpportunities();
122+
}
123+
122124
final class _OpenAccountDetails extends _MenuItemEvent {
123125
const _OpenAccountDetails();
124126
}
125127

128+
class _Opportunities extends StatelessWidget {
129+
const _Opportunities();
130+
131+
@override
132+
Widget build(BuildContext context) {
133+
return MenuItemTile(
134+
key: const Key('MyOpportunitiesMenuItem'),
135+
title: Text(context.l10n.myOpportunities),
136+
leading: VoicesAssets.icons.lightBulb.buildIcon(),
137+
onTap: () => Navigator.pop(context, const _MyOpportunities()),
138+
);
139+
}
140+
}
141+
126142
class _PopupMenu extends StatelessWidget {
127143
const _PopupMenu();
128144

@@ -140,6 +156,8 @@ class _PopupMenu extends StatelessWidget {
140156
children: [
141157
_AccountHeader(),
142158
VoicesDivider.expanded(),
159+
_Opportunities(),
160+
VoicesDivider.expanded(),
143161
_Account(),
144162
_Settings(),
145163
VoicesDivider.expanded(height: 17),
@@ -275,6 +293,8 @@ class _SessionAccountPopupMenuState extends State<SessionAccountPopupMenu>
275293
unawaited(launchUri(uri));
276294
case _Lock():
277295
unawaited(context.read<SessionCubit>().lock());
296+
case _MyOpportunities():
297+
Scaffold.maybeOf(context)?.openEndDrawer();
278298
}
279299
}
280300
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import 'dart:async';
2+
3+
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
4+
import 'package:catalyst_voices/widgets/buttons/copy_catalyst_id_button.dart';
5+
import 'package:catalyst_voices/widgets/snackbar/voices_snackbar.dart';
6+
import 'package:catalyst_voices/widgets/snackbar/voices_snackbar_type.dart';
7+
import 'package:catalyst_voices/widgets/widgets.dart';
8+
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
9+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
10+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
11+
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
12+
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
13+
import 'package:flutter/material.dart';
14+
import 'package:flutter/services.dart';
15+
import 'package:flutter_bloc/flutter_bloc.dart';
16+
17+
class OpportunitiesDrawer extends StatelessWidget {
18+
const OpportunitiesDrawer({super.key});
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return const VoicesDrawer(
23+
width: 488,
24+
shape: RoundedRectangleBorder(
25+
borderRadius: BorderRadius.only(
26+
topLeft: Radius.circular(16),
27+
bottomLeft: Radius.circular(16),
28+
),
29+
),
30+
child: Padding(
31+
padding: EdgeInsets.symmetric(
32+
horizontal: 32,
33+
vertical: 24,
34+
),
35+
child: Column(
36+
mainAxisSize: MainAxisSize.min,
37+
crossAxisAlignment: CrossAxisAlignment.start,
38+
children: [
39+
_Header(),
40+
SizedBox(height: 30),
41+
_Description(),
42+
SizedBox(height: 20),
43+
_BecomeReviewerCard(),
44+
SizedBox(height: 20),
45+
_RegisterAsVoter(),
46+
],
47+
),
48+
),
49+
);
50+
}
51+
}
52+
53+
class _BecomeReviewerCard extends StatelessWidget with LaunchUrlMixin {
54+
const _BecomeReviewerCard();
55+
56+
@override
57+
Widget build(BuildContext context) {
58+
return _OpportunityCard(
59+
background: VoicesAssets.images.opportunities.reviewer.path,
60+
child: Padding(
61+
padding: const EdgeInsets.only(
62+
top: 28,
63+
left: 24,
64+
),
65+
child: Column(
66+
mainAxisSize: MainAxisSize.min,
67+
crossAxisAlignment: CrossAxisAlignment.start,
68+
children: [
69+
SizedBox(
70+
width: 198,
71+
child: Text(
72+
context.l10n.turnOpinionsIntoActions,
73+
style: context.textTheme.headlineMedium?.copyWith(
74+
color: context.colorScheme.primary,
75+
),
76+
),
77+
),
78+
const SizedBox(height: 15),
79+
CopyCatalystIdButton(onTap: () => _handleCopyCatalystId(context)),
80+
const SizedBox(height: 4),
81+
_OpportunityActionButton(
82+
onTap: () {
83+
// TODO(LynxLynxx): add url;
84+
// launchUri();
85+
},
86+
title: context.l10n.becomeReviewer,
87+
trailing: VoicesAssets.icons.externalLink.buildIcon(),
88+
),
89+
],
90+
),
91+
),
92+
);
93+
}
94+
95+
void _copyToClipboard(CatalystId? text) {
96+
unawaited(Clipboard.setData(ClipboardData(text: text.toString())));
97+
}
98+
99+
void _handleCopyCatalystId(BuildContext context) {
100+
final catalystId = context.read<SessionCubit>().state.account?.catalystId;
101+
_copyToClipboard(catalystId);
102+
_showSuccessSnackbar(context);
103+
}
104+
105+
void _showSuccessSnackbar(BuildContext context) {
106+
VoicesSnackBar.hideCurrent(context);
107+
108+
VoicesSnackBar(
109+
type: VoicesSnackBarType.success,
110+
behavior: SnackBarBehavior.floating,
111+
message: context.l10n.copied,
112+
).show(context);
113+
}
114+
}
115+
116+
class _Description extends StatelessWidget {
117+
const _Description();
118+
119+
@override
120+
Widget build(BuildContext context) {
121+
return Text(
122+
context.l10n.newUpdates,
123+
style: context.textTheme.bodyLarge,
124+
);
125+
}
126+
}
127+
128+
class _Header extends StatelessWidget {
129+
const _Header();
130+
131+
@override
132+
Widget build(BuildContext context) {
133+
return Row(
134+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
135+
children: [
136+
Text(
137+
context.l10n.myOpportunities,
138+
style: context.textTheme.titleLarge,
139+
),
140+
CloseButton(
141+
onPressed: () => Navigator.maybeOf(context)?.pop(),
142+
),
143+
],
144+
);
145+
}
146+
}
147+
148+
class _OpportunityActionButton extends StatelessWidget {
149+
final VoidCallback? onTap;
150+
final String title;
151+
final Widget? trailing;
152+
153+
const _OpportunityActionButton({
154+
this.onTap,
155+
required this.title,
156+
this.trailing,
157+
});
158+
159+
@override
160+
Widget build(BuildContext context) {
161+
return VoicesFilledButton(
162+
onTap: onTap,
163+
trailing: trailing,
164+
child: Text(title),
165+
);
166+
}
167+
}
168+
169+
class _OpportunityCard extends StatelessWidget {
170+
final String background;
171+
final Widget child;
172+
173+
const _OpportunityCard({
174+
required this.background,
175+
required this.child,
176+
});
177+
178+
@override
179+
Widget build(BuildContext context) {
180+
return Container(
181+
constraints: BoxConstraints.tight(const Size(426, 260)),
182+
decoration: BoxDecoration(
183+
borderRadius: BorderRadius.circular(16),
184+
image: DecorationImage(
185+
image: CatalystImage.asset(background).image,
186+
fit: BoxFit.cover,
187+
),
188+
),
189+
child: child,
190+
);
191+
}
192+
}
193+
194+
class _RegisterAsVoter extends StatelessWidget with LaunchUrlMixin {
195+
const _RegisterAsVoter();
196+
197+
@override
198+
Widget build(BuildContext context) {
199+
return _OpportunityCard(
200+
background: VoicesAssets.images.opportunities.voter.path,
201+
child: Align(
202+
alignment: Alignment.topRight,
203+
child: Padding(
204+
padding: const EdgeInsets.only(
205+
top: 44,
206+
right: 24,
207+
),
208+
child: Column(
209+
mainAxisSize: MainAxisSize.min,
210+
crossAxisAlignment: CrossAxisAlignment.start,
211+
children: [
212+
SizedBox(
213+
width: 198,
214+
child: Text(
215+
context.l10n.f14Voting,
216+
style: context.textTheme.headlineMedium?.copyWith(
217+
color: context.colorScheme.primary,
218+
),
219+
),
220+
),
221+
const SizedBox(height: 20),
222+
_OpportunityActionButton(
223+
onTap: () {
224+
// TODO(LynxLynxx): add url;
225+
// launchUri();
226+
},
227+
title: context.l10n.votingRegistration,
228+
trailing: VoicesAssets.icons.externalLink.buildIcon(),
229+
),
230+
],
231+
),
232+
),
233+
),
234+
);
235+
}
236+
}

catalyst_voices/apps/voices/lib/pages/spaces/spaces_shell_page.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:catalyst_voices/pages/campaign/admin_tools/campaign_admin_tools_
55
import 'package:catalyst_voices/pages/campaign/details/widgets/campaign_management.dart';
66
import 'package:catalyst_voices/pages/spaces/appbar/session_action_header.dart';
77
import 'package:catalyst_voices/pages/spaces/appbar/session_state_header.dart';
8+
import 'package:catalyst_voices/pages/spaces/drawer/opportunities_drawer.dart';
89
import 'package:catalyst_voices/pages/spaces/drawer/spaces_drawer.dart';
910
import 'package:catalyst_voices/widgets/widgets.dart';
1011
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
@@ -122,6 +123,7 @@ class _SpacesShellPageState extends State<SpacesShellPage> {
122123
SpacesShellPage._spacesShortcutsActivators,
123124
isUnlocked: state.isUnlocked,
124125
),
126+
endDrawer: const OpportunitiesDrawer(),
125127
body: widget.child,
126128
);
127129
},
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'package:catalyst_voices/widgets/buttons/voices_text_button.dart';
2+
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
3+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
4+
import 'package:flutter/widgets.dart';
5+
6+
class CopyCatalystIdButton extends StatelessWidget {
7+
final VoidCallback? onTap;
8+
9+
const CopyCatalystIdButton({
10+
super.key,
11+
required this.onTap,
12+
});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return VoicesTextButton(
17+
onTap: onTap,
18+
leading: VoicesAssets.icons.duplicate.buildIcon(),
19+
child: Text(context.l10n.copyMyCatalystId),
20+
);
21+
}
22+
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@ class VoicesDrawer extends StatelessWidget {
2828
/// This widget is main "body" of [VoicesDrawer].
2929
final Widget child;
3030

31+
final ShapeBorder shape;
32+
33+
final double width;
34+
3135
/// The default constructor for the [VoicesDrawer].
3236
const VoicesDrawer({
3337
super.key,
3438
this.bottom,
3539
required this.child,
40+
this.shape = const RoundedRectangleBorder(),
41+
this.width = 360,
3642
});
3743

3844
@override
@@ -50,8 +56,8 @@ class VoicesDrawer extends StatelessWidget {
5056
),
5157
),
5258
child: Drawer(
53-
width: 360,
54-
shape: const RoundedRectangleBorder(),
59+
width: width,
60+
shape: shape,
5561
child: Column(
5662
children: [
5763
Expanded(child: child),
22.6 KB
Loading
28.8 KB
Loading

catalyst_voices/packages/internal/catalyst_voices_assets/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ flutter:
2525
assets:
2626
- assets/images/
2727
- assets/images/category/
28+
- assets/images/opportunities/
2829
- assets/icons/
2930
- assets/lottie/
3031
- assets/videos/

0 commit comments

Comments
 (0)