From 74cc65fe163195576f46295474f3ceb507f374bd Mon Sep 17 00:00:00 2001 From: Koen Van Looveren Date: Sun, 18 Feb 2024 10:14:48 +0100 Subject: [PATCH] fix: Added extra components & a more complete UI library --- CHANGELOG.md | 26 +++++ README.md | 12 ++ example/assets/icons/check.svg | 3 + example/assets/icons/chevron_right.svg | 3 + example/lib/main.dart | 10 +- .../src/screen/components/buttons_screen.dart | 42 +++---- .../screen/components/list_item_screen.dart | 44 ++++++++ .../components/list_item_title_screen.dart | 22 ++++ .../screen/components/listview_screen.dart | 48 ++++---- .../components/loading_indicator_screen.dart | 16 +-- .../selectable_list_item_screen.dart | 38 +++++++ .../components/separated_column_screen.dart | 41 +++++++ example/lib/src/screen/components_screen.dart | 76 +++++++++---- example/lib/src/screen/home_screen.dart | 3 +- lib/impaktfull_ui.dart | 4 + .../components/icon/impaktfull_svg_icon.dart | 4 +- .../list_item/impaktfull_list_item.dart | 103 ++++++++++++++++++ .../list_item/impaktfull_list_item_title.dart | 27 +++++ .../impaktfull_selectable_list_item.dart | 62 +++++++++++ .../listview/impaktfull_listview.dart | 3 +- .../screen/impaktfull_navbar_action.dart | 4 +- .../components/screen/impaktfull_screen.dart | 2 +- .../impaktfull_separated_column.dart | 34 ++++++ lib/src/theme/impaktfull_branding.dart | 3 +- lib/src/theme/impaktfull_theme.dart | 100 +++++++++++++++-- 25 files changed, 636 insertions(+), 94 deletions(-) create mode 100644 example/assets/icons/check.svg create mode 100644 example/assets/icons/chevron_right.svg create mode 100644 example/lib/src/screen/components/list_item_screen.dart create mode 100644 example/lib/src/screen/components/list_item_title_screen.dart create mode 100644 example/lib/src/screen/components/selectable_list_item_screen.dart create mode 100644 example/lib/src/screen/components/separated_column_screen.dart create mode 100644 lib/src/components/list_item/impaktfull_list_item.dart create mode 100644 lib/src/components/list_item/impaktfull_list_item_title.dart create mode 100644 lib/src/components/list_item/impaktfull_selectable_list_item.dart create mode 100644 lib/src/components/separated_column/impaktfull_separated_column.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef0215..22a2200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# 0.0.3 + +## Feat + +- Added a couple new components: + +``` + ImpaktfullListItem + ImpaktfullListItemTitle + ImpaktfullSelectableListItem + ImpaktfullSeparatedColumn +``` + +- Added a couple new default icons: + +``` + assets/icons/check.svg + assets/icons/chevron_right.svg +``` + +- Added support for custom durations + +## Refactor + +- Some onCard is now onCardPrimary and we added onCardSecondary + # 0.0.2 ## Feat diff --git a/README.md b/README.md index 90deb54..cd1c67c 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,21 @@ Components are always prefixed with `Impaktfull` to avoid conflicts with other l - ImpaktfullScreen - ImpaktfullSeparator - ImpaktfullTouchFeedback +- ImpaktfullListItem +- ImpaktfullListItemTitle +- ImpaktfullSelectableListItem +- ImpaktfullSeparatedColumn Many more to come in the future, always with the focus on minimizing maintenance and maximizing a recognizable UI/brand for Impaktfull +## Default Icons + +These icons should be added to your assets folder to use the default icons. (in your own project) + +- assets/icons/arrow_left.svg +- assets/icons/check.svg +- assets/icons/chevron_right.svg + ## License You are free to use this library as long as you give credit to Impaktfull. You can use it for commercial and non-commercial projects. See the [LICENSE](LICENSE) file for more information. \ No newline at end of file diff --git a/example/assets/icons/check.svg b/example/assets/icons/check.svg new file mode 100644 index 0000000..b845540 --- /dev/null +++ b/example/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/assets/icons/chevron_right.svg b/example/assets/icons/chevron_right.svg new file mode 100644 index 0000000..cd012fb --- /dev/null +++ b/example/assets/icons/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/example/lib/main.dart b/example/lib/main.dart index 2d69542..d0610f8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,9 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; import 'package:impaktfull_ui_example/src/screen/home_screen.dart'; -void main() => runApp(const ImpaktfullApp( - title: 'Impaktfull UI Example', - home: HomeScreen(), - )); +void main() => runApp( + const ImpaktfullApp( + title: 'Impaktfull UI Example', + home: HomeScreen(), + ), + ); diff --git a/example/lib/src/screen/components/buttons_screen.dart b/example/lib/src/screen/components/buttons_screen.dart index f2e2d33..66dc475 100644 --- a/example/lib/src/screen/components/buttons_screen.dart +++ b/example/lib/src/screen/components/buttons_screen.dart @@ -8,26 +8,28 @@ class ButtonsScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return ImpaktfullScreen( - title: 'Components - Buttons', - onBackTapped: () => Navigator.of(context).pop(), - child: ImpaktfullListView( - children: [ - ImpaktfullButton.primary( - label: 'Primary Button', - onTap: () => SnackyUtil.show('On Primary tapped'), - ), - const SizedBox(height: 8), - ImpaktfullButton.secondary( - label: 'Secondary Button', - onTap: () => SnackyUtil.show('On Secondary tapped'), - ), - const SizedBox(height: 8), - ImpaktfullButton.accent( - label: 'Accent Button', - onTap: () => SnackyUtil.show('On Accent tapped'), - ), - ], + return ImpaktfullThemeLocalizer( + builder: (context, teme) => ImpaktfullScreen( + title: 'Components - Buttons', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + children: [ + ImpaktfullButton.primary( + label: 'Primary Button', + onTap: () => SnackyUtil.show('On Primary tapped'), + ), + const SizedBox(height: 8), + ImpaktfullButton.secondary( + label: 'Secondary Button', + onTap: () => SnackyUtil.show('On Secondary tapped'), + ), + const SizedBox(height: 8), + ImpaktfullButton.accent( + label: 'Accent Button', + onTap: () => SnackyUtil.show('On Accent tapped'), + ), + ], + ), ), ); } diff --git a/example/lib/src/screen/components/list_item_screen.dart b/example/lib/src/screen/components/list_item_screen.dart new file mode 100644 index 0000000..a807f8b --- /dev/null +++ b/example/lib/src/screen/components/list_item_screen.dart @@ -0,0 +1,44 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:impaktfull_ui_example/src/util/snacky_uitl.dart'; + +class ListItemScreen extends StatelessWidget { + const ListItemScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - List Item', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + children: [ + const ImpaktfullListItem(title: 'Some title'), + const ImpaktfullListItem( + title: 'Some title', + subTitle: 'Some subtitle', + ), + ImpaktfullListItem( + title: 'Some title', + subTitle: 'Some subtitle', + onTap: () => SnackyUtil.show('on tap'), + ), + ImpaktfullListItem( + title: 'Some title', + subTitle: 'Some subtitle', + onAsyncTap: () async { + await Future.delayed(const Duration(seconds: 2)); + }, + ), + ImpaktfullListItem( + title: 'Some title', + subTitle: 'Some subtitle', + leadingAsset: theme.assets.icons.check, + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/list_item_title_screen.dart b/example/lib/src/screen/components/list_item_title_screen.dart new file mode 100644 index 0000000..d7d85ab --- /dev/null +++ b/example/lib/src/screen/components/list_item_title_screen.dart @@ -0,0 +1,22 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class ListItemTitleScreen extends StatelessWidget { + const ListItemTitleScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - List Item Title', + onBackTapped: () => Navigator.of(context).pop(), + child: const ImpaktfullListView( + children: [ + ImpaktfullListItemTitle(title: 'Some title'), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/listview_screen.dart b/example/lib/src/screen/components/listview_screen.dart index 8ec566b..484ee28 100644 --- a/example/lib/src/screen/components/listview_screen.dart +++ b/example/lib/src/screen/components/listview_screen.dart @@ -10,29 +10,31 @@ class ListViewScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return ImpaktfullScreen( - title: 'Components - ListView', - onBackTapped: () => Navigator.of(context).pop(), - child: ImpaktfullListView( - children: [ - ImpaktfullButton.primary( - label: 'Children', - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewChildren())), - ), - const SizedBox(height: 8), - ImpaktfullButton.primary( - label: 'Item Builder', - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewItemBuilder())), - ), - const SizedBox(height: 8), - ImpaktfullButton.primary( - label: 'Item Separated Builder', - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewItemSeparatedBuilder())), - ), - ], + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - ListView', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + children: [ + ImpaktfullButton.primary( + label: 'Children', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListViewChildren())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'Item Builder', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListViewItemBuilder())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'Item Separated Builder', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListViewItemSeparatedBuilder())), + ), + ], + ), ), ); } diff --git a/example/lib/src/screen/components/loading_indicator_screen.dart b/example/lib/src/screen/components/loading_indicator_screen.dart index 58bc4a0..4191556 100644 --- a/example/lib/src/screen/components/loading_indicator_screen.dart +++ b/example/lib/src/screen/components/loading_indicator_screen.dart @@ -7,13 +7,15 @@ class LoadingIndicatorScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return ImpaktfullScreen( - title: 'Components - Loading Indicator', - onBackTapped: () => Navigator.of(context).pop(), - child: const ImpaktfullListView( - children: [ - ImpaktfullLoadingIndicator(), - ], + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - Loading Indicator', + onBackTapped: () => Navigator.of(context).pop(), + child: const ImpaktfullListView( + children: [ + ImpaktfullLoadingIndicator(), + ], + ), ), ); } diff --git a/example/lib/src/screen/components/selectable_list_item_screen.dart b/example/lib/src/screen/components/selectable_list_item_screen.dart new file mode 100644 index 0000000..7fdef11 --- /dev/null +++ b/example/lib/src/screen/components/selectable_list_item_screen.dart @@ -0,0 +1,38 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:impaktfull_ui_example/src/util/snacky_uitl.dart'; + +class SelectableListItemScreen extends StatelessWidget { + const SelectableListItemScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - Selectable List Item', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + children: [ + ImpaktfullSelectableListItem( + title: 'Some title', + isSelected: true, + onTap: () => SnackyUtil.show('On tap'), + ), + ImpaktfullSelectableListItem( + title: 'Some title', + isSelected: false, + onTap: () => SnackyUtil.show('On tap'), + ), + ImpaktfullSelectableListItem( + title: 'Some title', + isSelected: true, + leadingAsset: theme.assets.icons.arrowLeft, + onTap: () => SnackyUtil.show('On tap'), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/separated_column_screen.dart b/example/lib/src/screen/components/separated_column_screen.dart new file mode 100644 index 0000000..c94940f --- /dev/null +++ b/example/lib/src/screen/components/separated_column_screen.dart @@ -0,0 +1,41 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class SeparatedColumnScreen extends StatelessWidget { + const SeparatedColumnScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - SeparatedColumn', + onBackTapped: () => Navigator.of(context).pop(), + child: const ImpaktfullListView( + children: [ + ImpaktfullSeparatedColumn( + children: [ + Text('Item 1'), + Text('Item 2'), + Text('Item 3'), + Text('Item 4'), + Text('Item 5'), + ], + ), + SizedBox(height: 8), + ImpaktfullSeparatedColumn( + type: ImpaktfullSeparatorType.card, + children: [ + Text('Item 1'), + Text('Item 2'), + Text('Item 3'), + Text('Item 4'), + Text('Item 5'), + ], + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components_screen.dart b/example/lib/src/screen/components_screen.dart index c6e5cfe..6ca7209 100644 --- a/example/lib/src/screen/components_screen.dart +++ b/example/lib/src/screen/components_screen.dart @@ -1,7 +1,11 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; import 'package:impaktfull_ui_example/src/screen/components/buttons_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/list_item_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/list_item_title_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/listview_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/loading_indicator_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/selectable_list_item_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/separated_column_screen.dart'; class ComponentsScreen extends StatelessWidget { const ComponentsScreen({ @@ -10,29 +14,55 @@ class ComponentsScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return ImpaktfullScreen( - title: 'Components', - onBackTapped: () => Navigator.of(context).pop(), - child: ImpaktfullListView( - children: [ - ImpaktfullButton.primary( - label: 'Buttons', - onTap: () => Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const ButtonsScreen())), - ), - const SizedBox(height: 8), - ImpaktfullButton.primary( - label: 'ListViews', - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewScreen())), - ), - const SizedBox(height: 8), - ImpaktfullButton.primary( - label: 'LoadingIndicator', - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const LoadingIndicatorScreen())), - ), - ], + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + children: [ + ImpaktfullButton.primary( + label: 'Buttons', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ButtonsScreen())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'ListViews', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListViewScreen())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'List Item Title', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListItemTitleScreen())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'List Item', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListItemScreen())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'Selectable List Item', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const SelectableListItemScreen())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'SeparatedColumn', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const SeparatedColumnScreen())), + ), + const SizedBox(height: 8), + ImpaktfullButton.primary( + label: 'LoadingIndicator', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const LoadingIndicatorScreen())), + ), + ], + ), ), ); } diff --git a/example/lib/src/screen/home_screen.dart b/example/lib/src/screen/home_screen.dart index 7b63c29..88e2c55 100644 --- a/example/lib/src/screen/home_screen.dart +++ b/example/lib/src/screen/home_screen.dart @@ -14,8 +14,7 @@ class HomeScreen extends StatelessWidget { children: [ ImpaktfullButton.primary( label: 'Components', - onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ComponentsScreen())), + onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ComponentsScreen())), ), ], ), diff --git a/lib/impaktfull_ui.dart b/lib/impaktfull_ui.dart index 6b13ba5..f8d9c55 100644 --- a/lib/impaktfull_ui.dart +++ b/lib/impaktfull_ui.dart @@ -2,9 +2,13 @@ export 'src/components/auto_layout/impaktfull_auto_layout.dart'; export 'src/components/button/impaktfull_button.dart'; export 'src/components/icon/impaktfull_svg_icon.dart'; export 'src/components/listview/impaktfull_listview.dart'; +export 'src/components/list_item/impaktfull_list_item_title.dart'; +export 'src/components/list_item/impaktfull_list_item.dart'; +export 'src/components/list_item/impaktfull_selectable_list_item.dart'; export 'src/components/loading/impaktfull_loading.dart'; export 'src/components/screen/impaktfull_screen.dart'; export 'src/components/screen/impaktfull_statusbar.dart'; +export 'src/components/separated_column/impaktfull_separated_column.dart'; export 'src/components/separator/impaktfull_separator.dart'; export 'src/components/touch_feedback/impaktfull_touch_feedback.dart'; export 'src/theme/impaktfull_theme_localizer.dart'; diff --git a/lib/src/components/icon/impaktfull_svg_icon.dart b/lib/src/components/icon/impaktfull_svg_icon.dart index 768bd6c..2137930 100644 --- a/lib/src/components/icon/impaktfull_svg_icon.dart +++ b/lib/src/components/icon/impaktfull_svg_icon.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -class SvgIcon extends StatelessWidget { +class ImpaktfullSvgIcon extends StatelessWidget { final String asset; final int? size; final Color? color; - const SvgIcon({ + const ImpaktfullSvgIcon({ required this.asset, this.size, this.color, diff --git a/lib/src/components/list_item/impaktfull_list_item.dart b/lib/src/components/list_item/impaktfull_list_item.dart new file mode 100644 index 0000000..a4cda85 --- /dev/null +++ b/lib/src/components/list_item/impaktfull_list_item.dart @@ -0,0 +1,103 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:impaktfull_ui/src/components/auto_layout/impaktfull_auto_layout.dart'; +import 'package:impaktfull_ui/src/components/icon/impaktfull_svg_icon.dart'; +import 'package:impaktfull_ui/src/components/loading/impaktfull_loading.dart'; +import 'package:impaktfull_ui/src/components/touch_feedback/impaktfull_touch_feedback.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; + +class ImpaktfullListItem extends StatefulWidget { + final String title; + final String? leadingAsset; + final String? subTitle; + final VoidCallback? onTap; + final AsyncCallback? onAsyncTap; + + const ImpaktfullListItem({ + required this.title, + this.leadingAsset, + this.onTap, + this.onAsyncTap, + this.subTitle, + super.key, + }); + + @override + State createState() => _ImpaktfullListItemState(); +} + +class _ImpaktfullListItemState extends State { + var _isLoading = false; + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullTouchFeedback( + onTap: widget.onAsyncTap != null ? _onAsyncTap : widget.onTap, + child: ImpaktfullAutoLayout.horizontal( + crossAxisAlignment: CrossAxisAlignment.center, + backgroundColor: theme.colors.card, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + spacing: 8, + children: [ + if (widget.leadingAsset != null) ...[ + ImpaktfullSvgIcon( + asset: widget.leadingAsset!, + size: 16, + color: theme.colors.primary, + ), + ], + Expanded( + child: ImpaktfullAutoLayout.vertical( + spacing: 2, + children: [ + Text( + widget.title, + style: theme.textStyles.onCardPrimary.body, + ), + if (widget.subTitle != null) ...[ + Text( + widget.subTitle!, + style: theme.textStyles.onCardSecondary.body, + ), + ], + ], + ), + ), + if (widget.onTap != null || widget.onAsyncTap != null) ...[ + if (_isLoading) ...[ + SizedBox( + width: widget.subTitle == null ? 24 : 32, + height: widget.subTitle == null ? 24 : 32, + child: const ImpaktfullLoadingIndicator(), + ), + ] else ...[ + ImpaktfullSvgIcon( + asset: theme.assets.icons.chevronRight, + color: theme.colors.primary, + ), + ], + ], + ], + ), + ), + ); + } + + Future _onAsyncTap() async { + _isLoading = true; + setState(() {}); + try { + await widget.onAsyncTap!(); + _isLoading = false; + setState(() {}); + } catch (error) { + _isLoading = false; + setState(() {}); + rethrow; + } + } +} diff --git a/lib/src/components/list_item/impaktfull_list_item_title.dart b/lib/src/components/list_item/impaktfull_list_item_title.dart new file mode 100644 index 0000000..2f0ffa4 --- /dev/null +++ b/lib/src/components/list_item/impaktfull_list_item_title.dart @@ -0,0 +1,27 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class ImpaktfullListItemTitle extends StatelessWidget { + final String title; + + const ImpaktfullListItemTitle({ + required this.title, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => Container( + padding: EdgeInsets.only( + top: theme.dimens.listViewVerticalPadding, + right: theme.dimens.listViewHorizontalPadding, + left: theme.dimens.listViewHorizontalPadding, + ), + child: Text( + title, + style: theme.textStyles.onCanvasPrimary.titleSmall, + ), + ), + ); + } +} diff --git a/lib/src/components/list_item/impaktfull_selectable_list_item.dart b/lib/src/components/list_item/impaktfull_selectable_list_item.dart new file mode 100644 index 0000000..ea57af3 --- /dev/null +++ b/lib/src/components/list_item/impaktfull_selectable_list_item.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:impaktfull_ui/src/components/auto_layout/impaktfull_auto_layout.dart'; +import 'package:impaktfull_ui/src/components/icon/impaktfull_svg_icon.dart'; +import 'package:impaktfull_ui/src/components/touch_feedback/impaktfull_touch_feedback.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; + +class ImpaktfullSelectableListItem extends StatelessWidget { + final String title; + final String? leadingAsset; + final bool isSelected; + final VoidCallback onTap; + + const ImpaktfullSelectableListItem({ + required this.title, + required this.isSelected, + required this.onTap, + this.leadingAsset, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullTouchFeedback( + onTap: onTap, + child: ImpaktfullAutoLayout.horizontal( + crossAxisAlignment: CrossAxisAlignment.center, + backgroundColor: theme.colors.card, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + spacing: 8, + children: [ + if (leadingAsset != null) ...[ + ImpaktfullSvgIcon( + asset: leadingAsset!, + size: 16, + color: theme.colors.primary, + ), + ], + Expanded( + child: Text( + title, + style: theme.textStyles.onCardPrimary.body, + ), + ), + AnimatedOpacity( + opacity: isSelected ? 1 : 0, + duration: theme.durations.short, + curve: Curves.easeInOut, + child: ImpaktfullSvgIcon( + asset: theme.assets.icons.check, + color: theme.colors.accent1, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/components/listview/impaktfull_listview.dart b/lib/src/components/listview/impaktfull_listview.dart index bf66b15..c019cdf 100644 --- a/lib/src/components/listview/impaktfull_listview.dart +++ b/lib/src/components/listview/impaktfull_listview.dart @@ -77,8 +77,7 @@ class ImpaktfullListView extends StatelessWidget { return ListView.separated( padding: padding, itemBuilder: (context, index) => itemBuilder!(context, items![index]), - separatorBuilder: (context, index) => - ImpaktfullSeparator(type: separatorType!), + separatorBuilder: (context, index) => ImpaktfullSeparator(type: separatorType!), itemCount: items!.length, ); } diff --git a/lib/src/components/screen/impaktfull_navbar_action.dart b/lib/src/components/screen/impaktfull_navbar_action.dart index cb043fa..86432b7 100644 --- a/lib/src/components/screen/impaktfull_navbar_action.dart +++ b/lib/src/components/screen/impaktfull_navbar_action.dart @@ -17,7 +17,7 @@ class ImpaktfullNavbarAction extends StatelessWidget { Widget build(BuildContext context) { if (Platform.isAndroid) { return IconButton( - icon: SvgIcon( + icon: ImpaktfullSvgIcon( asset: svgIcon, ), onPressed: onTap, @@ -29,7 +29,7 @@ class ImpaktfullNavbarAction extends StatelessWidget { color: Colors.transparent, child: Padding( padding: const EdgeInsets.all(12), - child: SvgIcon( + child: ImpaktfullSvgIcon( asset: svgIcon, ), ), diff --git a/lib/src/components/screen/impaktfull_screen.dart b/lib/src/components/screen/impaktfull_screen.dart index 28a4fe6..c3aab1b 100644 --- a/lib/src/components/screen/impaktfull_screen.dart +++ b/lib/src/components/screen/impaktfull_screen.dart @@ -47,7 +47,7 @@ class ImpaktfullScreen extends StatelessWidget { const SizedBox(width: 4), if (onBackTapped != null) ...[ ImpaktfullNavbarAction( - svgIcon: theme.assets.icons.iconArrowLeft, + svgIcon: theme.assets.icons.arrowLeft, onTap: onBackTapped, ), ] else ...[ diff --git a/lib/src/components/separated_column/impaktfull_separated_column.dart b/lib/src/components/separated_column/impaktfull_separated_column.dart new file mode 100644 index 0000000..17301cc --- /dev/null +++ b/lib/src/components/separated_column/impaktfull_separated_column.dart @@ -0,0 +1,34 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class ImpaktfullSeparatedColumn extends StatelessWidget { + final List children; + final ImpaktfullSeparatorType type; + + const ImpaktfullSeparatedColumn({ + required this.children, + this.type = ImpaktfullSeparatorType.canvas, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(theme.dimens.generalBorderRadius), + color: type == ImpaktfullSeparatorType.canvas ? Colors.transparent : theme.colors.card, + ), + child: Column( + children: children.isEmpty + ? [] + : [ + for (int i = 0; i < children.length; i++) ...[ + if (i > 0) ImpaktfullSeparator(type: type), + children[i], + ], + ], + ), + ), + ); + } +} diff --git a/lib/src/theme/impaktfull_branding.dart b/lib/src/theme/impaktfull_branding.dart index 452f125..4720df7 100644 --- a/lib/src/theme/impaktfull_branding.dart +++ b/lib/src/theme/impaktfull_branding.dart @@ -14,7 +14,8 @@ class ImpaktfullBranding { static const textOnCanvasPrimary = primary; static const textOnCanvasSecondary = Color(0xFF7A99AC); static const textOnPrimary = Color(0xFFFFFFFF); - static const textOnCard = textOnCanvasPrimary; + static const textOnCardPrimary = textOnCanvasPrimary; + static const textOnCardSecondary = textOnCanvasSecondary; static const textOnAccent1 = Color(0xFFFFFFFF); static const textOnAccent2 = Color(0xFFFFFFFF); diff --git a/lib/src/theme/impaktfull_theme.dart b/lib/src/theme/impaktfull_theme.dart index 676397e..d30f670 100644 --- a/lib/src/theme/impaktfull_theme.dart +++ b/lib/src/theme/impaktfull_theme.dart @@ -10,6 +10,7 @@ class ImpaktfullTheme { ImpaktfullShadowTheme? shadows, ImpaktfullAssets? assets, ImpaktfullDimens? dimens, + ImpaktfullDurations? durations, }) => ImpaktfullTheme( colors: const ImpaktfullColorTheme( @@ -52,11 +53,74 @@ class ImpaktfullTheme { ImpaktfullBranding.textOnPrimary, ImpaktfullBranding.font), onAccent1: ImpaktfullTextStyleTheme.getTextStyleTheme( ImpaktfullBranding.textOnAccent1, ImpaktfullBranding.font), - onCard: ImpaktfullTextStyleTheme.getTextStyleTheme( - ImpaktfullBranding.textOnCard, ImpaktfullBranding.font), + onCardPrimary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnCardPrimary, ImpaktfullBranding.font), + onCardSecondary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnCardSecondary, ImpaktfullBranding.font), ), assets: assets ?? ImpaktfullAssets.getDefaults(), dimens: dimens ?? ImpaktfullDimens.getDefaults(), + durations: durations ?? ImpaktfullDurations.getDefaults(), + ); + + static ImpaktfullTheme fromAccent({ + required Color accent1, + required Color accent2, + required Color accent3, + ImpaktfullShadowTheme? shadows, + ImpaktfullAssets? assets, + ImpaktfullDimens? dimens, + ImpaktfullDurations? durations, + }) => + ImpaktfullTheme( + colors: ImpaktfullColorTheme( + primary: ImpaktfullBranding.primary, + accent1: accent1, + accent2: accent2, + accent3: accent3, + onPrimary: ImpaktfullBranding.textOnPrimary, + onAccent1: ImpaktfullBranding.textOnAccent1, + onAccent2: ImpaktfullBranding.textOnAccent2, + canvas: ImpaktfullBranding.canvas, + card: ImpaktfullBranding.card, + error: ImpaktfullBranding.error, + success: ImpaktfullBranding.success, + warning: ImpaktfullBranding.warning, + separator: ImpaktfullBranding.divider, + ), + shadows: shadows ?? + const ImpaktfullShadowTheme( + card: BoxShadow( + color: Color.fromARGB(10, 0, 0, 0), + blurRadius: 20, + offset: Offset(0, 1), + spreadRadius: 4, + ), + selectedCard: BoxShadow( + color: Color.fromARGB(20, 170, 194, 63), + blurRadius: 20, + offset: Offset(0, 1), + spreadRadius: 4, + ), + ), + textStyles: ImpaktfullTextStylesTheme( + onCanvasPrimary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnCanvasPrimary, ImpaktfullBranding.font), + onCanvasSecondary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnCanvasSecondary, + ImpaktfullBranding.font), + onPrimary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnPrimary, ImpaktfullBranding.font), + onAccent1: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnAccent1, ImpaktfullBranding.font), + onCardPrimary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnCardPrimary, ImpaktfullBranding.font), + onCardSecondary: ImpaktfullTextStyleTheme.getTextStyleTheme( + ImpaktfullBranding.textOnCardSecondary, ImpaktfullBranding.font), + ), + assets: assets ?? ImpaktfullAssets.getDefaults(), + dimens: dimens ?? ImpaktfullDimens.getDefaults(), + durations: durations ?? ImpaktfullDurations.getDefaults(), ); final ImpaktfullColorTheme colors; @@ -64,6 +128,7 @@ class ImpaktfullTheme { final ImpaktfullTextStylesTheme textStyles; final ImpaktfullAssets assets; final ImpaktfullDimens dimens; + final ImpaktfullDurations durations; const ImpaktfullTheme({ required this.colors, @@ -71,6 +136,7 @@ class ImpaktfullTheme { required this.textStyles, required this.assets, required this.dimens, + required this.durations, }); static ImpaktfullTheme of(BuildContext context) => theme; @@ -123,12 +189,14 @@ class ImpaktfullTextStylesTheme { final ImpaktfullTextStyleTheme onCanvasSecondary; final ImpaktfullTextStyleTheme onPrimary; final ImpaktfullTextStyleTheme onAccent1; - final ImpaktfullTextStyleTheme onCard; + final ImpaktfullTextStyleTheme onCardPrimary; + final ImpaktfullTextStyleTheme onCardSecondary; const ImpaktfullTextStylesTheme({ required this.onCanvasPrimary, required this.onCanvasSecondary, - required this.onCard, + required this.onCardPrimary, + required this.onCardSecondary, required this.onPrimary, required this.onAccent1, }); @@ -217,16 +285,22 @@ class ImpaktfullAssets { } class ImpaktfullAssetIcons { - final String iconArrowLeft; + final String arrowLeft; + final String check; + final String chevronRight; const ImpaktfullAssetIcons({ - required this.iconArrowLeft, + required this.arrowLeft, + required this.check, + required this.chevronRight, }); static ImpaktfullAssetIcons getDefaults() { const prefix = 'assets/icons/'; return const ImpaktfullAssetIcons( - iconArrowLeft: '$prefix/arrow_left.svg', + arrowLeft: '$prefix/arrow_left.svg', + check: '$prefix/check.svg', + chevronRight: '$prefix/chevron_right.svg', ); } } @@ -269,3 +343,15 @@ class ImpaktfullDimens { separatorHorizontalPadding: 16, ); } + +class ImpaktfullDurations { + final Duration short; + + const ImpaktfullDurations({ + required this.short, + }); + + static ImpaktfullDurations getDefaults() => const ImpaktfullDurations( + short: Duration(milliseconds: 250), + ); +}