Skip to content

Commit

Permalink
feat(cat-voices): Add roles chooser panel widget (#916)
Browse files Browse the repository at this point in the history
* feat: initial commit

* feat: preview card with state

* fix: icon

* feat: roles chooser panel

* feat: localization

* refactor: move l10n items

* feat: role images

* feat: adjusting learn more button

* refactor: move value

* docs: add initial docs

* docs: fix minor

* feat: view only

* feat: role summary panel

* chore: remove unused imports

* fix: cspell

* refactor: rename example category to role panels

* refactor: minor example

* fix: selection

* docs: minor fix

* refactor: organizing widgets

* refactor: change map to set for selection

* refactor: leaner

* refactor: rename from panel to container instead

* refactor: rename url example

* docs: fix data change

* refactor: roles mapping

* feat: role extension icon

* chore: remove spacebetween

* docs: minor fix

* fix: l10n

* chore: lintfix

---------

Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com>
  • Loading branch information
apskhem and dtscalac authored Oct 2, 2024
1 parent 860abd3 commit 3ab0739
Show file tree
Hide file tree
Showing 24 changed files with 576 additions and 3 deletions.
7 changes: 7 additions & 0 deletions catalyst_voices/lib/common/ext/account_role_ext.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:flutter/material.dart';
Expand All @@ -13,4 +14,10 @@ extension AccountRoleExt on AccountRole {
return context.l10n.drep;
}
}

String get icon => switch (this) {
AccountRole.voter => VoicesAssets.images.roleVoter.path,
AccountRole.proposer => VoicesAssets.images.roleProposer.path,
AccountRole.drep => VoicesAssets.images.roleDrep.path,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class VoicesSegmentedButton<T extends Object> extends StatelessWidget {
/// Should insert leading check icon into selected [segments].
final bool showSelectedIcon;

/// Customizes this button's appearance.
final ButtonStyle? style;

/// Default constructor.
const VoicesSegmentedButton({
super.key,
Expand All @@ -63,6 +66,7 @@ class VoicesSegmentedButton<T extends Object> extends StatelessWidget {
this.multiSelectionEnabled = false,
this.emptySelectionAllowed = false,
this.showSelectedIcon = true,
this.style,
});

@override
Expand All @@ -74,6 +78,7 @@ class VoicesSegmentedButton<T extends Object> extends StatelessWidget {
multiSelectionEnabled: multiSelectionEnabled,
emptySelectionAllowed: emptySelectionAllowed,
showSelectedIcon: showSelectedIcon,
style: style,
);
}
}
249 changes: 249 additions & 0 deletions catalyst_voices/lib/widgets/cards/role_chooser_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import 'package:catalyst_voices/widgets/common/grayscale_filter.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';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';

/// A role chooser card, responsible as a building block
/// for any role selection, and available in both
/// interactive and view-only mode.
class RoleChooserCard extends StatelessWidget {
/// The current displaying value.
final bool value;

/// Needs to be a rasterized image.
final String imageUrl;

/// The text label displaying on the card.
final String label;

/// Locks the value and shows it as default, only the selected value appears.
final bool lockValueAsDefault;

/// Hides the "Learn More" link.
final bool isLearnMoreHidden;

/// Toggles view-only mode.
final bool isViewOnly;

/// A callback triggered when the role selection changes.
final ValueChanged<bool>? onChanged;

/// A callback triggered when the "Learn More" link is clicked.
final VoidCallback? onLearnMore;

const RoleChooserCard({
super.key,
required this.value,
required this.imageUrl,
required this.label,
this.lockValueAsDefault = false,
this.isLearnMoreHidden = false,
this.isViewOnly = false,
this.onChanged,
this.onLearnMore,
});

@override
Widget build(BuildContext context) {
return Container(
height: 100,
padding: EdgeInsets.all(isViewOnly ? 8 : 12),
decoration: isViewOnly
? null
: BoxDecoration(
border: Border.all(
color: Theme.of(context).colors.outlineBorderVariant!,
width: 1,
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Column(
children: [
GrayscaleFilter(
image: CatalystImage.asset(
imageUrl,
width: 70,
height: 70,
fit: BoxFit.cover,
),
),
],
),
const SizedBox(width: 12),
Expanded(
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
overflow: TextOverflow.ellipsis,
label,
style: Theme.of(context).textTheme.titleSmall,
),
),
if (!isLearnMoreHidden) ...[
const SizedBox(width: 10),
_LearnMoreText(
onTap: onLearnMore,
),
],
],
),
const SizedBox(height: 8),
Row(
children: [
if (isViewOnly)
_DisplayingValueAsChips(
value: value,
lockValueAsDefault: lockValueAsDefault,
)
else
_DisplayingValueAsSegmentedButton(
value: value,
lockValueAsDefault: lockValueAsDefault,
onChanged: onChanged,
),
],
),
],
),
),
],
),
);
}
}

class _LearnMoreText extends StatelessWidget {
final VoidCallback? onTap;

const _LearnMoreText({
this.onTap,
});

@override
Widget build(BuildContext context) {
return LinkText(
context.l10n.learnMore,
onTap: onTap,
style: Theme.of(context).textTheme.labelMedium,
underline: false,
);
}
}

class _DisplayingValueAsChips extends StatelessWidget {
final bool value;
final bool lockValueAsDefault;

const _DisplayingValueAsChips({
required this.value,
required this.lockValueAsDefault,
});

@override
Widget build(BuildContext context) {
return Wrap(
spacing: 5,
runSpacing: 5,
children: [
VoicesChip.round(
content: Text(
value ? context.l10n.yes : context.l10n.no,
style: TextStyle(
color: value
? Theme.of(context).colors.successContainer
: Theme.of(context).colors.errorContainer,
),
),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
backgroundColor: value
? Theme.of(context).colors.success
: Theme.of(context).colors.iconsError,
),
if (lockValueAsDefault)
VoicesChip.round(
content: Text(
context.l10n.defaultRole,
style: TextStyle(
color: Theme.of(context).colors.iconsPrimary,
),
),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 4,
),
backgroundColor: Theme.of(context).colors.iconsForeground,
),
],
);
}
}

class _DisplayingValueAsSegmentedButton extends StatelessWidget {
final bool value;
final bool lockValueAsDefault;
final ValueChanged<bool>? onChanged;

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

@override
Widget build(BuildContext context) {
return Expanded(
child: VoicesSegmentedButton<bool>(
segments: lockValueAsDefault
? [
ButtonSegment(
value: value,
label: Text(
[
if (value) context.l10n.yes else context.l10n.no,
'(${context.l10n.defaultRole})',
].join(' '),
),
icon: Icon(
value ? Icons.check : Icons.block,
),
),
]
: [
ButtonSegment(
value: true,
label: Text(context.l10n.yes),
icon: value ? const Icon(Icons.check) : null,
),
ButtonSegment(
value: false,
label: Text(context.l10n.no),
icon: !value ? const Icon(Icons.block) : null,
),
],
style: SegmentedButton.styleFrom(
backgroundColor: Colors.transparent,
foregroundColor: Theme.of(context).colors.textOnPrimary,
selectedForegroundColor: value
? Theme.of(context).colors.successContainer
: Theme.of(context).colors.errorContainer,
selectedBackgroundColor: value
? Theme.of(context).colors.success
: Theme.of(context).colors.iconsError,
),
showSelectedIcon: false,
selected: {value},
onChanged: (selected) => onChanged?.call(selected.first),
),
);
}
}
43 changes: 43 additions & 0 deletions catalyst_voices/lib/widgets/common/grayscale_filter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';

/// A constant grayscale [ColorFilter] used to de-saturate an image.
const _grayscaleFilter = ColorFilter.matrix([
0.2126,
0.7152,
0.0722,
0,
0,
0.2126,
0.7152,
0.0722,
0,
0,
0.2126,
0.7152,
0.0722,
0,
0,
0,
0,
0,
1,
0,
]);

/// Applies grayscale filter to a widget.
class GrayscaleFilter extends StatelessWidget {
final Widget image;

const GrayscaleFilter({
super.key,
required this.image,
});

@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: _grayscaleFilter,
child: image,
);
}
}
10 changes: 7 additions & 3 deletions catalyst_voices/lib/widgets/common/link_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class LinkText extends StatelessWidget {
/// The text to be displayed.
final String data;

/// Displays the text with underline.
final bool underline;

/// An optional TextStyle to customize the appearance of the text.
final TextStyle? style;

Expand All @@ -21,6 +24,7 @@ class LinkText extends StatelessWidget {
const LinkText(
this.data, {
super.key,
this.underline = true,
this.style,
this.onTap,
});
Expand All @@ -32,9 +36,9 @@ class LinkText extends StatelessWidget {

final effectiveStyle = (style ?? const TextStyle()).copyWith(
color: color,
decoration: TextDecoration.underline,
decorationColor: color,
decorationStyle: TextDecorationStyle.solid,
decoration: underline ? TextDecoration.underline : null,
decorationColor: underline ? color : null,
decorationStyle: underline ? TextDecorationStyle.solid : null,
);

return GestureDetector(
Expand Down
Loading

0 comments on commit 3ab0739

Please sign in to comment.