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): setup campaign stage dates #1396

Merged
merged 6 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,44 @@ abstract class DateFormatter {
) {
return DateFormat.MMM().format(dateTime);
}

/// Formats full date and time.
/// If [timeOnNewline] is true then the time will be placed on a new line.
///
/// Example:
/// - Thu, 6 June 2024 10:00 am
static String formatFullDateTime(
DateTime dateTime, {
bool timeOnNewline = false,
}) {
final format =
timeOnNewline ? 'EEE, d MMMM yyyy\nh:mm a' : 'EEE, d MMMM yyyy h:mm a';
return DateFormat(format).format(dateTime);
}

/// Formats the timezone info extracted from the [dateTime].
///
/// Example:
/// - GMT+01:00 Central European Standard Time
static String formatTimezone(DateTime dateTime) {
final offset = _formatTimezoneOffset(dateTime.timeZoneOffset);
final timezone = dateTime.timeZoneName;
return 'GMT$offset $timezone';
}

static String _formatTimezoneOffset(Duration offset) {
if (offset.isNegative) {
return '-${_formatDurationHHmm(offset)}';
} else {
return '+${_formatDurationHHmm(offset)}';
}
}

static String _formatDurationHHmm(Duration offset) {
final nf = NumberFormat('00');
final hours = offset.inHours;
final minutes = offset.inMinutes - hours * Duration.minutesPerHour;

return '${nf.format(hours)}:${nf.format(minutes)}';
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

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';
Expand All @@ -24,32 +26,40 @@ class _CampaignManagementState extends State<CampaignManagement> {

@override
Widget build(BuildContext context) {
final currentStatus = context.watch<CampaignBuilderCubit>().campaignStatus;
return Row(
children: [
VoicesOutlinedButton(
child: Text(context.l10n.campaignManagement),
onTap: () async {
final result =
await CampaignManagementDialog.show(context, currentStatus);
_handleDialogResult(result);
},
),
_CampaignStatusIndicator(
campaignStatus: CampaignPublish.draft,
currentStatus: currentStatus,
),
_CampaignStatusIndicator(
campaignStatus: CampaignPublish.published,
currentStatus: currentStatus,
),
],
return BlocSelector<CampaignBuilderCubit, CampaignBuilderState,
CampaignPublish?>(
selector: (state) => state.publish,
builder: (context, publish) {
return Row(
children: [
VoicesOutlinedButton(
child: Text(context.l10n.campaignManagement),
onTap: () => unawaited(_showManagementDialog(publish)),
),
_CampaignStatusIndicator(
campaignStatus: CampaignPublish.draft,
currentStatus: publish,
),
_CampaignStatusIndicator(
campaignStatus: CampaignPublish.published,
currentStatus: publish,
),
],
);
},
);
}

void _handleDialogResult(CampaignPublish? newStatus) {
if (newStatus == null) return;
context.read<CampaignBuilderCubit>().updateCampaignStatus(newStatus);
Future<void> _showManagementDialog(CampaignPublish? publish) async {
final result = await CampaignManagementDialog.show(context, publish);
if (mounted) {
_handleDialogResult(result);
}
}

void _handleDialogResult(CampaignPublish? newPublish) {
if (newPublish == null) return;
context.read<CampaignBuilderCubit>().updateCampaignPublish(newPublish);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ class CampaignManagementDialog extends StatefulWidget {
}

class _CampaignManagementDialogState extends State<CampaignManagementDialog> {
late CampaignPublish _campaignSetup;
late CampaignPublish _campaignPublish;

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

@override
Expand All @@ -56,19 +56,19 @@ class _CampaignManagementDialogState extends State<CampaignManagementDialog> {
),
const SizedBox(height: 8),
_CampaignPublishSegmentButton(
value: _campaignSetup,
onChanged: (value) => _campaignSetup = value,
value: _campaignPublish,
onChanged: (value) => _campaignPublish = value,
),
const Spacer(),
Align(
alignment: Alignment.centerRight,
child: VoicesFilledButton(
child: Text(context.l10n.saveButtonText),
onTap: () {
Navigator.of(context).pop(_campaignSetup);
Navigator.of(context).pop(_campaignPublish);
context
.read<CampaignBuilderCubit>()
.updateCampaignStatus(_campaignSetup);
.updateCampaignPublish(_campaignPublish);
},
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:catalyst_voices/widgets/navigation/section_step_state_builder.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';

class TreasuryCampaignCategoriesStep extends StatelessWidget {
final TreasurySectionStep step;

const TreasuryCampaignCategoriesStep({
super.key,
required this.step,
});

@override
Widget build(BuildContext context) {
return SectionStepStateBuilder(
id: step.sectionStepId,
builder: (context, value, child) {
return WorkspaceTextTileContainer(
name: step.localizedDesc(context),
isSelected: value.isSelected,
headerActions: [
VoicesTextButton(
onTap: step.isEditable ? () {} : null,
child: Text(context.l10n.stepEdit),
),
],
content: step.localizedDesc(context),
);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';

class TreasuryDummyTopicStep extends StatelessWidget {
final DummyTopicStep step;
class TreasuryCampaignDetailsStep extends StatelessWidget {
final TreasurySectionStep step;

const TreasuryDummyTopicStep({
const TreasuryCampaignDetailsStep({
super.key,
required this.step,
});
Expand All @@ -18,7 +18,7 @@ class TreasuryDummyTopicStep extends StatelessWidget {
id: step.sectionStepId,
builder: (context, value, child) {
return WorkspaceTextTileContainer(
name: step.localizedName(context),
name: step.localizedDesc(context),
isSelected: value.isSelected,
headerActions: [
VoicesTextButton(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import 'package:catalyst_voices/pages/treasury/steps/treasury_campaign_widgets.dart';
import 'package:catalyst_voices/widgets/navigation/section_step_state_builder.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class TreasuryCampaignStagesEditStep extends StatelessWidget {
final TreasurySectionStep step;

const TreasuryCampaignStagesEditStep({
super.key,
required this.step,
});

@override
Widget build(BuildContext context) {
return SectionStepStateBuilder(
id: step.sectionStepId,
builder: (context, value, child) {
return WorkspaceTileContainer(
isSelected: value.isSelected,
content: Column(
children: [
TreasuryCampaignStepHeader(step: step),
const SizedBox(height: 12),
const TreasuryCampaignTimezone(),
const SizedBox(height: 24),
_DateRange(step: step),
],
),
);
},
);
}
}

class _DateRange extends StatefulWidget {
final TreasurySectionStep step;

const _DateRange({required this.step});

@override
State<_DateRange> createState() => _DateRangeState();
}

class _DateRangeState extends State<_DateRange> {
late final VoicesDateTimeFieldController _startDateController;
late final VoicesDateTimeFieldController _endDateController;

@override
void initState() {
super.initState();

final cubit = context.read<CampaignBuilderCubit>();
_startDateController = VoicesDateTimeFieldController(cubit.state.startDate);
_endDateController = VoicesDateTimeFieldController(cubit.state.endDate);
}

@override
void dispose() {
_startDateController.dispose();
_endDateController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SegmentHeader(
name: context.l10n.campaignDates,
padding: EdgeInsets.zero,
),
_EditDate(
controller: _startDateController,
label: context.l10n.campaignStart,
onEdit: _onEditStartDate,
),
const SizedBox(height: 32),
_EditDate(
controller: _endDateController,
label: context.l10n.campaignEnd,
onEdit: _onEditEndDate,
),
const SizedBox(height: 36),
Align(
alignment: Alignment.topRight,
child: VoicesFilledButton(
child: Text(context.l10n.saveButtonText),
onTap: () {
context.read<CampaignBuilderCubit>().updateCampaignDates(
startDate: _startDateController.value,
endDate: _endDateController.value,
);

SectionsControllerScope.of(context).editStep(
widget.step.sectionStepId,
enabled: false,
);
},
),
),
const SizedBox(height: 16),
],
),
);
}

// ignore: use_setters_to_change_properties
void _onEditStartDate(DateTime? startDate) {
_startDateController.value = startDate;
}

// ignore: use_setters_to_change_properties
void _onEditEndDate(DateTime? endDate) {
_endDateController.value = endDate;
}
}

class _EditDate extends StatelessWidget {
final String label;
final VoicesDateTimeFieldController controller;
final ValueChanged<DateTime?> onEdit;

const _EditDate({
required this.controller,
required this.label,
required this.onEdit,
});

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

return Row(
children: [
Flexible(
child: SizedBox(
width: 220,
child: Text(
label,
style: theme.textTheme.titleSmall,
),
),
),
Flexible(
child: VoicesDateTimeField(
controller: controller,
onChanged: onEdit,
onFieldSubmitted: onEdit,
),
),
],
);
}
}
Loading
Loading