Skip to content

Commit c186759

Browse files
authored
feat(cat-voices): Campaign managment status UI (#1314)
* feat: campaign managment status * fix: structure of the project * fix: earthfile * docs: adding docs for campaign enums * fix: whitespacing
1 parent 0e903a2 commit c186759

File tree

22 files changed

+480
-75
lines changed

22 files changed

+480
-75
lines changed

catalyst_voices/apps/voices/lib/app/view/app.dart

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class _AppState extends State<App> {
5050
BlocProvider<ProposalsCubit>(
5151
create: (_) => Dependencies.instance.get<ProposalsCubit>(),
5252
),
53+
BlocProvider<CampaignBuilderCubit>(
54+
create: (_) => Dependencies.instance.get<CampaignBuilderCubit>(),
55+
),
5356
];
5457
}
5558
}

catalyst_voices/apps/voices/lib/dependency/dependencies.dart

+5-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ final class Dependencies extends DependencyProvider {
5757
return CampaignDetailsBloc(
5858
get<CampaignRepository>(),
5959
);
60-
});
60+
})
61+
// TODO(ryszard-schossler): add repository for campaign management
62+
..registerLazySingleton<CampaignBuilderCubit>(
63+
CampaignBuilderCubit.new,
64+
);
6165
}
6266

6367
void _registerRepositories() {

catalyst_voices/apps/voices/lib/pages/campaign/admin_tools/campaign_admin_tools_events.dart

+16-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:catalyst_voices/widgets/buttons/voices_text_button.dart';
33
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
44
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
55
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
6-
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
6+
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
77
import 'package:flutter/material.dart';
88

99
/// The "events" tab of the [CampaignAdminToolsDialog].
@@ -31,13 +31,14 @@ class _CampaignStatusChooser extends StatelessWidget {
3131
child: Column(
3232
children: [
3333
const SizedBox(height: 8),
34-
for (final status in CampaignStatus.values)
35-
// TODO(dtscalac): store active one somewhere
36-
_EventItem(
37-
status: status,
38-
isActive: status == CampaignStatus.draft,
39-
onTap: () {},
40-
),
34+
for (final status in CampaignStage.values)
35+
if (status != CampaignStage.scheduled)
36+
// TODO(dtscalac): store active one somewhere
37+
_EventItem(
38+
status: status,
39+
isActive: status == CampaignStage.draft,
40+
onTap: () {},
41+
),
4142
const SizedBox(height: 8),
4243
],
4344
),
@@ -46,7 +47,7 @@ class _CampaignStatusChooser extends StatelessWidget {
4647
}
4748

4849
class _EventItem extends StatelessWidget {
49-
final CampaignStatus status;
50+
final CampaignStage status;
5051
final bool isActive;
5152
final VoidCallback onTap;
5253

@@ -97,15 +98,15 @@ class _EventItem extends StatelessWidget {
9798
}
9899

99100
SvgGenImage get _icon => switch (status) {
100-
CampaignStatus.draft => VoicesAssets.icons.clock,
101-
CampaignStatus.live => VoicesAssets.icons.flag,
102-
CampaignStatus.completed => VoicesAssets.icons.calendar,
101+
CampaignStage.draft => VoicesAssets.icons.clock,
102+
CampaignStage.live => VoicesAssets.icons.flag,
103+
_ => VoicesAssets.icons.calendar,
103104
};
104105

105106
String _text(VoicesLocalizations l10n) => switch (status) {
106-
CampaignStatus.draft => l10n.campaignPreviewEventBefore,
107-
CampaignStatus.live => l10n.campaignPreviewEventDuring,
108-
CampaignStatus.completed => l10n.campaignPreviewEventAfter,
107+
CampaignStage.draft => l10n.campaignPreviewEventBefore,
108+
CampaignStage.live => l10n.campaignPreviewEventDuring,
109+
_ => l10n.campaignPreviewEventAfter,
109110
};
110111
}
111112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import 'package:catalyst_voices/pages/campaign/details/widgets/campaign_management_dialog.dart';
2+
import 'package:catalyst_voices/widgets/widgets.dart';
3+
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
4+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
5+
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
6+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
7+
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
8+
import 'package:flutter/material.dart';
9+
import 'package:flutter_bloc/flutter_bloc.dart';
10+
11+
class CampaignManagement extends StatefulWidget {
12+
const CampaignManagement({super.key});
13+
14+
@override
15+
State<CampaignManagement> createState() => _CampaignManagementState();
16+
}
17+
18+
class _CampaignManagementState extends State<CampaignManagement> {
19+
@override
20+
void initState() {
21+
super.initState();
22+
context.read<CampaignBuilderCubit>().getCampaignStatus();
23+
}
24+
25+
@override
26+
Widget build(BuildContext context) {
27+
final currentStatus = context.watch<CampaignBuilderCubit>().campaignStatus;
28+
return Row(
29+
children: [
30+
VoicesOutlinedButton(
31+
child: Text(context.l10n.campaignManagement),
32+
onTap: () async {
33+
final result =
34+
await CampaignManagementDialog.show(context, currentStatus);
35+
_handleDialogResult(result);
36+
},
37+
),
38+
_CampaignStatusIndicator(
39+
campaignStatus: CampaignPublish.draft,
40+
currentStatus: currentStatus,
41+
),
42+
_CampaignStatusIndicator(
43+
campaignStatus: CampaignPublish.published,
44+
currentStatus: currentStatus,
45+
),
46+
],
47+
);
48+
}
49+
50+
void _handleDialogResult(CampaignPublish? newStatus) {
51+
if (newStatus == null) return;
52+
context.read<CampaignBuilderCubit>().updateCampaignStatus(newStatus);
53+
}
54+
}
55+
56+
class _CampaignStatusIndicator extends StatelessWidget {
57+
final CampaignPublish campaignStatus;
58+
final CampaignPublish? currentStatus;
59+
60+
const _CampaignStatusIndicator({
61+
required this.campaignStatus,
62+
required this.currentStatus,
63+
});
64+
65+
@override
66+
Widget build(BuildContext context) {
67+
final theme = Theme.of(context);
68+
69+
return Padding(
70+
padding: const EdgeInsets.symmetric(horizontal: 4),
71+
child: DecoratedBox(
72+
decoration: BoxDecoration(
73+
color: currentStatus == campaignStatus
74+
? theme.colors.success
75+
: theme.colors.onSurfaceNeutral012?.withOpacity(.12),
76+
borderRadius: BorderRadius.circular(8),
77+
),
78+
child: Padding(
79+
padding: const EdgeInsets.fromLTRB(8, 8, 16, 8),
80+
child: Row(
81+
children: [
82+
VoicesAssets.icons.check.buildIcon(
83+
color: currentStatus == campaignStatus
84+
? theme.colors.successContainer
85+
: theme.colors.onSurfaceNeutral012,
86+
),
87+
const SizedBox(width: 8),
88+
switch (campaignStatus) {
89+
CampaignPublish.draft => _Text(
90+
context.l10n.draft,
91+
isSelected: campaignStatus == currentStatus,
92+
),
93+
CampaignPublish.published => _Text(
94+
context.l10n.published,
95+
isSelected: campaignStatus == currentStatus,
96+
),
97+
},
98+
],
99+
),
100+
),
101+
),
102+
);
103+
}
104+
}
105+
106+
class _Text extends StatelessWidget {
107+
final String text;
108+
final bool isSelected;
109+
110+
const _Text(
111+
this.text, {
112+
required this.isSelected,
113+
});
114+
115+
@override
116+
Widget build(BuildContext context) {
117+
final theme = Theme.of(context);
118+
return Text(
119+
text,
120+
style: Theme.of(context).textTheme.labelLarge?.copyWith(
121+
color: isSelected
122+
? theme.colors.successContainer
123+
: theme.colors.onSurfaceNeutral012,
124+
),
125+
);
126+
}
127+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import 'package:catalyst_voices/widgets/modals/details/voices_align_title_header.dart';
2+
import 'package:catalyst_voices/widgets/widgets.dart';
3+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
4+
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
5+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
6+
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
7+
import 'package:flutter/material.dart';
8+
import 'package:flutter_bloc/flutter_bloc.dart';
9+
10+
class CampaignManagementDialog extends StatefulWidget {
11+
final CampaignPublish? initialValue;
12+
const CampaignManagementDialog._(this.initialValue);
13+
14+
static Future<CampaignPublish?> show(
15+
BuildContext context,
16+
CampaignPublish? initialValue,
17+
) async {
18+
final result = await VoicesDialog.show<CampaignPublish?>(
19+
context: context,
20+
builder: (context) => CampaignManagementDialog._(initialValue),
21+
);
22+
23+
return result;
24+
}
25+
26+
@override
27+
State<StatefulWidget> createState() => _CampaignManagementDialogState();
28+
}
29+
30+
class _CampaignManagementDialogState extends State<CampaignManagementDialog> {
31+
late CampaignPublish _campaignSetup;
32+
33+
@override
34+
void initState() {
35+
super.initState();
36+
_campaignSetup = widget.initialValue ?? CampaignPublish.draft;
37+
}
38+
39+
@override
40+
Widget build(BuildContext context) {
41+
return VoicesDetailsDialog(
42+
constraints: const BoxConstraints(maxWidth: 750, maxHeight: 270),
43+
backgroundColor: Theme.of(context).colors.elevationsOnSurfaceNeutralLv0,
44+
header: VoicesAlignTitleHeader(
45+
title: context.l10n.campaignManagement,
46+
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
47+
),
48+
body: Padding(
49+
padding: const EdgeInsets.fromLTRB(24, 12, 24, 24),
50+
child: Column(
51+
crossAxisAlignment: CrossAxisAlignment.start,
52+
children: [
53+
Text(
54+
context.l10n.status,
55+
style: Theme.of(context).textTheme.titleSmall,
56+
),
57+
const SizedBox(height: 8),
58+
_CampaignPublishSegmentButton(
59+
value: _campaignSetup,
60+
onChanged: (value) => _campaignSetup = value,
61+
),
62+
const Spacer(),
63+
Align(
64+
alignment: Alignment.centerRight,
65+
child: VoicesFilledButton(
66+
child: Text(context.l10n.saveButtonText),
67+
onTap: () {
68+
Navigator.of(context).pop(_campaignSetup);
69+
context
70+
.read<CampaignBuilderCubit>()
71+
.updateCampaignStatus(_campaignSetup);
72+
},
73+
),
74+
),
75+
],
76+
),
77+
),
78+
);
79+
}
80+
}
81+
82+
class _CampaignPublishSegmentButton extends StatefulWidget {
83+
final CampaignPublish value;
84+
final ValueChanged<CampaignPublish> onChanged;
85+
86+
const _CampaignPublishSegmentButton({
87+
required this.value,
88+
required this.onChanged,
89+
});
90+
91+
@override
92+
State<_CampaignPublishSegmentButton> createState() => _SingleChoiceState();
93+
}
94+
95+
class _SingleChoiceState extends State<_CampaignPublishSegmentButton> {
96+
late CampaignPublish _segmentValue;
97+
98+
@override
99+
void initState() {
100+
super.initState();
101+
_segmentValue = widget.value;
102+
}
103+
104+
@override
105+
Widget build(BuildContext context) {
106+
return VoicesSegmentedButton<CampaignPublish>(
107+
showSelectedIcon: false,
108+
segments: <ButtonSegment<CampaignPublish>>[
109+
ButtonSegment<CampaignPublish>(
110+
value: CampaignPublish.draft,
111+
label: Text(context.l10n.draft),
112+
),
113+
ButtonSegment<CampaignPublish>(
114+
value: CampaignPublish.published,
115+
label: Text(context.l10n.published),
116+
),
117+
],
118+
selected: <CampaignPublish>{_segmentValue},
119+
onChanged: (Set<CampaignPublish> newSelection) {
120+
setState(() {
121+
_segmentValue = newSelection.first;
122+
});
123+
widget.onChanged(_segmentValue);
124+
},
125+
);
126+
}
127+
}

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

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:catalyst_voices/common/ext/ext.dart';
22
import 'package:catalyst_voices/pages/campaign/admin_tools/campaign_admin_tools_dialog.dart';
3+
import 'package:catalyst_voices/pages/campaign/details/widgets/campaign_management.dart';
34
import 'package:catalyst_voices/pages/spaces/appbar/spaces_theme_mode_switch.dart';
45
import 'package:catalyst_voices/pages/spaces/drawer/spaces_drawer.dart';
56
import 'package:catalyst_voices/widgets/widgets.dart';
@@ -68,11 +69,7 @@ class _SpacesShellPageState extends State<SpacesShellPage> {
6869
appBar: VoicesAppBar(
6970
leading: isVisitor ? null : const DrawerToggleButton(),
7071
automaticallyImplyLeading: false,
71-
actions: const [
72-
SpacesThemeModeSwitch(),
73-
SessionActionHeader(),
74-
SessionStateHeader(),
75-
],
72+
actions: _getActions(widget.space),
7673
),
7774
drawer: isVisitor
7875
? null
@@ -96,6 +93,21 @@ class _SpacesShellPageState extends State<SpacesShellPage> {
9693
);
9794
}
9895

96+
List<Widget> _getActions(Space space) {
97+
if (space == Space.treasury) {
98+
return [
99+
const CampaignManagement(),
100+
const SpacesThemeModeSwitch(),
101+
];
102+
} else {
103+
return [
104+
const SpacesThemeModeSwitch(),
105+
const SessionActionHeader(),
106+
const SessionStateHeader(),
107+
];
108+
}
109+
}
110+
99111
void _toggleCampaignAdminTools() {
100112
setState(() {
101113
_showAdminTools = !_showAdminTools;

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'package:catalyst_voices/widgets/widgets.dart';
33
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
44
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
55
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
6-
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
76
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
87
import 'package:flutter/material.dart';
98

@@ -76,7 +75,7 @@ class CampaignPreviewCard extends StatelessWidget {
7675
}
7776

7877
String _getButtonText(BuildContext context) {
79-
if (campaign.stage == CampaignStatus.live) {
78+
if (campaign.stage == CampaignStage.live) {
8079
return context.l10n.viewProposals;
8180
} else {
8281
return context.l10n.viewVotingResults;

0 commit comments

Comments
 (0)