diff --git a/catalyst_voices/lib/widgets/snackbar/voices_snackbar.dart b/catalyst_voices/lib/widgets/snackbar/voices_snackbar.dart new file mode 100644 index 0000000000..2a19b1fbd7 --- /dev/null +++ b/catalyst_voices/lib/widgets/snackbar/voices_snackbar.dart @@ -0,0 +1,165 @@ +import 'package:catalyst_voices/widgets/snackbar/voices_snackbar_type.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'; + +/// [VoicesSnackBar] is a custom [SnackBar] widget that displays messages with +/// different types and actions. +/// +/// [VoicesSnackBar] comes with different types (info, success, warning, error) +/// and optional actions such as primary, secondary, and close buttons. +class VoicesSnackBar extends StatelessWidget { + /// The type of the [VoicesSnackBar], + /// which determines its appearance and behavior. + final VoicesSnackBarType type; + + /// Function to be executed when the primary action button is pressed. + final VoidCallback? onPrimaryPressed; + + /// Callback function to be executed when the secondary action button is + /// pressed. + final VoidCallback? onSecondaryPressed; + + /// Callback function to be executed when the close button is pressed. + final VoidCallback? onClosePressed; + + /// The behavior of the [VoicesSnackBar], which can be fixed or floating. + final SnackBarBehavior? behavior; + + /// The padding around the content of the [VoicesSnackBar]. + final EdgeInsetsGeometry? padding; + + /// The width of the [VoicesSnackBar]. + final double? width; + + const VoicesSnackBar({ + super.key, + required this.type, + this.onPrimaryPressed, + this.onSecondaryPressed, + this.onClosePressed, + this.width, + this.behavior = SnackBarBehavior.fixed, + this.padding = const EdgeInsets.all(16), + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final l10n = context.l10n; + + return DecoratedBox( + decoration: BoxDecoration( + color: type.backgroundColor(context), + borderRadius: BorderRadius.circular(8), + ), + child: Stack( + children: [ + Positioned( + top: 12, + right: 12, + child: IconButton( + icon: Icon( + size: 24, + CatalystVoicesIcons.x, + color: theme.colors.iconsForeground, + ), + onPressed: onClosePressed, + ), + ), + Column( + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + Icon( + size: 20, + type.icon(context), + color: type.iconColor(context), + ), + const SizedBox(width: 16), + Text( + type.title(context), + style: TextStyle( + color: type.titleColor(context), + fontSize: textTheme.titleMedium?.fontSize, + fontWeight: textTheme.titleMedium?.fontWeight, + fontFamily: textTheme.titleMedium?.fontFamily, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 48, + ), + child: Row( + children: [ + Text( + type.message(context), + style: textTheme.bodyMedium, + ), + ], + ), + ), + const SizedBox(height: 18), + Padding( + padding: const EdgeInsets.only( + left: 36, + ), + child: Row( + children: [ + TextButton( + onPressed: onPrimaryPressed, + child: Text( + type == VoicesSnackBarType.success + ? l10n.snackbarOkButtonText + : l10n.snackbarRefreshButtonText, + style: TextStyle( + color: theme.colors.textPrimary, + ), + ), + ), + const SizedBox(width: 8), + TextButton( + onPressed: onSecondaryPressed, + child: Text( + l10n.snackbarMoreButtonText, + style: TextStyle( + color: theme.colors.textPrimary, + decoration: TextDecoration.underline, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 18), + ], + ), + ], + ), + ); + } + + void show(BuildContext context) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: this, + behavior: behavior, + width: behavior == SnackBarBehavior.floating ? width : null, + padding: padding, + elevation: 0, + backgroundColor: Colors.transparent, + ), + ); + } + + static void hideCurrent(BuildContext context) => + ScaffoldMessenger.of(context).hideCurrentSnackBar(); +} diff --git a/catalyst_voices/lib/widgets/snackbar/voices_snackbar_type.dart b/catalyst_voices/lib/widgets/snackbar/voices_snackbar_type.dart new file mode 100644 index 0000000000..c3046914f8 --- /dev/null +++ b/catalyst_voices/lib/widgets/snackbar/voices_snackbar_type.dart @@ -0,0 +1,83 @@ +import 'package:catalyst_voices/widgets/snackbar/voices_snackbar.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'; + +/// Enum representing the different types of SnackBars available in the +/// [VoicesSnackBar] widget. +/// +/// Each type determines the appearance and behavior of the [VoicesSnackBar]. +enum VoicesSnackBarType { info, success, warning, error } + +class _SnackBarData { + final IconData icon; + final ColorResolver iconColor; + final ColorResolver titleColor; + final ColorResolver backgroundColor; + final L10nResolver message; + final L10nResolver title; + + const _SnackBarData({ + required this.icon, + required this.iconColor, + required this.titleColor, + required this.backgroundColor, + required this.message, + required this.title, + }); +} + +extension VoicesSnackBarTypeExtension on VoicesSnackBarType { + static final Map _data = { + VoicesSnackBarType.info: _SnackBarData( + icon: CatalystVoicesIcons.information_circle, + iconColor: (colors) => colors.iconsPrimary, + titleColor: (colors) => colors.onPrimaryContainer, + backgroundColor: (colors) => colors.primaryContainer, + message: (l10n) => l10n.snackbarInfoMessageText, + title: (l10n) => l10n.snackbarInfoLabelText, + ), + VoicesSnackBarType.success: _SnackBarData( + icon: CatalystVoicesIcons.check_circle, + iconColor: (colors) => colors.iconsSuccess, + titleColor: (colors) => colors.onSuccessContainer, + backgroundColor: (colors) => colors.successContainer, + message: (l10n) => l10n.snackbarSuccessMessageText, + title: (l10n) => l10n.snackbarSuccessLabelText, + ), + VoicesSnackBarType.warning: _SnackBarData( + icon: CatalystVoicesIcons.exclamation, + iconColor: (colors) => colors.iconsWarning, + titleColor: (colors) => colors.onWarningContainer, + backgroundColor: (colors) => colors.warningContainer, + message: (l10n) => l10n.snackbarWarningMessageText, + title: (l10n) => l10n.snackbarWarningLabelText, + ), + VoicesSnackBarType.error: _SnackBarData( + icon: CatalystVoicesIcons.exclamation_circle, + iconColor: (colors) => colors.iconsError, + titleColor: (colors) => colors.onErrorContainer, + backgroundColor: (colors) => colors.errorContainer, + message: (l10n) => l10n.snackbarErrorMessageText, + title: (l10n) => l10n.snackbarErrorLabelText, + ), + }; + + _SnackBarData get _snackBarData => _data[this]!; + + Color? backgroundColor(BuildContext context) => + _snackBarData.backgroundColor(Theme.of(context).colors); + + IconData icon(BuildContext context) => _snackBarData.icon; + + Color? iconColor(BuildContext context) => + _snackBarData.iconColor(Theme.of(context).colors); + + String message(BuildContext context) => _snackBarData.message(context.l10n); + + String title(BuildContext context) => _snackBarData.title(context.l10n); + + Color? titleColor(BuildContext context) => + _snackBarData.titleColor(Theme.of(context).colors); +} diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/catalyst_voices_brands.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/catalyst_voices_brands.dart index 1bb0dab35f..504dc9581b 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/catalyst_voices_brands.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/catalyst_voices_brands.dart @@ -1,3 +1,4 @@ export 'brands/brands.dart'; export 'theme_builder/theme_builder.dart'; export 'theme_extensions/theme_extensions.dart'; +export 'utils/typedefs.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_extensions/voices_color_scheme.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_extensions/voices_color_scheme.dart index c531f42d17..2c85d4f9ce 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_extensions/voices_color_scheme.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/theme_extensions/voices_color_scheme.dart @@ -51,6 +51,10 @@ class VoicesColorScheme extends ThemeExtension { final Color? elevationsOnSurfaceNeutralLv0; final Color? outlineBorder; final Color? outlineBorderVariant; + final Color? primaryContainer; + final Color? onPrimaryContainer; + final Color? errorContainer; + final Color? onErrorContainer; const VoicesColorScheme({ required this.textPrimary, @@ -97,6 +101,10 @@ class VoicesColorScheme extends ThemeExtension { required this.elevationsOnSurfaceNeutralLv0, required this.outlineBorder, required this.outlineBorderVariant, + required this.primaryContainer, + required this.onPrimaryContainer, + required this.errorContainer, + required this.onErrorContainer, }); @override @@ -145,6 +153,10 @@ class VoicesColorScheme extends ThemeExtension { Color? elevationsOnSurfaceNeutralLv0, Color? outlineBorder, Color? outlineBorderVariant, + Color? primaryContainer, + Color? onPrimaryContainer, + Color? errorContainer, + Color? onErrorContainer, }) { return VoicesColorScheme( textPrimary: textPrimary ?? this.textPrimary, @@ -199,6 +211,10 @@ class VoicesColorScheme extends ThemeExtension { elevationsOnSurfaceNeutralLv0 ?? this.elevationsOnSurfaceNeutralLv0, outlineBorder: outlineBorder ?? this.outlineBorder, outlineBorderVariant: outlineBorderVariant ?? this.outlineBorderVariant, + primaryContainer: primaryContainer ?? this.primaryContainer, + onPrimaryContainer: onPrimaryContainer ?? this.onPrimaryContainer, + errorContainer: errorContainer ?? this.errorContainer, + onErrorContainer: onErrorContainer ?? this.onErrorContainer, ); } @@ -290,6 +306,11 @@ class VoicesColorScheme extends ThemeExtension { outlineBorder: Color.lerp(outlineBorder, other.outlineBorder, t), outlineBorderVariant: Color.lerp(outlineBorderVariant, other.outlineBorderVariant, t), + primaryContainer: Color.lerp(primaryContainer, other.primaryContainer, t), + onPrimaryContainer: + Color.lerp(onPrimaryContainer, other.onPrimaryContainer, t), + errorContainer: Color.lerp(errorContainer, other.errorContainer, t), + onErrorContainer: Color.lerp(onErrorContainer, other.onErrorContainer, t), ); } } diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart index c6c7e40711..ddafd808ed 100644 --- a/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/themes/catalyst.dart @@ -4,56 +4,162 @@ import 'package:catalyst_voices_brands/src/theme_extensions/voices_color_scheme. import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -ThemeData _buildThemeData( - ColorScheme colorScheme, - VoicesColorScheme voicesColorScheme, - BrandAssets brandAssets, -) { - final textTheme = _buildTextTheme(voicesColorScheme); +const ColorScheme darkColorScheme = ColorScheme.dark( + primary: VoicesColors.darkPrimary, + primaryContainer: VoicesColors.darkPrimaryContainer, + onPrimaryContainer: VoicesColors.darkOnPrimaryContainer, + secondary: VoicesColors.darkSecondary, + onSecondary: VoicesColors.darkOnSecondary, + secondaryContainer: VoicesColors.darkSecondaryContainer, + onSecondaryContainer: VoicesColors.darkOnSecondaryContainer, + error: VoicesColors.darkError, + errorContainer: VoicesColors.darkErrorContainer, + onErrorContainer: VoicesColors.darkOnErrorContainer, + outline: VoicesColors.darkOutline, + outlineVariant: VoicesColors.darkOutlineVariant, +); - return ThemeData( - appBarTheme: AppBarTheme( - backgroundColor: voicesColorScheme.onSurfaceNeutralOpaqueLv1, - ), - filledButtonTheme: FilledButtonThemeData( - style: FilledButton.styleFrom( - minimumSize: const Size(48, 48), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - minimumSize: const Size(48, 48), - ), - ), - buttonBarTheme: const ButtonBarThemeData( - buttonHeight: 40, - ), - iconButtonTheme: IconButtonThemeData( - style: IconButton.styleFrom( - foregroundColor: voicesColorScheme.iconsForeground, - iconSize: 24, - ), - ), - drawerTheme: DrawerThemeData( - backgroundColor: voicesColorScheme.elevationsOnSurfaceNeutralLv0, - ), - listTileTheme: ListTileThemeData( - shape: const StadiumBorder(), - minTileHeight: 56, - contentPadding: const EdgeInsets.symmetric(horizontal: 20), - titleTextStyle: textTheme.labelLarge, - ), - dividerTheme: DividerThemeData( - color: colorScheme.outlineVariant, - ), - textTheme: textTheme, - colorScheme: colorScheme, - extensions: >[ - voicesColorScheme, - brandAssets, - ], - ); -} +const VoicesColorScheme darkVoicesColorScheme = VoicesColorScheme( + textPrimary: VoicesColors.darkTextPrimary, + textOnPrimary: VoicesColors.darkTextOnPrimary, + textOnPrimaryContainer: VoicesColors.darkTextOnPrimaryContainer, + textDisabled: VoicesColors.darkTextDisabled, + success: VoicesColors.darkSuccess, + onSuccess: VoicesColors.darkOnSuccess, + successContainer: VoicesColors.darkSuccessContainer, + onSuccessContainer: VoicesColors.darkOnSuccessContainer, + warning: VoicesColors.darkWarning, + onWarning: VoicesColors.darkOnWarning, + warningContainer: VoicesColors.darkWarningContainer, + onWarningContainer: VoicesColors.darkOnWarningContainer, + onSurfaceNeutral08: VoicesColors.darkOnSurfaceNeutral08, + onSurfaceNeutral012: VoicesColors.darkOnSurfaceNeutral012, + onSurfaceNeutral016: VoicesColors.darkOnSurfaceNeutral016, + onSurfaceNeutralOpaqueLv0: VoicesColors.darkOnSurfaceNeutralOpaqueLv0, + onSurfaceNeutralOpaqueLv1: VoicesColors.darkOnSurfaceNeutralOpaqueLv1, + onSurfaceNeutralOpaqueLv2: VoicesColors.darkOnSurfaceNeutralOpaqueLv2, + onSurfacePrimaryContainer: VoicesColors.darkOnSurfacePrimaryContainer, + onSurfacePrimary08: VoicesColors.darkOnSurfacePrimary08, + onSurfacePrimary012: VoicesColors.darkOnSurfacePrimary012, + onSurfacePrimary016: VoicesColors.darkOnSurfacePrimary016, + onSurfaceSecondary08: VoicesColors.darkOnSurfaceSecondary08, + onSurfaceSecondary012: VoicesColors.darkOnSurfaceSecondary012, + onSurfaceSecondary016: VoicesColors.darkOnSurfaceSecondary016, + onSurfaceError08: VoicesColors.darkOnSurfaceError08, + onSurfaceError012: VoicesColors.darkOnSurfaceError012, + onSurfaceError016: VoicesColors.darkOnSurfaceError016, + iconsForeground: VoicesColors.darkIconsForeground, + iconsBackground: VoicesColors.darkIconsBackground, + iconsDisabled: VoicesColors.darkIconsDisabled, + iconsPrimary: VoicesColors.darkIconsPrimary, + iconsSecondary: VoicesColors.darkIconsSecondary, + iconsSuccess: VoicesColors.darkIconsSuccess, + iconsWarning: VoicesColors.darkIconsWarning, + iconsError: VoicesColors.darkIconsError, + avatarsPrimary: VoicesColors.darkAvatarsPrimary, + avatarsSecondary: VoicesColors.darkAvatarsSecondary, + avatarsSuccess: VoicesColors.darkAvatarsSuccess, + avatarsWarning: VoicesColors.darkAvatarsWarning, + avatarsError: VoicesColors.darkAvatarsError, + elevationsOnSurfaceNeutralLv0: VoicesColors.darkElevationsOnSurfaceNeutralLv0, + outlineBorder: VoicesColors.darkOutlineBorderOutline, + outlineBorderVariant: VoicesColors.darkOutlineBorderOutlineVariant, + primaryContainer: VoicesColors.darkPrimaryContainer, + onPrimaryContainer: VoicesColors.darkOnPrimaryContainer, + errorContainer: VoicesColors.darkErrorContainer, + onErrorContainer: VoicesColors.darkOnErrorContainer, +); + +const ColorScheme lightColorScheme = ColorScheme.light( + primary: VoicesColors.lightPrimary, + primaryContainer: VoicesColors.lightPrimaryContainer, + onPrimaryContainer: VoicesColors.lightOnPrimaryContainer, + secondary: VoicesColors.lightSecondary, + onSecondary: VoicesColors.lightOnSecondary, + secondaryContainer: VoicesColors.lightSecondaryContainer, + onSecondaryContainer: VoicesColors.lightOnSecondaryContainer, + error: VoicesColors.lightError, + errorContainer: VoicesColors.lightErrorContainer, + onErrorContainer: VoicesColors.lightOnErrorContainer, + outline: VoicesColors.lightOutline, + outlineVariant: VoicesColors.lightOutlineVariant, +); + +const VoicesColorScheme lightVoicesColorScheme = VoicesColorScheme( + textPrimary: VoicesColors.lightTextPrimary, + textOnPrimary: VoicesColors.lightTextOnPrimary, + textOnPrimaryContainer: VoicesColors.lightTextOnPrimaryContainer, + textDisabled: VoicesColors.lightTextDisabled, + success: VoicesColors.lightSuccess, + onSuccess: VoicesColors.lightOnSuccess, + successContainer: VoicesColors.lightSuccessContainer, + onSuccessContainer: VoicesColors.lightOnSuccessContainer, + warning: VoicesColors.lightWarning, + onWarning: VoicesColors.lightOnWarning, + warningContainer: VoicesColors.lightWarningContainer, + onWarningContainer: VoicesColors.lightOnWarningContainer, + onSurfaceNeutral08: VoicesColors.lightOnSurfaceNeutral08, + onSurfaceNeutral012: VoicesColors.lightOnSurfaceNeutral012, + onSurfaceNeutral016: VoicesColors.lightOnSurfaceNeutral016, + onSurfaceNeutralOpaqueLv0: VoicesColors.lightOnSurfaceNeutralOpaqueLv0, + onSurfaceNeutralOpaqueLv1: VoicesColors.lightOnSurfaceNeutralOpaqueLv1, + onSurfaceNeutralOpaqueLv2: VoicesColors.lightOnSurfaceNeutralOpaqueLv2, + onSurfacePrimaryContainer: VoicesColors.lightOnSurfacePrimaryContainer, + onSurfacePrimary08: VoicesColors.lightOnSurfacePrimary08, + onSurfacePrimary012: VoicesColors.lightOnSurfacePrimary012, + onSurfacePrimary016: VoicesColors.lightOnSurfacePrimary016, + onSurfaceSecondary08: VoicesColors.lightOnSurfaceSecondary08, + onSurfaceSecondary012: VoicesColors.lightOnSurfaceSecondary012, + onSurfaceSecondary016: VoicesColors.lightOnSurfaceSecondary016, + onSurfaceError08: VoicesColors.lightOnSurfaceError08, + onSurfaceError012: VoicesColors.lightOnSurfaceError012, + onSurfaceError016: VoicesColors.lightOnSurfaceError016, + iconsForeground: VoicesColors.lightIconsForeground, + iconsBackground: VoicesColors.lightIconsBackground, + iconsDisabled: VoicesColors.lightIconsDisabled, + iconsPrimary: VoicesColors.lightIconsPrimary, + iconsSecondary: VoicesColors.lightIconsSecondary, + iconsSuccess: VoicesColors.lightIconsSuccess, + iconsWarning: VoicesColors.lightIconsWarning, + iconsError: VoicesColors.lightIconsError, + avatarsPrimary: VoicesColors.lightAvatarsPrimary, + avatarsSecondary: VoicesColors.lightAvatarsSecondary, + avatarsSuccess: VoicesColors.lightAvatarsSuccess, + avatarsWarning: VoicesColors.lightAvatarsWarning, + avatarsError: VoicesColors.lightAvatarsError, + elevationsOnSurfaceNeutralLv0: + VoicesColors.lightElevationsOnSurfaceNeutralLv0, + outlineBorder: VoicesColors.lightOutlineBorderOutline, + outlineBorderVariant: VoicesColors.lightOutlineBorderOutlineVariant, + primaryContainer: VoicesColors.lightPrimaryContainer, + onPrimaryContainer: VoicesColors.lightOnPrimaryContainer, + errorContainer: VoicesColors.lightErrorContainer, + onErrorContainer: VoicesColors.lightOnErrorContainer, +); + +/// [ThemeData] for the `catalyst` brand. +final ThemeData catalyst = _buildThemeData( + lightColorScheme, + lightVoicesColorScheme, + lightBrandAssets, +); + +final BrandAssets darkBrandAssets = BrandAssets( + logo: VoicesAssets.images.catalystLogoWhite, + logoIcon: VoicesAssets.images.catalystLogoIconWhite, +); + +/// Dark [ThemeData] for the `catalyst` brand. +final ThemeData darkCatalyst = _buildThemeData( + darkColorScheme, + darkVoicesColorScheme, + darkBrandAssets, +); + +final BrandAssets lightBrandAssets = BrandAssets( + logo: VoicesAssets.images.catalystLogo, + logoIcon: VoicesAssets.images.catalystLogoIcon, +); TextTheme _buildTextTheme(VoicesColorScheme voicesColorScheme) { return TextTheme( @@ -159,151 +265,53 @@ TextTheme _buildTextTheme(VoicesColorScheme voicesColorScheme) { ); } -const ColorScheme lightColorScheme = ColorScheme.light( - primary: VoicesColors.lightPrimary, - primaryContainer: VoicesColors.lightPrimaryContainer, - onPrimaryContainer: VoicesColors.lightOnPrimaryContainer, - secondary: VoicesColors.lightSecondary, - onSecondary: VoicesColors.lightOnSecondary, - secondaryContainer: VoicesColors.lightSecondaryContainer, - onSecondaryContainer: VoicesColors.lightOnSecondaryContainer, - error: VoicesColors.lightError, - errorContainer: VoicesColors.lightErrorContainer, - onErrorContainer: VoicesColors.lightOnErrorContainer, - outline: VoicesColors.lightOutline, - outlineVariant: VoicesColors.lightOutlineVariant, -); - -const VoicesColorScheme lightVoicesColorScheme = VoicesColorScheme( - textPrimary: VoicesColors.lightTextPrimary, - textOnPrimary: VoicesColors.lightTextOnPrimary, - textOnPrimaryContainer: VoicesColors.lightTextOnPrimaryContainer, - textDisabled: VoicesColors.lightTextDisabled, - success: VoicesColors.lightSuccess, - onSuccess: VoicesColors.lightOnSuccess, - successContainer: VoicesColors.lightSuccessContainer, - onSuccessContainer: VoicesColors.lightOnSuccessContainer, - warning: VoicesColors.lightWarning, - onWarning: VoicesColors.lightOnWarning, - warningContainer: VoicesColors.lightWarningContainer, - onWarningContainer: VoicesColors.lightOnWarningContainer, - onSurfaceNeutral08: VoicesColors.lightOnSurfaceNeutral08, - onSurfaceNeutral012: VoicesColors.lightOnSurfaceNeutral012, - onSurfaceNeutral016: VoicesColors.lightOnSurfaceNeutral016, - onSurfaceNeutralOpaqueLv0: VoicesColors.lightOnSurfaceNeutralOpaqueLv0, - onSurfaceNeutralOpaqueLv1: VoicesColors.lightOnSurfaceNeutralOpaqueLv1, - onSurfaceNeutralOpaqueLv2: VoicesColors.lightOnSurfaceNeutralOpaqueLv2, - onSurfacePrimaryContainer: VoicesColors.lightOnSurfacePrimaryContainer, - onSurfacePrimary08: VoicesColors.lightOnSurfacePrimary08, - onSurfacePrimary012: VoicesColors.lightOnSurfacePrimary012, - onSurfacePrimary016: VoicesColors.lightOnSurfacePrimary016, - onSurfaceSecondary08: VoicesColors.lightOnSurfaceSecondary08, - onSurfaceSecondary012: VoicesColors.lightOnSurfaceSecondary012, - onSurfaceSecondary016: VoicesColors.lightOnSurfaceSecondary016, - onSurfaceError08: VoicesColors.lightOnSurfaceError08, - onSurfaceError012: VoicesColors.lightOnSurfaceError012, - onSurfaceError016: VoicesColors.lightOnSurfaceError016, - iconsForeground: VoicesColors.lightIconsForeground, - iconsBackground: VoicesColors.lightIconsBackground, - iconsDisabled: VoicesColors.lightIconsDisabled, - iconsPrimary: VoicesColors.lightIconsPrimary, - iconsSecondary: VoicesColors.lightIconsSecondary, - iconsSuccess: VoicesColors.lightIconsSuccess, - iconsWarning: VoicesColors.lightIconsWarning, - iconsError: VoicesColors.lightIconsError, - avatarsPrimary: VoicesColors.lightAvatarsPrimary, - avatarsSecondary: VoicesColors.lightAvatarsSecondary, - avatarsSuccess: VoicesColors.lightAvatarsSuccess, - avatarsWarning: VoicesColors.lightAvatarsWarning, - avatarsError: VoicesColors.lightAvatarsError, - elevationsOnSurfaceNeutralLv0: - VoicesColors.lightElevationsOnSurfaceNeutralLv0, - outlineBorder: VoicesColors.lightOutlineBorderOutline, - outlineBorderVariant: VoicesColors.lightOutlineBorderOutlineVariant, -); - -const ColorScheme darkColorScheme = ColorScheme.dark( - primary: VoicesColors.darkPrimary, - primaryContainer: VoicesColors.darkPrimaryContainer, - onPrimaryContainer: VoicesColors.darkOnPrimaryContainer, - secondary: VoicesColors.darkSecondary, - onSecondary: VoicesColors.darkOnSecondary, - secondaryContainer: VoicesColors.darkSecondaryContainer, - onSecondaryContainer: VoicesColors.darkOnSecondaryContainer, - error: VoicesColors.darkError, - errorContainer: VoicesColors.darkErrorContainer, - onErrorContainer: VoicesColors.darkOnErrorContainer, - outline: VoicesColors.darkOutline, - outlineVariant: VoicesColors.darkOutlineVariant, -); - -const VoicesColorScheme darkVoicesColorScheme = VoicesColorScheme( - textPrimary: VoicesColors.darkTextPrimary, - textOnPrimary: VoicesColors.darkTextOnPrimary, - textOnPrimaryContainer: VoicesColors.darkTextOnPrimaryContainer, - textDisabled: VoicesColors.darkTextDisabled, - success: VoicesColors.darkSuccess, - onSuccess: VoicesColors.darkOnSuccess, - successContainer: VoicesColors.darkSuccessContainer, - onSuccessContainer: VoicesColors.darkOnSuccessContainer, - warning: VoicesColors.darkWarning, - onWarning: VoicesColors.darkOnWarning, - warningContainer: VoicesColors.darkWarningContainer, - onWarningContainer: VoicesColors.darkOnWarningContainer, - onSurfaceNeutral08: VoicesColors.darkOnSurfaceNeutral08, - onSurfaceNeutral012: VoicesColors.darkOnSurfaceNeutral012, - onSurfaceNeutral016: VoicesColors.darkOnSurfaceNeutral016, - onSurfaceNeutralOpaqueLv0: VoicesColors.darkOnSurfaceNeutralOpaqueLv0, - onSurfaceNeutralOpaqueLv1: VoicesColors.darkOnSurfaceNeutralOpaqueLv1, - onSurfaceNeutralOpaqueLv2: VoicesColors.darkOnSurfaceNeutralOpaqueLv2, - onSurfacePrimaryContainer: VoicesColors.darkOnSurfacePrimaryContainer, - onSurfacePrimary08: VoicesColors.darkOnSurfacePrimary08, - onSurfacePrimary012: VoicesColors.darkOnSurfacePrimary012, - onSurfacePrimary016: VoicesColors.darkOnSurfacePrimary016, - onSurfaceSecondary08: VoicesColors.darkOnSurfaceSecondary08, - onSurfaceSecondary012: VoicesColors.darkOnSurfaceSecondary012, - onSurfaceSecondary016: VoicesColors.darkOnSurfaceSecondary016, - onSurfaceError08: VoicesColors.darkOnSurfaceError08, - onSurfaceError012: VoicesColors.darkOnSurfaceError012, - onSurfaceError016: VoicesColors.darkOnSurfaceError016, - iconsForeground: VoicesColors.darkIconsForeground, - iconsBackground: VoicesColors.darkIconsBackground, - iconsDisabled: VoicesColors.darkIconsDisabled, - iconsPrimary: VoicesColors.darkIconsPrimary, - iconsSecondary: VoicesColors.darkIconsSecondary, - iconsSuccess: VoicesColors.darkIconsSuccess, - iconsWarning: VoicesColors.darkIconsWarning, - iconsError: VoicesColors.darkIconsError, - avatarsPrimary: VoicesColors.darkAvatarsPrimary, - avatarsSecondary: VoicesColors.darkAvatarsSecondary, - avatarsSuccess: VoicesColors.darkAvatarsSuccess, - avatarsWarning: VoicesColors.darkAvatarsWarning, - avatarsError: VoicesColors.darkAvatarsError, - elevationsOnSurfaceNeutralLv0: VoicesColors.darkElevationsOnSurfaceNeutralLv0, - outlineBorder: VoicesColors.darkOutlineBorderOutline, - outlineBorderVariant: VoicesColors.darkOutlineBorderOutlineVariant, -); - -final BrandAssets lightBrandAssets = BrandAssets( - logo: VoicesAssets.images.catalystLogo, - logoIcon: VoicesAssets.images.catalystLogoIcon, -); - -final BrandAssets darkBrandAssets = BrandAssets( - logo: VoicesAssets.images.catalystLogoWhite, - logoIcon: VoicesAssets.images.catalystLogoIconWhite, -); - -/// [ThemeData] for the `catalyst` brand. -final ThemeData catalyst = _buildThemeData( - lightColorScheme, - lightVoicesColorScheme, - lightBrandAssets, -); +ThemeData _buildThemeData( + ColorScheme colorScheme, + VoicesColorScheme voicesColorScheme, + BrandAssets brandAssets, +) { + final textTheme = _buildTextTheme(voicesColorScheme); -/// Dark [ThemeData] for the `catalyst` brand. -final ThemeData darkCatalyst = _buildThemeData( - darkColorScheme, - darkVoicesColorScheme, - darkBrandAssets, -); + return ThemeData( + appBarTheme: AppBarTheme( + backgroundColor: voicesColorScheme.onSurfaceNeutralOpaqueLv1, + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + minimumSize: const Size(48, 48), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + minimumSize: const Size(48, 48), + ), + ), + buttonBarTheme: const ButtonBarThemeData( + buttonHeight: 40, + ), + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + foregroundColor: voicesColorScheme.iconsForeground, + iconSize: 24, + ), + ), + drawerTheme: DrawerThemeData( + backgroundColor: voicesColorScheme.elevationsOnSurfaceNeutralLv0, + ), + listTileTheme: ListTileThemeData( + shape: const StadiumBorder(), + minTileHeight: 56, + contentPadding: const EdgeInsets.symmetric(horizontal: 20), + titleTextStyle: textTheme.labelLarge, + ), + dividerTheme: DividerThemeData( + color: colorScheme.outlineVariant, + ), + textTheme: textTheme, + colorScheme: colorScheme, + extensions: >[ + voicesColorScheme, + brandAssets, + ], + ); +} diff --git a/catalyst_voices/packages/catalyst_voices_brands/lib/src/utils/typedefs.dart b/catalyst_voices/packages/catalyst_voices_brands/lib/src/utils/typedefs.dart new file mode 100644 index 0000000000..f47df95261 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_brands/lib/src/utils/typedefs.dart @@ -0,0 +1,9 @@ +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:flutter/widgets.dart'; + +/// A function that resolves a [Color] based on a given +/// [VoicesColorScheme]. +/// +/// This function takes a [VoicesColorScheme] as input and returns an optional +/// [Color]. +typedef ColorResolver = Color? Function(VoicesColorScheme); diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart index 1c5fed9fcb..3f3950bdcb 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart @@ -211,6 +211,72 @@ abstract class VoicesLocalizations { /// In en, this message translates to: /// **'[cmd=K]'** String get searchButtonLabelText; + + /// Label text shown in the Snackbar widget when the message is an info message. + /// + /// In en, this message translates to: + /// **'Info'** + String get snackbarInfoLabelText; + + /// Text shown in the Snackbar widget when the message is an info message. + /// + /// In en, this message translates to: + /// **'This is an info message!'** + String get snackbarInfoMessageText; + + /// Label text shown in the Snackbar widget when the message is an success message. + /// + /// In en, this message translates to: + /// **'Success'** + String get snackbarSuccessLabelText; + + /// Text shown in the Snackbar widget when the message is an success message. + /// + /// In en, this message translates to: + /// **'This is a success message!'** + String get snackbarSuccessMessageText; + + /// Label text shown in the Snackbar widget when the message is an warning message. + /// + /// In en, this message translates to: + /// **'Warning'** + String get snackbarWarningLabelText; + + /// Text shown in the Snackbar widget when the message is an warning message. + /// + /// In en, this message translates to: + /// **'This is a warning message!'** + String get snackbarWarningMessageText; + + /// Label text shown in the Snackbar widget when the message is an error message. + /// + /// In en, this message translates to: + /// **'Error'** + String get snackbarErrorLabelText; + + /// Text shown in the Snackbar widget when the message is an error message. + /// + /// In en, this message translates to: + /// **'This is an error message!'** + String get snackbarErrorMessageText; + + /// Text shown in the Snackbar widget for the refresh button. + /// + /// In en, this message translates to: + /// **'Refresh'** + String get snackbarRefreshButtonText; + + /// Text shown in the Snackbar widget for the more button. + /// + /// In en, this message translates to: + /// **'Learn more'** + String get snackbarMoreButtonText; + + /// Text shown in the Snackbar widget for the ok button. + /// + /// In en, this message translates to: + /// **'Ok'** + String get snackbarOkButtonText; } class _VoicesLocalizationsDelegate extends LocalizationsDelegate { diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart index b5ed7ab0d1..bd04c65217 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart @@ -63,4 +63,37 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get searchButtonLabelText => '[cmd=K]'; + + @override + String get snackbarInfoLabelText => 'Info'; + + @override + String get snackbarInfoMessageText => 'This is an info message!'; + + @override + String get snackbarSuccessLabelText => 'Success'; + + @override + String get snackbarSuccessMessageText => 'This is a success message!'; + + @override + String get snackbarWarningLabelText => 'Warning'; + + @override + String get snackbarWarningMessageText => 'This is a warning message!'; + + @override + String get snackbarErrorLabelText => 'Error'; + + @override + String get snackbarErrorMessageText => 'This is an error message!'; + + @override + String get snackbarRefreshButtonText => 'Refresh'; + + @override + String get snackbarMoreButtonText => 'Learn more'; + + @override + String get snackbarOkButtonText => 'Ok'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart index 1380cfd07b..91f8e2e31c 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart @@ -63,4 +63,37 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get searchButtonLabelText => '[cmd=K]'; + + @override + String get snackbarInfoLabelText => 'Info'; + + @override + String get snackbarInfoMessageText => 'This is an info message!'; + + @override + String get snackbarSuccessLabelText => 'Success'; + + @override + String get snackbarSuccessMessageText => 'This is a success message!'; + + @override + String get snackbarWarningLabelText => 'Warning'; + + @override + String get snackbarWarningMessageText => 'This is a warning message!'; + + @override + String get snackbarErrorLabelText => 'Error'; + + @override + String get snackbarErrorMessageText => 'This is an error message!'; + + @override + String get snackbarRefreshButtonText => 'Refresh'; + + @override + String get snackbarMoreButtonText => 'Learn more'; + + @override + String get snackbarOkButtonText => 'Ok'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb index 47b89204ac..b363e04120 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -79,5 +79,49 @@ "searchButtonLabelText": "[cmd=K]", "@searchButtonLabelText": { "description": "Label text shown in the Search widget." + }, + "snackbarInfoLabelText": "Info", + "@snackbarInfoLabelText": { + "description": "Label text shown in the Snackbar widget when the message is an info message." + }, + "snackbarInfoMessageText": "This is an info message!", + "@snackbarInfoMessageText": { + "description": "Text shown in the Snackbar widget when the message is an info message." + }, + "snackbarSuccessLabelText": "Success", + "@snackbarSuccessLabelText": { + "description": "Label text shown in the Snackbar widget when the message is an success message." + }, + "snackbarSuccessMessageText": "This is a success message!", + "@snackbarSuccessMessageText": { + "description": "Text shown in the Snackbar widget when the message is an success message." + }, + "snackbarWarningLabelText": "Warning", + "@snackbarWarningLabelText": { + "description": "Label text shown in the Snackbar widget when the message is an warning message." + }, + "snackbarWarningMessageText": "This is a warning message!", + "@snackbarWarningMessageText": { + "description": "Text shown in the Snackbar widget when the message is an warning message." + }, + "snackbarErrorLabelText": "Error", + "@snackbarErrorLabelText": { + "description": "Label text shown in the Snackbar widget when the message is an error message." + }, + "snackbarErrorMessageText": "This is an error message!", + "@snackbarErrorMessageText": { + "description": "Text shown in the Snackbar widget when the message is an error message." + }, + "snackbarRefreshButtonText": "Refresh", + "@snackbarRefreshButtonText": { + "description": "Text shown in the Snackbar widget for the refresh button." + }, + "snackbarMoreButtonText": "Learn more", + "@snackbarMoreButtonText": { + "description": "Text shown in the Snackbar widget for the more button." + }, + "snackbarOkButtonText": "Ok", + "@snackbarOkButtonText": { + "description": "Text shown in the Snackbar widget for the ok button." } } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/src/catalyst_voices_localization.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/src/catalyst_voices_localization.dart index 678bb60f53..085d43329a 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/src/catalyst_voices_localization.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/src/catalyst_voices_localization.dart @@ -1,2 +1,3 @@ -export 'package:catalyst_voices_localization/generated/catalyst_voices_localizations.dart'; -export 'package:catalyst_voices_localization/src/build_context_localization_extension.dart'; +export '../generated/catalyst_voices_localizations.dart'; +export 'build_context_localization_extension.dart'; +export 'typedefs.dart'; diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/src/typedefs.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/src/typedefs.dart new file mode 100644 index 0000000000..6092c8cc84 --- /dev/null +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/src/typedefs.dart @@ -0,0 +1,7 @@ +import 'package:catalyst_voices_localization/generated/catalyst_voices_localizations.dart'; + +/// A function that resolves a localized string based on a given +/// [VoicesLocalizations]. +/// +/// This function takes a [VoicesLocalizations] as input and returns a [String]. +typedef L10nResolver = String Function(VoicesLocalizations); diff --git a/catalyst_voices/uikit_example/lib/examples/voices_snackbar_example.dart b/catalyst_voices/uikit_example/lib/examples/voices_snackbar_example.dart new file mode 100644 index 0000000000..25df7b925c --- /dev/null +++ b/catalyst_voices/uikit_example/lib/examples/voices_snackbar_example.dart @@ -0,0 +1,48 @@ +import 'package:catalyst_voices/widgets/snackbar/voices_snackbar.dart'; +import 'package:catalyst_voices/widgets/snackbar/voices_snackbar_type.dart'; +import 'package:flutter/material.dart'; + +class VoicesSnackbarExample extends StatelessWidget { + static const String route = '/snackbar-example'; + + const VoicesSnackbarExample({super.key}); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.sizeOf(context).width; + + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(32), + child: Wrap( + spacing: 16, + runSpacing: 16, + children: [ + for (final type in VoicesSnackBarType.values) + OutlinedButton( + onPressed: () { + VoicesSnackBar( + type: type, + padding: EdgeInsets.only( + bottom: screenWidth / 2, + left: screenWidth / 3, + right: screenWidth / 3, + ), + onPrimaryPressed: () {}, + onSecondaryPressed: () {}, + onClosePressed: () => VoicesSnackBar.hideCurrent(context), + ).show(context); + }, + child: Text( + type.toString().split('.').last, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/catalyst_voices/uikit_example/lib/examples_list.dart b/catalyst_voices/uikit_example/lib/examples_list.dart index ecdf62b911..b2cb4affa2 100644 --- a/catalyst_voices/uikit_example/lib/examples_list.dart +++ b/catalyst_voices/uikit_example/lib/examples_list.dart @@ -4,8 +4,29 @@ import 'package:catalyst_voices/widgets/menu/voices_list_tile.dart'; import 'package:flutter/material.dart'; import 'package:uikit_example/examples/voices_chip_example.dart'; import 'package:uikit_example/examples/voices_navigation_example.dart'; +import 'package:uikit_example/examples/voices_snackbar_example.dart'; class ExamplesListPage extends StatelessWidget { + static List get examples { + return const [ + ExampleTile( + title: 'VoicesNavigation (AppBar + Drawer)', + route: VoicesNavigationExample.route, + page: VoicesNavigationExample(), + ), + ExampleTile( + title: 'Voices Chips', + route: VoicesChipExample.route, + page: VoicesChipExample(), + ), + ExampleTile( + title: 'Voices Snackbar', + route: VoicesSnackbarExample.route, + page: VoicesSnackbarExample(), + ), + ]; + } + const ExamplesListPage({super.key}); @override @@ -22,21 +43,6 @@ class ExamplesListPage extends StatelessWidget { ), ); } - - static List get examples { - return const [ - ExampleTile( - title: 'VoicesNavigation (AppBar + Drawer)', - route: VoicesNavigationExample.route, - page: VoicesNavigationExample(), - ), - ExampleTile( - title: 'Voices Chips', - route: VoicesChipExample.route, - page: VoicesChipExample(), - ), - ]; - } } class ExampleTile extends StatelessWidget {