From 3cf47193178b411fd14ced6177584557229aa4eb Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 2 Jul 2024 16:29:21 +0100 Subject: [PATCH] feat: Added ZetaProvider.base to allow for better developer experience --- example/example.md | 42 + example/lib/main.dart | 36 +- .../lib/pages/components/avatar_example.dart | 1091 +++++++++-------- lib/src/components/avatars/avatar.dart | 36 +- lib/src/zeta.dart | 3 +- lib/src/zeta_provider.dart | 128 +- test/test_utils/test_app.dart | 18 +- 7 files changed, 752 insertions(+), 602 deletions(-) create mode 100644 example/example.md diff --git a/example/example.md b/example/example.md new file mode 100644 index 00000000..b15b08dc --- /dev/null +++ b/example/example.md @@ -0,0 +1,42 @@ +## Set up + +To use Zeta components in you app, first the whole app must be wrapped with `ZetaProvider`. The easiest way to do this is with the custom builder `ZetaProvider.custom`. +You can provide initial values for `ThemeData`, `ThemeMode`, `contrast` and `ZetaThemeData`. + +* `initialThemeData` allows you to extend an existing Flutter themeData to use alongside the `Zeta` theme. +* `initialThemeMode` sets whether the app starts in light or dark mode, or uses the device default. +* `initialContrast` sets whether the app starts with standard (WCAG AA) contrast, or if it attempts to use the more accessible contrast (WCAG AAA). +* `initialZetaThemeData` allows you to override the Zeta theme with a custom theme. + +```dart +return ZetaProvider.base( + initialThemeData: initialThemeData, + initialThemeMode: initialThemeMode, + initialContrast: initialContrast, + initialZetaThemeData: initialZetaThemeData, + initialRounded: initialRounded, + builder: (context, lightTheme, darkTheme, themeMode) { + return MaterialApp.router( + routerConfig: router, + themeMode: themeMode, + theme: lightTheme, + darkTheme: darkTheme, + ); + }, +); +``` + +## Using the components + +Once Zeta is configured, Zeta components can be used as any other componenent, and will inherit theme and other information from `Zeta`. + +```dart +Column( + children:[ + ZetaButton(label: 'Button', onPressed: (){}), + ZetaAvatar(), + ZetaStatusLabel(), + // etc... + ] +) +``` \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index cf16ece0..3692f8f5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -14,7 +14,7 @@ void main() async { runApp( ZetaExample( themeService: themeService, - initialThemeData: themePreferences.$1 ?? ZetaThemeData(), + initialZetaThemeData: themePreferences.$1 ?? ZetaThemeData(), initialThemeMode: themePreferences.$2 ?? ThemeMode.system, initialContrast: themePreferences.$3 ?? ZetaContrast.aa, ), @@ -27,41 +27,31 @@ class ZetaExample extends StatelessWidget { required this.themeService, required this.initialContrast, required this.initialThemeMode, - required this.initialThemeData, + required this.initialZetaThemeData, }); final ZetaThemeService themeService; final ZetaContrast initialContrast; final ThemeMode initialThemeMode; - final ZetaThemeData initialThemeData; + final ZetaThemeData initialZetaThemeData; @override Widget build(BuildContext context) { - return ZetaProvider( - themeService: themeService, + final initialThemeData = null; + final initialRounded = true; + + return ZetaProvider.base( + initialZetaThemeData: initialZetaThemeData, + initialThemeMode: initialThemeMode, initialContrast: initialContrast, initialThemeData: initialThemeData, - initialThemeMode: initialThemeMode, - builder: (context, themeData, themeMode) { - final dark = themeData.colorsDark.toScheme(); - final light = themeData.colorsLight.toScheme(); + initialRounded: initialRounded, + builder: (context, lightTheme, darkTheme, themeMode) { return MaterialApp.router( routerConfig: router, themeMode: themeMode, - theme: ThemeData( - useMaterial3: true, - fontFamily: themeData.fontFamily, - scaffoldBackgroundColor: light.surfaceTertiary, - colorScheme: light, - textTheme: zetaTextTheme, - ), - darkTheme: ThemeData( - useMaterial3: true, - fontFamily: themeData.fontFamily, - scaffoldBackgroundColor: dark.surfaceTertiary, - colorScheme: dark, - textTheme: zetaTextTheme, - ), + theme: lightTheme, + darkTheme: darkTheme, ); }, ); diff --git a/example/lib/pages/components/avatar_example.dart b/example/lib/pages/components/avatar_example.dart index 23a6c5dc..ff84aa2d 100644 --- a/example/lib/pages/components/avatar_example.dart +++ b/example/lib/pages/components/avatar_example.dart @@ -20,557 +20,560 @@ class AvatarExample extends StatelessWidget { return ExampleScaffold( name: AvatarExample.name, child: SingleChildScrollView( - padding: EdgeInsets.all(ZetaSpacing.medium), - child: Column( - children: [ - Column( - children: [ - Text( - 'ZetaAvatar.image', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + padding: EdgeInsets.all(ZetaSpacing.medium), + child: Column( + children: [ + Column( + children: [ + Text( + 'ZetaAvatar.image', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 15), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image(size: size), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 15), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - borderColor: Zeta.of(context).colors.green, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 15), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - image: image, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 15), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - borderColor: Zeta.of(context).colors.green, - image: image, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - Column( - children: [ - Text( - 'ZetaAvatar.initials', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image(size: size), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.initials', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 15), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 15), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - borderColor: Zeta.of(context).colors.green, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - Column( - children: [ - Text( - 'ZetaAvatar.image with badge', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 15), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.image with badge', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - upperBadge: ZetaAvatarBadge.notification(value: 3), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - borderColor: Zeta.of(context).colors.green, - upperBadge: ZetaAvatarBadge.notification(value: 3), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - upperBadge: ZetaAvatarBadge.notification(value: 3), - image: image, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - borderColor: Zeta.of(context).colors.green, - upperBadge: ZetaAvatarBadge.notification(value: 3), - image: image, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - Column( - children: [ - Text( - 'ZetaAvatar.initials with badge', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + upperBadge: ZetaAvatarBadge.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaAvatarBadge.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + upperBadge: ZetaAvatarBadge.notification(value: 3), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaAvatarBadge.notification(value: 3), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.initials with badge', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - upperBadge: ZetaAvatarBadge.notification(value: 3), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - borderColor: Zeta.of(context).colors.green, - upperBadge: ZetaAvatarBadge.notification(value: 3), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - Column( - children: [ - Text( - 'ZetaAvatar.image with special status', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + upperBadge: ZetaAvatarBadge.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaAvatarBadge.notification(value: 3), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.image with special status', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - borderColor: Zeta.of(context).colors.green, - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - lowerBadge: ZetaAvatarBadge.icon(), - image: image, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - borderColor: Zeta.of(context).colors.green, - lowerBadge: ZetaAvatarBadge.icon(), - image: image, - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - Column( - children: [ - Text( - 'ZetaAvatar.initials with special status', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + lowerBadge: ZetaAvatarBadge.icon(), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + borderColor: Zeta.of(context).colors.green, + lowerBadge: ZetaAvatarBadge.icon(), + image: image, + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar.initials with special status', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - borderColor: Zeta.of(context).colors.green, - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - Column( - children: [ - Text( - 'ZetaAvatar with notification badge and status badge', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Row( - children: [ - Column( - children: ZetaAvatarSize.values.map((size) { - final height = size.pixelSize; - final padding = (height - 14) / 2; - return Column( - children: [ - SizedBox( - height: height, - child: Padding( - padding: EdgeInsets.only(top: padding), - child: Text(size.name.toUpperCase()), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + Column( + children: [ + Text( + 'ZetaAvatar with notification badge and status badge', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 15), + Row( + children: [ + Column( + children: ZetaAvatarSize.values.map((size) { + final height = size.pixelSize; + final padding = (height - 14) / 2; + return Column( + children: [ + SizedBox( + height: height, + child: Padding( + padding: EdgeInsets.only(top: padding), + child: Text(size.name.toUpperCase()), + ), ), - ), - const SizedBox(height: 20), - ], - ); - }).toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - image: image, - upperBadge: ZetaAvatarBadge.notification(value: 3), - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.image( - size: size, - image: image, - borderColor: Zeta.of(context).colors.green, - upperBadge: ZetaAvatarBadge.notification(value: 3), - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - upperBadge: ZetaAvatarBadge.notification(value: 3), - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - const SizedBox(width: 12), - Column( - children: ZetaAvatarSize.values - .map((size) => Column( - children: [ - ZetaAvatar.initials( - size: size, - initials: 'AB', - borderColor: Zeta.of(context).colors.green, - upperBadge: ZetaAvatarBadge.notification(value: 3), - lowerBadge: ZetaAvatarBadge.icon(), - ), - const SizedBox(height: 20), - ], - )) - .toList(), - ), - ], - ), - ], - ), - ].divide(const SizedBox(height: ZetaSpacing.xl_2)).toList(), + const SizedBox(height: 20), + ], + ); + }).toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + image: image, + upperBadge: ZetaAvatarBadge.notification(value: 3), + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.image( + size: size, + image: image, + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaAvatarBadge.notification(value: 3), + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + upperBadge: ZetaAvatarBadge.notification(value: 3), + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + const SizedBox(width: 12), + Column( + children: ZetaAvatarSize.values + .map((size) => Column( + children: [ + ZetaAvatar.initials( + size: size, + initials: 'AB', + borderColor: Zeta.of(context).colors.green, + upperBadge: ZetaAvatarBadge.notification(value: 3), + lowerBadge: ZetaAvatarBadge.icon(), + ), + const SizedBox(height: 20), + ], + )) + .toList(), + ), + ], + ), + ], + ), + ].divide(const SizedBox(height: ZetaSpacing.xl_2)).toList(), + ), ), ), ); diff --git a/lib/src/components/avatars/avatar.dart b/lib/src/components/avatars/avatar.dart index 50a6a967..f5ccc2a7 100644 --- a/lib/src/components/avatars/avatar.dart +++ b/lib/src/components/avatars/avatar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; + import '../../../zeta_flutter.dart'; /// [ZetaAvatar] size @@ -47,6 +48,7 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticLabel = 'avatar', this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', + this.initialTextStyle, }); /// Constructor for [ZetaAvatar] with image. @@ -61,7 +63,8 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', }) : backgroundColor = null, - initials = null; + initials = null, + initialTextStyle = null; /// Constructor for [ZetaAvatar] with initials. const ZetaAvatar.initials({ @@ -75,6 +78,7 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticLabel = 'avatar', this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', + this.initialTextStyle, }) : image = null; /// Constructor for [ZetaAvatar] with initials from a full name. @@ -89,6 +93,7 @@ class ZetaAvatar extends ZetaStatelessWidget { this.semanticLabel = 'avatar', this.semanticUpperBadgeLabel = 'upperBadge', this.semanticLowerBadgeLabel = 'lowerBadge', + this.initialTextStyle, }) : image = null, initials = name.initials; @@ -132,6 +137,19 @@ class ZetaAvatar extends ZetaStatelessWidget { /// {@macro zeta-widget-semantic-label} final String semanticUpperBadgeLabel; + /// Text style for initials. + /// + /// Defaults to: + /// ```dart + /// TextStyle( + /// fontSize: size.fontSize, + /// letterSpacing: ZetaSpacing.none, + /// color: backgroundColor?.onColor, + /// fontWeight: FontWeight.w500, + /// ) + /// ``` + final TextStyle? initialTextStyle; + /// Return copy of avatar with certain changed fields ZetaAvatar copyWith({ ZetaAvatarSize? size, @@ -168,12 +186,13 @@ class ZetaAvatar extends ZetaStatelessWidget { ? Center( child: Text( size == ZetaAvatarSize.xs ? initials!.substring(0, 1) : initials!, - style: TextStyle( - fontSize: size.fontSize, - letterSpacing: ZetaSpacing.none, - color: backgroundColor?.onColor, - fontWeight: FontWeight.w500, - ), + style: initialTextStyle ?? + TextStyle( + fontSize: size.fontSize, + letterSpacing: ZetaSpacing.none, + color: backgroundColor?.onColor, + fontWeight: FontWeight.w500, + ), ), ) : null); @@ -261,7 +280,8 @@ class ZetaAvatar extends ZetaStatelessWidget { ..add(ColorProperty('statusColor', borderColor)) ..add(StringProperty('semanticUpperBadgeValue', semanticUpperBadgeLabel)) ..add(StringProperty('semanticValue', semanticLabel)) - ..add(StringProperty('semanticLowerBadgeValue', semanticLowerBadgeLabel)); + ..add(StringProperty('semanticLowerBadgeValue', semanticLowerBadgeLabel)) + ..add(DiagnosticsProperty('initialTextStyle', initialTextStyle)); } } diff --git a/lib/src/zeta.dart b/lib/src/zeta.dart index 1c7ec703..59103626 100644 --- a/lib/src/zeta.dart +++ b/lib/src/zeta.dart @@ -79,7 +79,8 @@ class Zeta extends InheritedWidget { return oldWidget.contrast != contrast || oldWidget.themeMode != themeMode || oldWidget.themeData != themeData || - oldWidget._mediaBrightness != _mediaBrightness; + oldWidget._mediaBrightness != _mediaBrightness || + oldWidget.rounded != rounded; } /// Fetches the [Zeta] instance from the provided [context]. diff --git a/lib/src/zeta_provider.dart b/lib/src/zeta_provider.dart index 92eeb198..4f4cbe97 100644 --- a/lib/src/zeta_provider.dart +++ b/lib/src/zeta_provider.dart @@ -1,17 +1,28 @@ +// ignore_for_file: prefer_function_declarations_over_variables + import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'theme/contrast.dart'; -import 'theme/theme_data.dart'; -import 'theme/theme_service.dart'; -import 'zeta.dart'; +import '../zeta_flutter.dart'; -/// A typedef for the ZetaAppBuilder function which takes [BuildContext], [ZetaThemeData], +/// A typedef for the ZetaAppBuilder function which passes [BuildContext], [ZetaThemeData], /// and [ThemeMode] and returns a [Widget]. typedef ZetaAppBuilder = Widget Function(BuildContext context, ZetaThemeData themeData, ThemeMode themeMode); +/// A typedef for the ZetaAppBuilder function which passes [BuildContext], light [ThemeData], +/// dark [ThemeData] and [ThemeMode] and returns a [Widget]. +typedef ZetaBaseAppBuilder = Widget Function( + BuildContext context, + ThemeData light, + ThemeData dark, + ThemeMode themeMode, +); + +final _emptyBuilder = (_, __, ___) => const Nothing(); +final _emptyBase = (_, __, ___, ____) => const Nothing(); + /// A widget that provides Zeta theming and contrast data down the widget tree. class ZetaProvider extends StatefulWidget with Diagnosticable { /// Constructs a [ZetaProvider] widget. @@ -19,13 +30,33 @@ class ZetaProvider extends StatefulWidget with Diagnosticable { /// The [builder] argument is required. The [initialThemeMode], [initialContrast], /// and [initialThemeData] arguments provide initial values. ZetaProvider({ + super.key, required this.builder, this.initialThemeMode = ThemeMode.system, this.initialContrast = ZetaContrast.aa, - this.themeService, ZetaThemeData? initialThemeData, + this.themeService, + this.initialRounded = true, + }) : initialZetaThemeData = initialThemeData ?? ZetaThemeData(), + baseBuilder = _emptyBase, + initialThemeData = null; + + /// ZetaProvider constructor that returns light and dark [ThemeData]s ready to be consumed. + /// + /// The [builder] argument is required. The [initialThemeMode], [initialContrast], + /// and [initialThemeData] arguments provide initial values. + ZetaProvider.base({ super.key, - }) : initialThemeData = initialThemeData ?? ZetaThemeData(); + required ZetaBaseAppBuilder builder, + this.initialThemeMode = ThemeMode.system, + this.initialContrast = ZetaContrast.aa, + ZetaThemeData? initialZetaThemeData, + this.initialThemeData, + this.initialRounded = true, + }) : baseBuilder = builder, + initialZetaThemeData = initialZetaThemeData ?? ZetaThemeData(), + builder = _emptyBuilder, + themeService = null; /// Specifies the initial theme mode for the app. /// @@ -37,7 +68,7 @@ class ZetaProvider extends StatefulWidget with Diagnosticable { /// /// This contains all the theming information. If not provided, /// it defaults to a basic [ZetaThemeData] instance. - final ZetaThemeData initialThemeData; + final ZetaThemeData initialZetaThemeData; /// Specifies the initial contrast setting for the app. /// @@ -50,11 +81,22 @@ class ZetaProvider extends StatefulWidget with Diagnosticable { /// and is expected to return a [Widget]. final ZetaAppBuilder builder; + /// A builder function to construct the widget tree using the provided theming information. + /// + /// This builder returns light and dark [ThemeData]s ready to be consumed. + final ZetaBaseAppBuilder baseBuilder; + /// A `ZetaThemeService` /// /// It provides the structure for loading and saving themes in Zeta application. final ZetaThemeService? themeService; + /// [ThemeData] used in [ZetaProvider.base] constructor as the foundation for a custom theme. + final ThemeData? initialThemeData; + + /// Sets whether app should start with components in their rounded or sharp variants. + final bool initialRounded; + @override State createState() => ZetaProviderState(); @@ -62,11 +104,14 @@ class ZetaProvider extends StatefulWidget with Diagnosticable { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties - ..add(DiagnosticsProperty('themeData', initialThemeData)) + ..add(DiagnosticsProperty('themeData', initialZetaThemeData)) ..add(ObjectFlagProperty.has('builder', builder)) ..add(EnumProperty('initialThemeMode', initialThemeMode)) ..add(EnumProperty('initialContrast', initialContrast)) - ..add(DiagnosticsProperty('themeService', themeService)); + ..add(DiagnosticsProperty('themeService', themeService)) + ..add(ObjectFlagProperty.has('customBuilder', baseBuilder)) + ..add(DiagnosticsProperty('initialThemeData', initialThemeData)) + ..add(DiagnosticsProperty('initialRounded', initialRounded)); } /// Retrieves the [ZetaProviderState] from the provided context. @@ -110,6 +155,8 @@ class ZetaProviderState extends State with Diagnosticable, Widgets /// Represents the late initialization of the system's current brightness (dark or light mode). late Brightness _platformBrightness; + late bool _rounded; + /// Represents a nullable brightness value to be used for brightness change debouncing. Brightness? _debounceBrightness; @@ -137,8 +184,11 @@ class ZetaProviderState extends State with Diagnosticable, Widgets // Set the initial contrast. _contrast = widget.initialContrast; + // Sets the initial rounded. + _rounded = widget.initialRounded; + // Apply the initial contrast to the theme data. - _themeData = widget.initialThemeData.apply(contrast: _contrast); + _themeData = widget.initialZetaThemeData.apply(contrast: _contrast); } /// Clean up function to be called when this object is removed from the tree. @@ -188,10 +238,33 @@ class ZetaProviderState extends State with Diagnosticable, Widgets @override Widget build(BuildContext context) { + if (widget.baseBuilder != _emptyBase) { + return Zeta( + themeMode: _themeMode, + themeData: _themeData, + contrast: _contrast, + mediaBrightness: _platformBrightness, + rounded: _rounded, + child: widget.baseBuilder( + context, + generateZetaTheme( + brightness: Brightness.light, + existingTheme: ThemeData(colorScheme: _themeData.colorsLight.toScheme()), + ), + generateZetaTheme( + brightness: Brightness.dark, + existingTheme: ThemeData(colorScheme: _themeData.colorsDark.toScheme()), + ), + _themeMode, + ), + ); + } + return Zeta( themeMode: _themeMode, themeData: _themeData, contrast: _contrast, + rounded: _rounded, mediaBrightness: _platformBrightness, child: widget.builder(context, _themeData, _themeMode), ); @@ -202,11 +275,13 @@ class ZetaProviderState extends State with Diagnosticable, Widgets super.didUpdateWidget(oldWidget); if (oldWidget.initialContrast != widget.initialContrast || oldWidget.initialThemeMode != widget.initialThemeMode || - oldWidget.initialThemeData != widget.initialThemeData) { + oldWidget.initialThemeData != widget.initialThemeData || + oldWidget.initialRounded != widget.initialRounded) { setState(() { _themeMode = widget.initialThemeMode; _contrast = widget.initialContrast; - _themeData = widget.initialThemeData.apply(contrast: _contrast); + _themeData = widget.initialZetaThemeData.apply(contrast: _contrast); + _rounded = widget.initialRounded; }); } } @@ -236,6 +311,16 @@ class ZetaProviderState extends State with Diagnosticable, Widgets }); } + /// Updates the current rounded. + // ignore: avoid_positional_boolean_parameters + void updateRounded(bool rounded) { + //TODO: This is not triggering rebuild + setState(() { + _rounded = rounded; + _saveThemeChange(); + }); + } + void _saveThemeChange() { unawaited( widget.themeService?.saveTheme( @@ -255,3 +340,20 @@ class ZetaProviderState extends State with Diagnosticable, Widgets ..add(EnumProperty('themeMode', _themeMode)); } } + +/// Creates a variant of [ThemeData] with added [Zeta] styles. +ThemeData generateZetaTheme({ + required Brightness brightness, + ThemeData? existingTheme, + String? fontFamily, +}) { + return ThemeData( + useMaterial3: existingTheme?.useMaterial3 ?? true, + fontFamily: fontFamily ?? kZetaFontFamily, + brightness: existingTheme?.brightness ?? brightness, + scaffoldBackgroundColor: existingTheme?.colorScheme.surfaceTertiary, + colorScheme: existingTheme?.colorScheme, + textTheme: existingTheme?.textTheme ?? zetaTextTheme, + iconTheme: existingTheme?.iconTheme ?? const IconThemeData(size: 20), + ); +} diff --git a/test/test_utils/test_app.dart b/test/test_utils/test_app.dart index 6e2396fb..a84dc976 100644 --- a/test/test_utils/test_app.dart +++ b/test/test_utils/test_app.dart @@ -20,21 +20,13 @@ class TestApp extends StatelessWidget { @override Widget build(BuildContext context) { - return ZetaProvider( + return ZetaProvider.base( initialThemeMode: themeMode ?? ThemeMode.system, - initialThemeData: ZetaThemeData(rounded: rounded ?? true), - builder: (context, themeData, themeMode) { + builder: (context, lightTheme, darkTheme, themeMode) { return MaterialApp( - theme: ThemeData( - fontFamily: themeData.fontFamily, - colorScheme: themeData.colorsLight.toScheme(), - textTheme: zetaTextTheme, - ), - darkTheme: ThemeData( - fontFamily: themeData.fontFamily, - colorScheme: themeData.colorsDark.toScheme(), - textTheme: zetaTextTheme, - ), + theme: lightTheme, + darkTheme: darkTheme, + themeMode: themeMode, home: Scaffold( body: removeBody ? home