Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cat-voices): Campaign managment status UI #1314

Merged
merged 6 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions catalyst_voices/apps/voices/lib/app/view/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class _AppState extends State<App> {
BlocProvider<ProposalsCubit>(
create: (_) => Dependencies.instance.get<ProposalsCubit>(),
),
BlocProvider<CampaignStatusCubit>(
create: (_) => Dependencies.instance.get<CampaignStatusCubit>(),
),
];
}
}
4 changes: 3 additions & 1 deletion catalyst_voices/apps/voices/lib/dependency/dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ final class Dependencies extends DependencyProvider {
return CampaignDetailsBloc(
get<CampaignRepository>(),
);
});
})
// TODO(ryszard-schossler): add repository for campaign status
..registerLazySingleton<CampaignStatusCubit>(CampaignStatusCubit.new);
}

void _registerRepositories() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'package:catalyst_voices/pages/campaign/details/widgets/campaign_management_dialog.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CampaignManagement extends StatefulWidget {
const CampaignManagement({super.key});

@override
State<CampaignManagement> createState() => _CampaignManagementState();
}

class _CampaignManagementState extends State<CampaignManagement> {
@override
void initState() {
super.initState();
context.read<CampaignStatusCubit>().getCampaignStatus();
}

@override
Widget build(BuildContext context) {
final currentStatus = context.watch<CampaignStatusCubit>().campaignStatus;
return Row(
children: [
VoicesOutlinedButton(
child: Text(context.l10n.campaignManagement),
onTap: () async {
final result =
await CampaignManagementDialog.show(context, currentStatus);
_handleDialogResult(result);
},
),
_CampaignStatusIndicator(
campaignStatus: CampaignStatus.draft,
currentStatus: currentStatus,
),
_CampaignStatusIndicator(
campaignStatus: CampaignStatus.published,
currentStatus: currentStatus,
),
],
);
}

void _handleDialogResult(CampaignStatus? newStatus) {
if (newStatus == null) return;

context.read<CampaignStatusCubit>().updateCampaignStatus(newStatus);
}
}

class _CampaignStatusIndicator extends StatelessWidget {
final CampaignStatus campaignStatus;
final CampaignStatus? currentStatus;

const _CampaignStatusIndicator({
required this.campaignStatus,
required this.currentStatus,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: DecoratedBox(
decoration: BoxDecoration(
color: currentStatus == campaignStatus
? theme.colors.success
: theme.colors.onSurfaceNeutral012?.withOpacity(.12),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 16, 8),
child: Row(
children: [
VoicesAssets.icons.check.buildIcon(
color: currentStatus == campaignStatus
? theme.colors.successContainer
: theme.colors.onSurfaceNeutral012,
),
const SizedBox(width: 8),
switch (campaignStatus) {
CampaignStatus.draft => _Text(
context.l10n.campaignDraftStatus,
isSelected: campaignStatus.isSelected(currentStatus),
),
CampaignStatus.published => _Text(
context.l10n.campaignPublishedStatus,
isSelected: campaignStatus.isSelected(currentStatus),
),
},
],
),
),
),
);
}
}

class _Text extends StatelessWidget {
final String text;
final bool isSelected;

const _Text(
this.text, {
required this.isSelected,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Text(
text,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: isSelected
? theme.colors.successContainer
: theme.colors.onSurfaceNeutral012,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'package:catalyst_voices/widgets/modals/details/voices_align_title_header.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CampaignManagementDialog extends StatefulWidget {
final CampaignStatus? initialValue;
const CampaignManagementDialog._(this.initialValue);

static Future<CampaignStatus?> show(
BuildContext context,
CampaignStatus? initialValue,
) async {
final result = await VoicesDialog.show<CampaignStatus?>(
context: context,
builder: (context) => CampaignManagementDialog._(initialValue),
);

return result;
}

@override
State<StatefulWidget> createState() => _CampaignManagementDialogState();
}

class _CampaignManagementDialogState extends State<CampaignManagementDialog> {
late CampaignStatus _campaignSetup;

@override
void initState() {
super.initState();
_campaignSetup = widget.initialValue ?? CampaignStatus.draft;
}

@override
Widget build(BuildContext context) {
return VoicesDetailsDialog(
constraints: const BoxConstraints(maxWidth: 750, maxHeight: 270),
backgroundColor: Theme.of(context).colors.elevationsOnSurfaceNeutralLv0,
header: VoicesAlignTitleHeader(
title: context.l10n.campaignManagement,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
),
body: Padding(
padding: const EdgeInsets.fromLTRB(24, 12, 24, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.status,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
_CampaignStatusSegmentButton(
value: _campaignSetup,
onChanged: (value) => _campaignSetup = value,
),
const Spacer(),
Align(
alignment: Alignment.centerRight,
child: VoicesFilledButton(
child: Text(context.l10n.saveButtonText),
onTap: () {
Navigator.of(context).pop(_campaignSetup);
context
.read<CampaignStatusCubit>()
.updateCampaignStatus(_campaignSetup);
},
),
),
],
),
),
);
}
}

class _CampaignStatusSegmentButton extends StatefulWidget {
final CampaignStatus value;
final ValueChanged<CampaignStatus> onChanged;

const _CampaignStatusSegmentButton({
required this.value,
required this.onChanged,
});

@override
State<_CampaignStatusSegmentButton> createState() => _SingleChoiceState();
}

class _SingleChoiceState extends State<_CampaignStatusSegmentButton> {
late CampaignStatus segmentValue;

@override
void initState() {
super.initState();
segmentValue = widget.value;
}

@override
Widget build(BuildContext context) {
return VoicesSegmentedButton<CampaignStatus>(
showSelectedIcon: false,
segments: <ButtonSegment<CampaignStatus>>[
ButtonSegment<CampaignStatus>(
value: CampaignStatus.draft,
label: Text(context.l10n.campaignDraftStatus),
),
ButtonSegment<CampaignStatus>(
value: CampaignStatus.published,
label: Text(context.l10n.campaignPublishedStatus),
),
],
selected: <CampaignStatus>{segmentValue},
onChanged: (Set<CampaignStatus> newSelection) {
setState(() {
segmentValue = newSelection.first;
});
widget.onChanged(segmentValue);
},
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:catalyst_voices/common/ext/ext.dart';
import 'package:catalyst_voices/pages/campaign/details/widgets/campaign_management.dart';
import 'package:catalyst_voices/pages/spaces/appbar/spaces_theme_mode_switch.dart';
import 'package:catalyst_voices/pages/spaces/drawer/spaces_drawer.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
Expand Down Expand Up @@ -61,11 +62,7 @@ class _SpacesShellPageState extends State<SpacesShellPage> {
appBar: VoicesAppBar(
leading: isVisitor ? null : const DrawerToggleButton(),
automaticallyImplyLeading: false,
actions: const [
SpacesThemeModeSwitch(),
SessionActionHeader(),
SessionStateHeader(),
],
actions: _getActions(widget.space),
),
drawer: isVisitor
? null
Expand All @@ -79,4 +76,19 @@ class _SpacesShellPageState extends State<SpacesShellPage> {
),
);
}

List<Widget> _getActions(Space space) {
if (space == Space.treasury) {
return [
const CampaignManagement(),
const SpacesThemeModeSwitch(),
];
} else {
return [
const SpacesThemeModeSwitch(),
const SessionActionHeader(),
const SessionStateHeader(),
];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:catalyst_voices/widgets/buttons/voices_buttons.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:flutter/material.dart';

class VoicesAlignTitleHeader extends StatelessWidget {
final String title;
final TextStyle? titleStyle;
final EdgeInsets? padding;

const VoicesAlignTitleHeader({
super.key,
required this.title,
this.padding,
this.titleStyle,
});

@override
Widget build(BuildContext context) {
return Padding(
padding: padding ?? const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: titleStyle ?? Theme.of(context).textTheme.titleMedium,
),
const _CloseButton(),
],
),
);
}
}

class _CloseButton extends StatelessWidget {
const _CloseButton();

@override
Widget build(BuildContext context) {
final colors = Theme.of(context).colors;

final style = IconButton.styleFrom(
backgroundColor: colors.iconsBackground,
foregroundColor: colors.iconsForeground,
);

return IconButtonTheme(
data: IconButtonThemeData(style: style),
child: XButton(
onTap: () async => Navigator.of(context).maybePop(),
),
);
}
}
Loading