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

chore(cat-voices): Using TimezoneText in ProposalCard #1711

Merged
merged 13 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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 @@ -41,13 +41,44 @@ abstract class DateFormatter {
return l10n.inXDays(days);
}

static (String date, String time) formatDateTimeParts(
DateTime date,
) {
final dayMonthFormatter = DateFormat('d MMMM').format(date);
/// Formats a given DateTime into separate date and time strings.
///
/// The method formats the date part in either 'd MMMM yyyy' or
/// 'd MMMM' format depending on the value of [includeYear].
/// The time part is always formatted in 'HH:mm' format.
///
/// Parameters:
/// - [date]: The DateTime object to format.
/// - [includeYear]: A boolean indicating whether the year should be included
/// in the formatted date string. Defaults to `false`.
///
/// Returns a map with:
/// - `date`: The formatted date string.
/// - `time`: The formatted time string.
///
/// Examples:
/// - For `DateTime(2023, 10, 14, 9, 30)` with `includeYear = false`:
/// - date: "14 October"
/// - time: "09:30"
/// - For `DateTime(2023, 10, 14, 9, 30)` with `includeYear = true`:
/// - date: "14 October 2023"
/// - time: "09:30"
/// - For `DateTime(2022, 2, 5, 15, 45)` with `includeYear = false`:
/// - date: "5 February"
/// - time: "15:45"
/// - For `DateTime(2022, 2, 5, 15, 45)` with `includeYear = true`:
/// - date: "5 February 2022"
/// - time: "15:45"
static ({String date, String time}) formatDateTimeParts(
DateTime date, {
bool includeYear = false,
}) {
final formatter =
includeYear ? DateFormat('d MMMM yyyy') : DateFormat('d MMMM');
final dayMonthFormatter = formatter.format(date);
final timeFormatter = DateFormat('HH:mm').format(date);

return (dayMonthFormatter, timeFormatter);
return (date: dayMonthFormatter, time: timeFormatter);
}

static String formatShortMonth(
Expand Down Expand Up @@ -119,4 +150,15 @@ abstract class DateFormatter {

return '';
}

/// Formats a given [DateTime] object into a string
/// with the format "d MMM HH:mm".
///
/// The format consists of:
/// - Day of the month as a number without leading zeros (e.g., 4)
/// - Abbreviated month name (e.g., Jan, Feb, Mar)
/// - 24-hour time format with hours and minutes (e.g., 14:30)
static String formatDayMonthTime(DateTime dateTime) {
return DateFormat('d MMM HH:mm').format(dateTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class _GuestVisitorBody extends StatelessWidget {
),
_CampaignCategories(
List.filled(
7,
6,
CampaignCategoryCardViewModel.dummy(),
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ class CampaignStageCard extends StatelessWidget {
final formattedDate =
DateFormatter.formatDateTimeParts(campaign.startDate);
return context.l10n
.campaignBeginsOn(formattedDate.$1, formattedDate.$2);
.campaignBeginsOn(formattedDate.date, formattedDate.time);
case CampaignStage.live:
final formattedDate =
DateFormatter.formatDateTimeParts(campaign.endDate);
return context.l10n.campaignEndsOn(formattedDate.$1, formattedDate.$2);
return context.l10n
.campaignEndsOn(formattedDate.date, formattedDate.time);
case CampaignStage.completed:
return '';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:catalyst_voices/common/ext/ext.dart';
import 'package:catalyst_voices/common/formatters/date_formatter.dart';
import 'package:catalyst_voices/widgets/text/day_month_time_text.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
Expand All @@ -9,7 +10,7 @@ import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';

/// Displays a proposal in pending state on a card.
class PendingProposalCard extends StatelessWidget {
class PendingProposalCard extends StatefulWidget {
final PendingProposal proposal;
final bool showStatus;
final bool showLastUpdate;
Expand All @@ -25,56 +26,124 @@ class PendingProposalCard extends StatelessWidget {
this.onFavoriteChanged,
});

@override
State<PendingProposalCard> createState() => _PendingProposalCardState();
}

class _PendingProposalCardState extends State<PendingProposalCard> {
late final WidgetStatesController _statesController;
late final _ProposalBorderColor border;
LynxLynxx marked this conversation as resolved.
Show resolved Hide resolved

@override
void initState() {
super.initState();
_statesController = WidgetStatesController();
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
border = _ProposalBorderColor(
publishStage: widget.proposal.publishStage,
colorScheme: context.colorScheme,
colors: context.colors,
);
LynxLynxx marked this conversation as resolved.
Show resolved Hide resolved
}

@override
void dispose() {
_statesController.dispose();
super.dispose();
}

bool isHovered = false;
damian-molinski marked this conversation as resolved.
Show resolved Hide resolved
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxWidth: 326),
decoration: BoxDecoration(
color: Theme.of(context).colors.elevationsOnSurfaceNeutralLv1White,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.all(16),
return Material(
color: Colors.transparent,
child: InkWell(
statesController: _statesController,
onTap: () {},
child: ValueListenableBuilder(
valueListenable: _statesController,
builder: (context, value, child) => Container(
constraints: const BoxConstraints(maxWidth: 326),
decoration: BoxDecoration(
color: context.colors.elevationsOnSurfaceNeutralLv1White,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: border.resolve(_statesController.value),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_Topbar(
showStatus: showStatus,
isFavorite: isFavorite,
onFavoriteChanged: onFavoriteChanged,
),
_Category(
category: proposal.category,
),
const SizedBox(height: 4),
_Title(text: proposal.title),
_Author(author: proposal.author),
_FundsAndDuration(
funds: proposal.fundsRequested,
duration: proposal.duration,
),
const SizedBox(height: 12),
_Description(text: proposal.description),
const SizedBox(height: 24),
_ProposalInfo(
proposalStage: proposal.publishStage,
version: proposal.version,
lastUpdate: proposal.lastUpdateDate,
commentsCount: proposal.commentsCount,
showLastUpdate: showLastUpdate,
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Topbar(
showStatus: widget.showStatus,
isFavorite: widget.isFavorite,
onFavoriteChanged: widget.onFavoriteChanged,
),
_Category(
category: widget.proposal.category,
),
const SizedBox(height: 4),
_Title(text: widget.proposal.title),
_Author(author: widget.proposal.author),
_FundsAndDuration(
funds: widget.proposal.fundsRequested,
duration: widget.proposal.duration,
),
const SizedBox(height: 12),
_Description(text: widget.proposal.description),
const SizedBox(height: 24),
_ProposalInfo(
proposalStage: widget.proposal.publishStage,
version: widget.proposal.version,
lastUpdate: widget.proposal.lastUpdateDate,
commentsCount: widget.proposal.commentsCount,
showLastUpdate: widget.showLastUpdate,
),
],
),
),
],
),
),
],
),
),
);
}
}

final class _ProposalBorderColor extends WidgetStateColor {
final ProposalPublish publishStage;
final VoicesColorScheme colors;
final ColorScheme colorScheme;

_ProposalBorderColor({
required this.publishStage,
required this.colors,
required this.colorScheme,
}) : super(colors.outlineBorder.value);

@override
Color resolve(Set<WidgetState> states) {
if (states.contains(WidgetState.hovered)) {
return switch (publishStage) {
ProposalPublish.draft => colorScheme.secondary,
ProposalPublish.published => colorScheme.primary,
};
}

return colors.elevationsOnSurfaceNeutralLv1White;
}
}

class _Topbar extends StatelessWidget {
final bool showStatus;
final bool isFavorite;
Expand Down Expand Up @@ -327,13 +396,10 @@ class _ProposalInfo extends StatelessWidget {
),
if (showLastUpdate) ...[
const SizedBox(width: 4),
Text(
DateFormatter.formatShortMonth(
context.l10n,
lastUpdate,
),
style: context.textTheme.labelLarge?.copyWith(
color: context.colors.textOnPrimaryLevel1,
VoicesPlainTooltip(
message: _tooltipMessage(context),
child: DayMonthTimeText(
dateTime: lastUpdate,
),
),
],
Expand All @@ -356,7 +422,13 @@ class _ProposalInfo extends StatelessWidget {
) {
return switch (proposalStage) {
ProposalPublish.draft => l10n.draft,
ProposalPublish.published => l10n.published,
ProposalPublish.published => l10n.finalProposal,
};
}

String _tooltipMessage(BuildContext context) {
final dt = DateFormatter.formatDateTimeParts(lastUpdate, includeYear: true);

return context.l10n.publishedOn(dt.date, dt.time);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'package:catalyst_voices/common/ext/ext.dart';
import 'package:flutter/material.dart';

class VoicesSlider extends StatelessWidget {
Expand All @@ -7,7 +6,6 @@ class VoicesSlider extends StatelessWidget {
final double max;
final int divisions;
final ValueChanged<double>? onChanged;
final SliderThemeData? sliderTheme;

const VoicesSlider({
super.key,
Expand All @@ -16,34 +14,16 @@ class VoicesSlider extends StatelessWidget {
this.max = 1,
this.divisions = 25,
this.onChanged,
this.sliderTheme,
});

@override
Widget build(BuildContext context) {
return SliderTheme(
data: sliderTheme ??
SliderThemeData(
trackHeight: 6,
activeTrackColor: Colors.white,
secondaryActiveTrackColor: Colors.tealAccent,
inactiveTrackColor: context.colors.onSurfaceNeutral016,
thumbColor: Colors.white,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 4,
elevation: 0,
pressedElevation: 0,
),
overlayColor: Colors.transparent,
inactiveTickMarkColor: Colors.transparent,
),
child: Slider(
value: value,
onChanged: onChanged,
min: min,
max: max,
divisions: divisions,
),
return Slider(
value: value,
onChanged: onChanged,
min: min,
max: max,
divisions: divisions,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
import 'package:catalyst_voices/common/formatters/date_formatter.dart';
import 'package:catalyst_voices/widgets/text/timezone_date_time_text.dart';
import 'package:flutter/material.dart';

class DayMonthTimeText extends StatelessWidget {
final DateTime dateTime;
final bool showTimezone;

const DayMonthTimeText({
super.key,
required this.dateTime,
this.showTimezone = false,
});

@override
Widget build(BuildContext context) {
return TimezoneDateTimeText(
dateTime,
formatter: (context, lastUpdate) {
return DateFormatter.formatDayMonthTime(lastUpdate);
},
style: context.textTheme.labelLarge?.copyWith(
color: context.colors.textOnPrimaryLevel1,
),
showTimezone: showTimezone,
);
}
}
Loading
Loading