From ef7d5790b7adf19ac55d685018153fe94d7faff4 Mon Sep 17 00:00:00 2001 From: Koen Van Looveren Date: Fri, 1 Mar 2024 08:28:12 +0100 Subject: [PATCH] fix: Added a lot of new components & bugfixes --- CHANGELOG.md | 25 +++ README.md | 2 + example/assets/icons/add.svg | 5 + example/assets/icons/home.svg | 1 + example/assets/icons/home_selected.svg | 1 + example/assets/icons/search.svg | 1 + example/assets/icons/search_selected.svg | 1 + example/assets/icons/settings.svg | 1 + example/assets/icons/settings_selected.svg | 1 + .../components/bottom_navigation_screen.dart | 39 +++- .../src/screen/components/card_screen.dart | 53 ++++++ .../src/screen/components/dialog_screen.dart | 81 +++++++++ .../lib/src/screen/components/fab_screen.dart | 30 ++++ .../listview/listview_childeren_screen.dart | 9 +- .../listview/listview_empty_screen.dart | 22 +++ .../listview_item_builder_screen.dart | 5 +- ...istview_item_seperated_builder_screen.dart | 5 +- .../screen/components/listview_screen.dart | 13 +- .../screen/components/pagination_screen.dart | 38 ++++ example/lib/src/screen/components/screen.dart | 53 ++++++ example/lib/src/screen/components_screen.dart | 30 ++++ example/lib/src/util/example_assets.dart | 11 ++ lib/impaktfull_ui.dart | 6 + .../impaktfull_bottom_navigation.dart | 3 +- .../impaktfull_bottom_navigation_item.dart | 4 +- .../components/button/impaktfull_button.dart | 97 +++++++--- lib/src/components/card/impaktfull_card.dart | 43 +++++ .../checkbox/impaktfull_checkbox.dart | 4 +- .../date_picker/date_picker_item.dart | 11 +- .../date_picker/impaktfull_date_picker.dart | 8 +- .../impaktfull_date_time_range_picker.dart | 5 +- .../impaktfull_date_time_picker_dialog.dart | 4 +- .../components/dialog/impaktfull_dialog.dart | 98 ++++++---- lib/src/components/fab/impaktfull_fab.dart | 37 ++++ .../components/list_item/base_list_item.dart | 2 +- .../list_item/impaktfull_list_item.dart | 2 + .../listview/impaktfull_listview.dart | 133 ++++++++++---- .../pagination/impaktfull_pagination.dart | 169 ++++++++++++++++++ .../radio_button/impaktfull_radio_button.dart | 3 +- .../impaktfull_refresh_indicator.dart | 26 +++ .../screen/impaktfull_navbar_action.dart | 22 +-- .../components/screen/impaktfull_screen.dart | 23 ++- .../components/switch/impaktfull_switch.dart | 8 +- .../impaktfull_touch_feedback.dart | 108 ++++++++++- lib/src/impaktfull_app.dart | 44 +++-- lib/src/theme/impaktfull_branding.dart | 1 + lib/src/theme/impaktfull_theme.dart | 115 ++++++++---- lib/src/util/test_util.dart | 7 + 48 files changed, 1211 insertions(+), 199 deletions(-) create mode 100644 example/assets/icons/add.svg create mode 100644 example/assets/icons/home.svg create mode 100644 example/assets/icons/home_selected.svg create mode 100644 example/assets/icons/search.svg create mode 100644 example/assets/icons/search_selected.svg create mode 100644 example/assets/icons/settings.svg create mode 100644 example/assets/icons/settings_selected.svg create mode 100644 example/lib/src/screen/components/card_screen.dart create mode 100644 example/lib/src/screen/components/dialog_screen.dart create mode 100644 example/lib/src/screen/components/fab_screen.dart create mode 100644 example/lib/src/screen/components/listview/listview_empty_screen.dart create mode 100644 example/lib/src/screen/components/pagination_screen.dart create mode 100644 example/lib/src/screen/components/screen.dart create mode 100644 example/lib/src/util/example_assets.dart create mode 100644 lib/src/components/card/impaktfull_card.dart create mode 100644 lib/src/components/fab/impaktfull_fab.dart create mode 100644 lib/src/components/pagination/impaktfull_pagination.dart create mode 100644 lib/src/components/refresh_indicator/impaktfull_refresh_indicator.dart create mode 100644 lib/src/util/test_util.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e81a6c0..444d1b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# 0.0.20 + +## Feat + +- Readme added pub.dev badge +- Example better icons & assets +- Added ImpaktfullCard +- Added ImpaktfullDialog +- Added ImpaktfullFab +- Added ImpaktfullPagination +- Added ImpaktfullScreen +- ImpaktfullBottomNavigationItem supports selected icon assets +- ImpaktfullListView refresh action & loading state +- ImpaktfullListView pull to refresh +- ImpaktfullRefreshIndicator +- ImpaktfullApp now supports showing or hiding the debugFlag +- ImpaktfullButton can have a loading state with `onAsyncTap` +- ImpaktfullTouchFeedback now has actual feedback based on the platform +- ImpaktfullTheme now has more shadow options (`card`, `selectedCard`, `bottomNavigation`, `button`) +- ImpaktfullTheme now has border options (`card`, `selectedCard`) + +## Fix +- ImpaktfullDatePicker now uses the borderRadius from the theme +- Small disposed errors in ImpaktfullListItem + # 0.0.18 - 0.0.19 ## Fix diff --git a/README.md b/README.md index 363f61a..e967511 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ This UI library was built to have a simple way to build UIs for Impaktfull. This # Usage +[![pub package](https://img.shields.io/pub/v/impaktfull_ui.svg)](https://pub.dartlang.org/packages/impaktfull_ui) + ## Setup - Setup your theme (colors, textStyles, shadows, dimens, assets) diff --git a/example/assets/icons/add.svg b/example/assets/icons/add.svg new file mode 100644 index 0000000..5c64474 --- /dev/null +++ b/example/assets/icons/add.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/example/assets/icons/home.svg b/example/assets/icons/home.svg new file mode 100644 index 0000000..0e618b5 --- /dev/null +++ b/example/assets/icons/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/icons/home_selected.svg b/example/assets/icons/home_selected.svg new file mode 100644 index 0000000..c184794 --- /dev/null +++ b/example/assets/icons/home_selected.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/icons/search.svg b/example/assets/icons/search.svg new file mode 100644 index 0000000..bf4e505 --- /dev/null +++ b/example/assets/icons/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/icons/search_selected.svg b/example/assets/icons/search_selected.svg new file mode 100644 index 0000000..93c5015 --- /dev/null +++ b/example/assets/icons/search_selected.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/icons/settings.svg b/example/assets/icons/settings.svg new file mode 100644 index 0000000..ec3561d --- /dev/null +++ b/example/assets/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/assets/icons/settings_selected.svg b/example/assets/icons/settings_selected.svg new file mode 100644 index 0000000..153cbe5 --- /dev/null +++ b/example/assets/icons/settings_selected.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/example/lib/src/screen/components/bottom_navigation_screen.dart b/example/lib/src/screen/components/bottom_navigation_screen.dart index d951717..442142d 100644 --- a/example/lib/src/screen/components/bottom_navigation_screen.dart +++ b/example/lib/src/screen/components/bottom_navigation_screen.dart @@ -1,4 +1,5 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:impaktfull_ui_example/src/util/example_assets.dart'; class BottomNavigationScreen extends StatefulWidget { const BottomNavigationScreen({ @@ -19,24 +20,50 @@ class _BottomNavigationScreenState extends State { title: 'Components - BottomNavigation', onBackTapped: () => Navigator.of(context).pop(), child: ImpaktfullListView( + spacing: 8, children: [ ImpaktfullBottomNavigation( items: [ ImpaktfullBottomNavigationItem( - label: 'Label 1', - svgIcon: theme.assets.icons.chevronRight, + label: 'Home', + svgIcon: ExampleAssets.home, isSelected: _selectedIndex == 0, onTap: () => setState(() => _selectedIndex = 0), ), ImpaktfullBottomNavigationItem( - label: 'Label 2', - svgIcon: theme.assets.icons.chevronRight, + label: 'Search', + svgIcon: ExampleAssets.search, isSelected: _selectedIndex == 1, onTap: () => setState(() => _selectedIndex = 1), ), ImpaktfullBottomNavigationItem( - label: 'Label 3', - svgIcon: theme.assets.icons.chevronRight, + label: 'Settings', + svgIcon: ExampleAssets.settings, + isSelected: _selectedIndex == 2, + onTap: () => setState(() => _selectedIndex = 2), + ), + ], + ), + ImpaktfullBottomNavigation( + items: [ + ImpaktfullBottomNavigationItem( + label: 'Home', + svgIcon: ExampleAssets.home, + svgIconSelected: ExampleAssets.homeSelected, + isSelected: _selectedIndex == 0, + onTap: () => setState(() => _selectedIndex = 0), + ), + ImpaktfullBottomNavigationItem( + label: 'Search', + svgIcon: ExampleAssets.search, + svgIconSelected: ExampleAssets.searchSelected, + isSelected: _selectedIndex == 1, + onTap: () => setState(() => _selectedIndex = 1), + ), + ImpaktfullBottomNavigationItem( + label: 'Settings', + svgIcon: ExampleAssets.settings, + svgIconSelected: ExampleAssets.settingsSelected, isSelected: _selectedIndex == 2, onTap: () => setState(() => _selectedIndex = 2), ), diff --git a/example/lib/src/screen/components/card_screen.dart b/example/lib/src/screen/components/card_screen.dart new file mode 100644 index 0000000..b0292ce --- /dev/null +++ b/example/lib/src/screen/components/card_screen.dart @@ -0,0 +1,53 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class CardScreen extends StatelessWidget { + const CardScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - Card', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + spacing: 8, + children: [ + ImpaktfullCard( + child: ImpaktfullAutoLayout.vertical( + spacing: 8, + children: [ + Text( + 'Some normal title', + style: theme.textStyles.onCardPrimary.title, + ), + Text( + 'And this will be some body text that is a bit longer than the title', + style: theme.textStyles.onCardPrimary.body, + ), + ], + ), + ), + ImpaktfullCard( + isSelected: true, + child: ImpaktfullAutoLayout.vertical( + spacing: 8, + children: [ + Text( + 'Some normal title (selected)', + style: theme.textStyles.onCardPrimary.title, + ), + Text( + 'And this will be some body text that is a bit longer than the title', + style: theme.textStyles.onCardPrimary.body, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/dialog_screen.dart b/example/lib/src/screen/components/dialog_screen.dart new file mode 100644 index 0000000..bac5020 --- /dev/null +++ b/example/lib/src/screen/components/dialog_screen.dart @@ -0,0 +1,81 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:impaktfull_ui_example/src/util/snacky_uitl.dart'; + +class DialogScreen extends StatelessWidget { + const DialogScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - Dialog', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + spacing: 8, + children: [ + ImpaktfullButton.accent( + label: 'Show dialog', + onAsyncTap: () async { + showDialog( + context: context, + builder: (context) => ImpaktfullDialog( + child: Text( + 'some body', + style: theme.textStyles.onCardPrimary.body, + ), + ), + ); + }, + ), + ImpaktfullButton.accent( + label: 'Show dialog (with title & body)', + onAsyncTap: () async { + final result = await showDialog( + context: context, + builder: (context) => ImpaktfullDialog( + title: 'Some title', + body: 'Some body', + onSecondaryTapped: () => Navigator.of(context).pop(false), + onPrimaryTapped: () => Navigator.of(context).pop(true), + ), + ); + SnackyUtil.show('Result of dialog: `$result`'); + }, + ), + ImpaktfullButton.accent( + label: 'Show dialog (with result)', + onAsyncTap: () async { + final result = await showDialog( + context: context, + builder: (context) => ImpaktfullDialog( + onSecondaryTapped: () => Navigator.of(context).pop(false), + onPrimaryTapped: () => Navigator.of(context).pop(true), + child: Text( + 'some body', + style: theme.textStyles.onCardPrimary.body, + ), + ), + ); + SnackyUtil.show('Result of dialog: `$result`'); + }, + ), + ImpaktfullButton.accent( + label: 'Show date time picker dialog', + onAsyncTap: () async { + final result = await showDialog( + context: context, + builder: (context) => ImpaktfullDateTimePickerDialog( + selectedDateTime: DateTime.now(), + ), + ); + SnackyUtil.show('Result of date time picker dialog: `$result`'); + }, + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/fab_screen.dart b/example/lib/src/screen/components/fab_screen.dart new file mode 100644 index 0000000..dc24504 --- /dev/null +++ b/example/lib/src/screen/components/fab_screen.dart @@ -0,0 +1,30 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:impaktfull_ui_example/src/util/example_assets.dart'; +import 'package:impaktfull_ui_example/src/util/snacky_uitl.dart'; + +class FabScreen extends StatelessWidget { + const FabScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - Fab', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + spacing: 8, + children: [ + Center( + child: ImpaktfullFab( + asset: ExampleAssets.add, + onTap: () => SnackyUtil.show('OnTap'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/listview/listview_childeren_screen.dart b/example/lib/src/screen/components/listview/listview_childeren_screen.dart index 60f8c9e..f7d1fea 100644 --- a/example/lib/src/screen/components/listview/listview_childeren_screen.dart +++ b/example/lib/src/screen/components/listview/listview_childeren_screen.dart @@ -1,7 +1,7 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; -class ListViewChildren extends StatelessWidget { - const ListViewChildren({ +class ListViewChildrenScreen extends StatelessWidget { + const ListViewChildrenScreen({ super.key, }); @@ -10,8 +10,9 @@ class ListViewChildren extends StatelessWidget { return ImpaktfullScreen( title: 'ListView - Children', onBackTapped: () => Navigator.of(context).pop(), - child: const ImpaktfullListView( - children: [ + child: ImpaktfullListView( + onRefresh: () async => Future.delayed(const Duration(seconds: 2)), + children: const [ Text('Child 1'), Text('Child 2'), Text('Child 3'), diff --git a/example/lib/src/screen/components/listview/listview_empty_screen.dart b/example/lib/src/screen/components/listview/listview_empty_screen.dart new file mode 100644 index 0000000..0702d4b --- /dev/null +++ b/example/lib/src/screen/components/listview/listview_empty_screen.dart @@ -0,0 +1,22 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class ListViewEmptyScreen extends StatelessWidget { + const ListViewEmptyScreen({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullScreen( + title: 'ListView - Empty', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView.builder( + onRefresh: () async => Future.delayed(const Duration(seconds: 2)), + items: const [], + itemBuilder: (context, item) => Container(), + noDataLabel: 'No data', + refreshBtnLabel: 'Probeer opnieuw', + ), + ); + } +} diff --git a/example/lib/src/screen/components/listview/listview_item_builder_screen.dart b/example/lib/src/screen/components/listview/listview_item_builder_screen.dart index 3f40a70..3627b10 100644 --- a/example/lib/src/screen/components/listview/listview_item_builder_screen.dart +++ b/example/lib/src/screen/components/listview/listview_item_builder_screen.dart @@ -1,7 +1,7 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; -class ListViewItemBuilder extends StatelessWidget { - const ListViewItemBuilder({ +class ListViewItemBuilderScreen extends StatelessWidget { + const ListViewItemBuilderScreen({ super.key, }); @@ -11,6 +11,7 @@ class ListViewItemBuilder extends StatelessWidget { title: 'ListView - Item Builder', onBackTapped: () => Navigator.of(context).pop(), child: ImpaktfullListView.builder( + onRefresh: () async => Future.delayed(const Duration(seconds: 2)), items: List.generate(100, (index) => 'Child ${index + 1}'), itemBuilder: (context, item) => Text(item), noDataLabel: 'No Data found', diff --git a/example/lib/src/screen/components/listview/listview_item_seperated_builder_screen.dart b/example/lib/src/screen/components/listview/listview_item_seperated_builder_screen.dart index 97c8de2..6b12902 100644 --- a/example/lib/src/screen/components/listview/listview_item_seperated_builder_screen.dart +++ b/example/lib/src/screen/components/listview/listview_item_seperated_builder_screen.dart @@ -1,7 +1,7 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; -class ListViewItemSeparatedBuilder extends StatelessWidget { - const ListViewItemSeparatedBuilder({ +class ListViewItemSeparatedBuilderScreen extends StatelessWidget { + const ListViewItemSeparatedBuilderScreen({ super.key, }); @@ -11,6 +11,7 @@ class ListViewItemSeparatedBuilder extends StatelessWidget { title: 'ListView - Item Separated Builder', onBackTapped: () => Navigator.of(context).pop(), child: ImpaktfullListView.separated( + onRefresh: () async => Future.delayed(const Duration(seconds: 2)), separatorType: ImpaktfullSeparatorType.canvas, items: List.generate(100, (index) => 'Child ${index + 1}'), itemBuilder: (context, item) => Text(item), diff --git a/example/lib/src/screen/components/listview_screen.dart b/example/lib/src/screen/components/listview_screen.dart index f9eeb5b..e22afa3 100644 --- a/example/lib/src/screen/components/listview_screen.dart +++ b/example/lib/src/screen/components/listview_screen.dart @@ -1,5 +1,6 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; import 'package:impaktfull_ui_example/src/screen/components/listview/listview_childeren_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/listview/listview_empty_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/listview/listview_item_builder_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/listview/listview_item_seperated_builder_screen.dart'; @@ -20,17 +21,23 @@ class ListViewScreen extends StatelessWidget { ImpaktfullButton.primary( label: 'Children', onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewChildren())), + builder: (context) => const ListViewChildrenScreen())), ), ImpaktfullButton.primary( label: 'Item Builder', onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewItemBuilder())), + builder: (context) => const ListViewItemBuilderScreen())), ), ImpaktfullButton.primary( label: 'Item Separated Builder', onTap: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const ListViewItemSeparatedBuilder())), + builder: (context) => + const ListViewItemSeparatedBuilderScreen())), + ), + ImpaktfullButton.primary( + label: 'Empty', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ListViewEmptyScreen())), ), ], ), diff --git a/example/lib/src/screen/components/pagination_screen.dart b/example/lib/src/screen/components/pagination_screen.dart new file mode 100644 index 0000000..4c9b8f2 --- /dev/null +++ b/example/lib/src/screen/components/pagination_screen.dart @@ -0,0 +1,38 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class PaginationScreen extends StatefulWidget { + const PaginationScreen({ + super.key, + }); + + @override + State createState() => _PaginationScreenState(); +} + +class _PaginationScreenState extends State { + var _currentPage = 1; + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - Pagination', + onBackTapped: () => Navigator.of(context).pop(), + child: ImpaktfullListView( + spacing: 8, + children: [ + ImpaktfullPagination( + currentPage: _currentPage, + count: 200, + onPageChange: (int page, int countPerPage) { + setState(() { + _currentPage = page; + }); + }, + showFirstLastButtons: true, + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/src/screen/components/screen.dart b/example/lib/src/screen/components/screen.dart new file mode 100644 index 0000000..62be7e1 --- /dev/null +++ b/example/lib/src/screen/components/screen.dart @@ -0,0 +1,53 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:impaktfull_ui_example/src/util/example_assets.dart'; +import 'package:impaktfull_ui_example/src/util/snacky_uitl.dart'; + +class ImpaktfullExampleScreen extends StatefulWidget { + const ImpaktfullExampleScreen({ + super.key, + }); + + @override + State createState() => + _ImpaktfullExampleScreenState(); +} + +class _ImpaktfullExampleScreenState extends State { + var _selectedIndex = 0; + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullScreen( + title: 'Components - ListView', + onBackTapped: () => Navigator.of(context).pop(), + fab: ImpaktfullFab( + asset: ExampleAssets.add, + onTap: () => SnackyUtil.show('On fab tapped!'), + ), + bottomAction: ImpaktfullBottomNavigation( + items: [ + ImpaktfullBottomNavigationItem( + svgIcon: ExampleAssets.home, + label: 'Home', + isSelected: _selectedIndex == 0, + onTap: () => setState(() => _selectedIndex = 0), + ), + ImpaktfullBottomNavigationItem( + svgIcon: ExampleAssets.search, + label: 'Search', + isSelected: _selectedIndex == 1, + onTap: () => setState(() => _selectedIndex = 1), + ), + ImpaktfullBottomNavigationItem( + svgIcon: ExampleAssets.settings, + label: 'Settings', + isSelected: _selectedIndex == 2, + onTap: () => setState(() => _selectedIndex = 2), + ), + ], + ), + child: const Placeholder(), + ), + ); + } +} diff --git a/example/lib/src/screen/components_screen.dart b/example/lib/src/screen/components_screen.dart index 5c57c0c..2a765ba 100644 --- a/example/lib/src/screen/components_screen.dart +++ b/example/lib/src/screen/components_screen.dart @@ -1,17 +1,22 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; import 'package:impaktfull_ui_example/src/screen/components/bottom_navigation_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/buttons_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/card_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/checkbox_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/date_picker_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/date_time_picker_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/date_time_range_picker_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/dialog_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/fab_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/input_field_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/nav_bar_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/pagination_screen.dart'; import 'package:impaktfull_ui_example/src/screen/components/radiobutton_screen.dart'; +import 'package:impaktfull_ui_example/src/screen/components/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'; import 'package:impaktfull_ui_example/src/screen/components/switch_list_item_screen.dart'; @@ -43,6 +48,11 @@ class ComponentsScreen extends StatelessWidget { onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (context) => const ButtonsScreen())), ), + ImpaktfullButton.accent( + label: 'Card', + onTap: () => Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const CardScreen())), + ), ImpaktfullButton.accent( label: 'Checkbox', onTap: () => Navigator.of(context).push(MaterialPageRoute( @@ -53,6 +63,16 @@ class ComponentsScreen extends StatelessWidget { onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (context) => const DatePickerScreen())), ), + ImpaktfullButton.accent( + label: 'Dialog', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const DialogScreen())), + ), + ImpaktfullButton.accent( + label: 'Fab', + onTap: () => Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const FabScreen())), + ), ImpaktfullButton.accent( label: 'DateTimePicker', onTap: () => Navigator.of(context).push(MaterialPageRoute( @@ -93,11 +113,21 @@ class ComponentsScreen extends StatelessWidget { onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (context) => const NavBarScreen())), ), + ImpaktfullButton.accent( + label: 'Pagination', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const PaginationScreen())), + ), ImpaktfullButton.accent( label: 'RadioButton', onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (context) => const RadioButtonScreen())), ), + ImpaktfullButton.accent( + label: 'Screen', + onTap: () => Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ImpaktfullExampleScreen())), + ), ImpaktfullButton.accent( label: 'Selectable List Item', onTap: () => Navigator.of(context).push(MaterialPageRoute( diff --git a/example/lib/src/util/example_assets.dart b/example/lib/src/util/example_assets.dart new file mode 100644 index 0000000..8adface --- /dev/null +++ b/example/lib/src/util/example_assets.dart @@ -0,0 +1,11 @@ +class ExampleAssets { + const ExampleAssets._(); + + static const String home = 'assets/icons/home.svg'; + static const String homeSelected = 'assets/icons/home_selected.svg'; + static const String search = 'assets/icons/search.svg'; + static const String searchSelected = 'assets/icons/search_selected.svg'; + static const String settings = 'assets/icons/settings.svg'; + static const String settingsSelected = 'assets/icons/settings_selected.svg'; + static const String add = 'assets/icons/add.svg'; +} diff --git a/lib/impaktfull_ui.dart b/lib/impaktfull_ui.dart index eb16b69..542032e 100644 --- a/lib/impaktfull_ui.dart +++ b/lib/impaktfull_ui.dart @@ -2,11 +2,15 @@ export 'src/components/auto_layout/impaktfull_auto_layout.dart'; export 'src/components/button/impaktfull_button.dart'; export 'src/components/bottom_navigation/impaktfull_bottom_navigation.dart'; export 'src/components/bottom_navigation/impaktfull_bottom_navigation_item.dart'; +export 'src/components/card/impaktfull_card.dart'; export 'src/components/checkbox/impaktfull_checkbox.dart'; export 'src/components/date_picker/impaktfull_date_picker.dart'; export 'src/components/date_time_picker/impaktfull_date_time_picker.dart'; export 'src/components/date_time_range_picker/impaktfull_date_time_range_picker.dart'; +export 'src/components/dialog/impaktfull_date_time_picker_dialog.dart'; +export 'src/components/dialog/impaktfull_dialog.dart'; export 'src/components/input_field/impaktfull_input_field.dart'; +export 'src/components/fab/impaktfull_fab.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'; @@ -15,7 +19,9 @@ export 'src/components/list_item/impaktfull_radio_button_list_item.dart'; export 'src/components/list_item/impaktfull_selectable_list_item.dart'; export 'src/components/list_item/impaktfull_switch_list_item.dart'; export 'src/components/loading/impaktfull_loading.dart'; +export 'src/components/pagination/impaktfull_pagination.dart'; export 'src/components/radio_button/impaktfull_radio_button.dart'; +export 'src/components/refresh_indicator/impaktfull_refresh_indicator.dart'; export 'src/components/screen/impaktfull_screen.dart'; export 'src/components/screen/impaktfull_statusbar.dart'; export 'src/components/screen/impaktfull_navbar.dart'; diff --git a/lib/src/components/bottom_navigation/impaktfull_bottom_navigation.dart b/lib/src/components/bottom_navigation/impaktfull_bottom_navigation.dart index fcd2d49..2e7777d 100644 --- a/lib/src/components/bottom_navigation/impaktfull_bottom_navigation.dart +++ b/lib/src/components/bottom_navigation/impaktfull_bottom_navigation.dart @@ -17,7 +17,8 @@ class ImpaktfullBottomNavigation extends StatelessWidget { decoration: BoxDecoration( color: theme.colors.card, boxShadow: [ - theme.shadows.card, + if (theme.shadows.bottomNavigation != null) + theme.shadows.bottomNavigation!, ], ), child: SafeArea( diff --git a/lib/src/components/bottom_navigation/impaktfull_bottom_navigation_item.dart b/lib/src/components/bottom_navigation/impaktfull_bottom_navigation_item.dart index adc9898..c54f114 100644 --- a/lib/src/components/bottom_navigation/impaktfull_bottom_navigation_item.dart +++ b/lib/src/components/bottom_navigation/impaktfull_bottom_navigation_item.dart @@ -3,6 +3,7 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; class ImpaktfullBottomNavigationItem extends StatelessWidget { final String label; final String svgIcon; + final String? svgIconSelected; final bool isSelected; final VoidCallback onTap; @@ -11,6 +12,7 @@ class ImpaktfullBottomNavigationItem extends StatelessWidget { required this.svgIcon, required this.isSelected, required this.onTap, + this.svgIconSelected, super.key, }); @@ -26,7 +28,7 @@ class ImpaktfullBottomNavigationItem extends StatelessWidget { children: [ const SizedBox(height: 8), ImpaktfullSvgIcon( - asset: svgIcon, + asset: isSelected ? svgIconSelected ?? svgIcon : svgIcon, color: isSelected ? theme.colors.accent1 : theme.colors.primary, ), diff --git a/lib/src/components/button/impaktfull_button.dart b/lib/src/components/button/impaktfull_button.dart index 61d5766..99d8f9b 100644 --- a/lib/src/components/button/impaktfull_button.dart +++ b/lib/src/components/button/impaktfull_button.dart @@ -1,4 +1,6 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.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.dart'; import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; @@ -9,54 +11,83 @@ enum _ButtonType { accent, } -class ImpaktfullButton extends StatelessWidget { +class ImpaktfullButton extends StatefulWidget { final String label; final VoidCallback? onTap; + final AsyncCallback? onAsyncTap; final _ButtonType _type; const ImpaktfullButton.primary({ required this.label, - required this.onTap, + this.onTap, + this.onAsyncTap, super.key, }) : _type = _ButtonType.primary; const ImpaktfullButton.secondary({ required this.label, - required this.onTap, + this.onTap, + this.onAsyncTap, super.key, }) : _type = _ButtonType.secondary; const ImpaktfullButton.accent({ required this.label, - required this.onTap, + this.onTap, + this.onAsyncTap, super.key, }) : _type = _ButtonType.accent; + @override + State createState() => _ImpaktfullButtonState(); +} + +class _ImpaktfullButtonState extends State { + var _isLoading = false; + @override Widget build(BuildContext context) { + final hasOnTap = widget.onTap != null || widget.onAsyncTap != null; return ImpaktfullThemeLocalizer( builder: (context, theme) => Opacity( - opacity: onTap == null ? 0.3 : 1, + opacity: hasOnTap ? 1 : 0.3, child: IgnorePointer( - ignoring: onTap == null, - child: ImpaktfullTouchFeedback( - onTap: onTap, - child: Container( - decoration: BoxDecoration( - color: _getBackground(theme), - borderRadius: - BorderRadius.circular(theme.dimens.generalBorderRadius), - ), - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 16, + ignoring: !hasOnTap, + child: Stack( + alignment: Alignment.center, + children: [ + Visibility.maintain( + visible: !_isLoading, + child: ImpaktfullTouchFeedback( + onTap: hasOnTap ? _onTap : null, + color: _getBackground(theme), + shadow: [ + if (theme.shadows.button != null) theme.shadows.button!, + ], + borderRadius: + BorderRadius.circular(theme.dimens.generalBorderRadius), + child: Container( + constraints: const BoxConstraints( + minWidth: 48, + minHeight: 48, + ), + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), + child: Text( + widget.label, + style: _getTextStyle(theme).title, + textAlign: TextAlign.center, + ), + ), + ), ), - child: Text( - label, - style: _getTextStyle(theme).title, - textAlign: TextAlign.center, - ), - ), + if (_isLoading) ...[ + const ImpaktfullLoadingIndicator(), + ], + ], ), ), ), @@ -64,8 +95,7 @@ class ImpaktfullButton extends StatelessWidget { } Color _getBackground(ImpaktfullTheme theme) { - if (onTap == null) return theme.colors.primary; - switch (_type) { + switch (widget._type) { case _ButtonType.primary: return theme.colors.primary; case _ButtonType.secondary: @@ -76,7 +106,7 @@ class ImpaktfullButton extends StatelessWidget { } ImpaktfullTextStyleTheme _getTextStyle(ImpaktfullTheme theme) { - switch (_type) { + switch (widget._type) { case _ButtonType.primary: return theme.textStyles.onPrimary; case _ButtonType.secondary: @@ -85,4 +115,19 @@ class ImpaktfullButton extends StatelessWidget { return theme.textStyles.onAccent1; } } + + Future _onTap() async { + if (widget.onTap != null) { + widget.onTap!(); + return; + } + setState(() => _isLoading = true); + try { + await widget.onAsyncTap!(); + } catch (error) { + debugPrint(error.toString()); + } + if (!mounted) return; + setState(() => _isLoading = false); + } } diff --git a/lib/src/components/card/impaktfull_card.dart b/lib/src/components/card/impaktfull_card.dart new file mode 100644 index 0000000..8dea85e --- /dev/null +++ b/lib/src/components/card/impaktfull_card.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; + +class ImpaktfullCard extends StatelessWidget { + final Widget child; + final bool isSelected; + + const ImpaktfullCard({ + required this.child, + this.isSelected = false, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) { + final border = + isSelected ? theme.borders.selectedCard : theme.borders.card; + final shadow = + isSelected ? theme.shadows.selectedCard : theme.shadows.card; + return Container( + decoration: BoxDecoration( + color: theme.colors.card, + border: border, + borderRadius: + BorderRadius.circular(theme.dimens.generalBorderRadius), + boxShadow: [ + if (shadow != null) shadow, + ], + ), + padding: EdgeInsets.only( + top: 16 - (border?.top.width ?? 0), + right: 16 - (border?.right.width ?? 0), + bottom: 16 - (border?.bottom.width ?? 0), + left: 16 - (border?.left.width ?? 0), + ), + child: child, + ); + }, + ); + } +} diff --git a/lib/src/components/checkbox/impaktfull_checkbox.dart b/lib/src/components/checkbox/impaktfull_checkbox.dart index 74ef9d2..7cdc0c2 100644 --- a/lib/src/components/checkbox/impaktfull_checkbox.dart +++ b/lib/src/components/checkbox/impaktfull_checkbox.dart @@ -16,6 +16,9 @@ class ImpaktfullCheckBox extends StatelessWidget { builder: (context, theme) => Center( child: ImpaktfullTouchFeedback( onTap: () => onChanged(!value), + borderRadius: + BorderRadius.circular(theme.dimens.switchThumbBorderRadius), + color: theme.colors.card, child: SizedBox( width: 24, height: 24, @@ -30,7 +33,6 @@ class ImpaktfullCheckBox extends StatelessWidget { color: theme.colors.accent2, width: theme.dimens.borderWidth, ), - color: theme.colors.card, ), ), ), diff --git a/lib/src/components/date_picker/date_picker_item.dart b/lib/src/components/date_picker/date_picker_item.dart index a470ea5..dfc5437 100644 --- a/lib/src/components/date_picker/date_picker_item.dart +++ b/lib/src/components/date_picker/date_picker_item.dart @@ -27,6 +27,12 @@ class DatePickerItem extends StatelessWidget { return ImpaktfullThemeLocalizer( builder: (context, theme) => ImpaktfullTouchFeedback( onTap: onTap, + borderRadius: BorderRadius.circular(theme.dimens.generalBorderRadius), + color: _isSelected() + ? theme.colors.primary + : _isHighlighted() + ? theme.colors.accent1.withOpacity(0.05) + : null, child: Container( decoration: BoxDecoration( borderRadius: @@ -36,11 +42,6 @@ class DatePickerItem extends StatelessWidget { _isHighlighted() ? theme.colors.accent1 : Colors.transparent, width: theme.dimens.borderWidth, ), - color: _isSelected() - ? theme.colors.primary - : _isHighlighted() - ? theme.colors.accent1.withOpacity(0.05) - : null, ), constraints: const BoxConstraints( minWidth: 40, diff --git a/lib/src/components/date_picker/impaktfull_date_picker.dart b/lib/src/components/date_picker/impaktfull_date_picker.dart index 37eff73..ca01b72 100644 --- a/lib/src/components/date_picker/impaktfull_date_picker.dart +++ b/lib/src/components/date_picker/impaktfull_date_picker.dart @@ -139,9 +139,11 @@ class _ImpaktfullDatePickerState extends State { onTap: _onPreviousTapped, ), Expanded( - child: GestureDetector( - onTap: _onTitleTapped, - onLongPress: _onTitleLongTapped, + child: PlatfomTouchFeedback( + onTap: _type == ImpaktfullDatePickerType.years + ? null + : _onTitleTapped, + onLongTap: _onTitleLongTapped, child: ColoredBox( color: Colors.transparent, child: Text( diff --git a/lib/src/components/date_time_range_picker/impaktfull_date_time_range_picker.dart b/lib/src/components/date_time_range_picker/impaktfull_date_time_range_picker.dart index 430504e..b93fb29 100644 --- a/lib/src/components/date_time_range_picker/impaktfull_date_time_range_picker.dart +++ b/lib/src/components/date_time_range_picker/impaktfull_date_time_range_picker.dart @@ -1,5 +1,8 @@ -import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:impaktfull_ui/src/components/auto_layout/impaktfull_auto_layout.dart'; import 'package:impaktfull_ui/src/components/dialog/impaktfull_date_time_picker_dialog.dart'; +import 'package:impaktfull_ui/src/components/touch_feedback/impaktfull_touch_feedback.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; import 'package:impaktfull_ui/src/util/date_extensions.dart'; class ImpaktfullDateTimeRangePicker extends StatefulWidget { diff --git a/lib/src/components/dialog/impaktfull_date_time_picker_dialog.dart b/lib/src/components/dialog/impaktfull_date_time_picker_dialog.dart index a6d71e2..61f0963 100644 --- a/lib/src/components/dialog/impaktfull_date_time_picker_dialog.dart +++ b/lib/src/components/dialog/impaktfull_date_time_picker_dialog.dart @@ -1,5 +1,7 @@ -import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:impaktfull_ui/src/components/date_time_picker/impaktfull_date_time_picker.dart'; import 'package:impaktfull_ui/src/components/dialog/impaktfull_dialog.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; class ImpaktfullDateTimePickerDialog extends StatefulWidget { final DateTime? initalSelectedDateTime; diff --git a/lib/src/components/dialog/impaktfull_dialog.dart b/lib/src/components/dialog/impaktfull_dialog.dart index a15733f..4ed1568 100644 --- a/lib/src/components/dialog/impaktfull_dialog.dart +++ b/lib/src/components/dialog/impaktfull_dialog.dart @@ -1,65 +1,85 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; class ImpaktfullDialog extends StatelessWidget { - final Widget child; + final String? title; + final String? body; + final Widget? child; final String? secondaryLabel; final String? primaryLabel; final VoidCallback? onSecondaryTapped; final VoidCallback? onPrimaryTapped; const ImpaktfullDialog({ - required this.child, + this.title, + this.body, + this.child, this.secondaryLabel, this.onSecondaryTapped, this.primaryLabel, this.onPrimaryTapped, super.key, - }); + }) : assert(child != null || title != null || body != null); @override Widget build(BuildContext context) { return ImpaktfullThemeLocalizer( builder: (context, theme) => Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 400), - margin: const EdgeInsets.all(8), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: theme.colors.card, - borderRadius: - BorderRadius.circular(theme.dimens.generalBorderRadius), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - child, - if (onSecondaryTapped != null || onPrimaryTapped != null) ...[ - const SizedBox(height: 16), - ImpaktfullAutoLayout.horizontal( - spacing: 8, - children: [ - if (onSecondaryTapped != null) ...[ - Expanded( - child: ImpaktfullButton.secondary( - label: secondaryLabel ?? - theme.localizations.current.generalLabelCancel, - onTap: onSecondaryTapped, + child: Material( + type: MaterialType.transparency, + child: Container( + constraints: const BoxConstraints(maxWidth: 500), + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: theme.colors.card, + borderRadius: + BorderRadius.circular(theme.dimens.generalBorderRadius), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (title != null) ...[ + Text( + title!, + style: theme.textStyles.onCardPrimary.title, + ), + ], + if (body != null) ...[ + Text( + body!, + style: theme.textStyles.onCardPrimary.body, + ), + ], + if (child != null) child!, + if (onSecondaryTapped != null || onPrimaryTapped != null) ...[ + const SizedBox(height: 16), + ImpaktfullAutoLayout.horizontal( + spacing: 8, + children: [ + if (onSecondaryTapped != null) ...[ + Expanded( + child: ImpaktfullButton.secondary( + label: secondaryLabel ?? + theme.localizations.current.generalLabelCancel, + onTap: onSecondaryTapped, + ), ), - ), - ], - if (onPrimaryTapped != null) ...[ - Expanded( - child: ImpaktfullButton.primary( - label: primaryLabel ?? - theme.localizations.current.generalLabelAccept, - onTap: onPrimaryTapped, + ], + if (onPrimaryTapped != null) ...[ + Expanded( + child: ImpaktfullButton.primary( + label: primaryLabel ?? + theme.localizations.current.generalLabelAccept, + onTap: onPrimaryTapped, + ), ), - ), + ], ], - ], - ), + ), + ], ], - ], + ), ), ), ), diff --git a/lib/src/components/fab/impaktfull_fab.dart b/lib/src/components/fab/impaktfull_fab.dart new file mode 100644 index 0000000..2ec51e5 --- /dev/null +++ b/lib/src/components/fab/impaktfull_fab.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.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 ImpaktfullFab extends StatelessWidget { + final String asset; + final VoidCallback onTap; + + const ImpaktfullFab({ + required this.asset, + required this.onTap, + super.key, + }); + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullTouchFeedback( + onTap: onTap, + color: theme.colors.accent1, + borderRadius: BorderRadius.circular(theme.dimens.generalBorderRadius), + shadow: [ + if (theme.shadows.button != null) theme.shadows.button!, + ], + child: Padding( + padding: const EdgeInsets.all(12), + child: ImpaktfullSvgIcon( + asset: asset, + size: 24, + color: theme.colors.onAccent1, + ), + ), + ), + ); + } +} diff --git a/lib/src/components/list_item/base_list_item.dart b/lib/src/components/list_item/base_list_item.dart index 4cb4b30..5db3f7c 100644 --- a/lib/src/components/list_item/base_list_item.dart +++ b/lib/src/components/list_item/base_list_item.dart @@ -24,9 +24,9 @@ class BaseListItem extends StatelessWidget { return ImpaktfullThemeLocalizer( builder: (context, theme) => ImpaktfullTouchFeedback( onTap: onTap, + color: theme.colors.card, child: ImpaktfullAutoLayout.horizontal( crossAxisAlignment: CrossAxisAlignment.center, - backgroundColor: theme.colors.card, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, diff --git a/lib/src/components/list_item/impaktfull_list_item.dart b/lib/src/components/list_item/impaktfull_list_item.dart index 961ca81..344c956 100644 --- a/lib/src/components/list_item/impaktfull_list_item.dart +++ b/lib/src/components/list_item/impaktfull_list_item.dart @@ -68,9 +68,11 @@ class _ImpaktfullListItemState extends State { setState(() {}); try { await widget.onAsyncTap!(); + if (!mounted) return; _isLoading = false; setState(() {}); } catch (error) { + if (!mounted) return; _isLoading = false; setState(() {}); rethrow; diff --git a/lib/src/components/listview/impaktfull_listview.dart b/lib/src/components/listview/impaktfull_listview.dart index 55dedb7..d0861bf 100644 --- a/lib/src/components/listview/impaktfull_listview.dart +++ b/lib/src/components/listview/impaktfull_listview.dart @@ -1,10 +1,13 @@ +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/button/impaktfull_button.dart'; import 'package:impaktfull_ui/src/components/loading/impaktfull_loading.dart'; +import 'package:impaktfull_ui/src/components/refresh_indicator/impaktfull_refresh_indicator.dart'; import 'package:impaktfull_ui/src/components/separator/impaktfull_separator.dart'; import 'package:impaktfull_ui/src/theme/impaktfull_theme.dart'; -class ImpaktfullListView extends StatelessWidget { +class ImpaktfullListView extends StatefulWidget { final List? children; final List? items; final Widget Function(BuildContext, T)? itemBuilder; @@ -12,20 +15,24 @@ class ImpaktfullListView extends StatelessWidget { final bool separated; final bool? skipPadding; final String? noDataLabel; + final String? refreshBtnLabel; final bool isLoading; + final AsyncCallback? onRefresh; final ImpaktfullSeparatorType? separatorType; const ImpaktfullListView({ required this.children, this.isLoading = false, this.spacing = 0, + this.onRefresh, super.key, }) : itemBuilder = null, items = null, separated = false, skipPadding = true, noDataLabel = null, - separatorType = null; + separatorType = null, + refreshBtnLabel = null; const ImpaktfullListView.builder({ required this.items, @@ -33,6 +40,8 @@ class ImpaktfullListView extends StatelessWidget { required String this.noDataLabel, this.spacing = 0, this.isLoading = false, + this.refreshBtnLabel, + this.onRefresh, super.key, }) : separated = false, children = null, @@ -45,6 +54,8 @@ class ImpaktfullListView extends StatelessWidget { required String this.noDataLabel, this.isLoading = false, required ImpaktfullSeparatorType this.separatorType, + this.refreshBtnLabel, + this.onRefresh, super.key, required, }) : spacing = 0, @@ -52,48 +63,110 @@ class ImpaktfullListView extends StatelessWidget { separated = true, skipPadding = null; + @override + State> createState() => _ImpaktfullListViewState(); +} + +class _ImpaktfullListViewState extends State> { + var _isLoading = false; + @override Widget build(BuildContext context) { - if (isLoading) return const Center(child: ImpaktfullLoadingIndicator()); + if (widget.isLoading) { + return const Center(child: ImpaktfullLoadingIndicator()); + } final theme = ImpaktfullTheme.of(context); final padding = EdgeInsets.symmetric( horizontal: theme.dimens.listViewHorizontalPadding, vertical: theme.dimens.listViewVerticalPadding, ).add(MediaQuery.of(context).padding); - if (children != null) { - return ListView( - padding: padding, - children: [ - ImpaktfullAutoLayout.vertical( - spacing: spacing, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children!, - ), - ], + if (widget.children != null) { + return ImpaktfullRefreshIndicator( + onRefresh: widget.onRefresh, + child: ListView( + padding: padding, + children: [ + ImpaktfullAutoLayout.vertical( + spacing: widget.spacing, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: widget.children!, + ), + ], + ), ); } - if (items!.isEmpty) { - return Center( - child: Text( - noDataLabel!, - style: theme.textStyles.onCanvasPrimary.title, + if (widget.items!.isEmpty) { + return ImpaktfullRefreshIndicator( + onRefresh: widget.onRefresh, + child: LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + physics: widget.onRefresh == null + ? null + : const AlwaysScrollableScrollPhysics(), + child: Container( + padding: const EdgeInsets.all(16), + height: constraints.maxHeight, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.noDataLabel!, + style: theme.textStyles.onCanvasPrimary.title, + ), + if (widget.refreshBtnLabel != null && + widget.onRefresh != null) ...[ + const SizedBox(height: 16), + if (_isLoading) ...[ + const ImpaktfullLoadingIndicator(), + ] else ...[ + ImpaktfullButton.secondary( + label: widget.refreshBtnLabel!, + onTap: _onRefreshTapped, + ), + ], + ], + ], + ), + ), + ), ), ); } - if (separated) { - return ListView.separated( - padding: padding, - itemBuilder: (context, index) => itemBuilder!(context, items![index]), - separatorBuilder: (context, index) => - ImpaktfullSeparator(type: separatorType!), - itemCount: items!.length, + if (widget.separated) { + return ImpaktfullRefreshIndicator( + onRefresh: widget.onRefresh, + child: ListView.separated( + padding: padding, + itemBuilder: (context, index) => + widget.itemBuilder!(context, widget.items![index]), + separatorBuilder: (context, index) => + ImpaktfullSeparator(type: widget.separatorType!), + itemCount: widget.items!.length, + ), ); } - return ListView.separated( - padding: padding, - itemBuilder: (context, index) => itemBuilder!(context, items![index]), - separatorBuilder: (context, index) => SizedBox(height: spacing), - itemCount: items!.length, + return ImpaktfullRefreshIndicator( + onRefresh: widget.onRefresh, + child: ListView.separated( + padding: padding, + itemBuilder: (context, index) => + widget.itemBuilder!(context, widget.items![index]), + separatorBuilder: (context, index) => SizedBox(height: widget.spacing), + itemCount: widget.items!.length, + ), ); } + + Future _onRefreshTapped() async { + if (widget.onRefresh == null) return; + setState(() => _isLoading = true); + try { + await widget.onRefresh!(); + } catch (e) { + debugPrint('Error refreshing list: $e'); + } + setState(() => _isLoading = false); + } } diff --git a/lib/src/components/pagination/impaktfull_pagination.dart b/lib/src/components/pagination/impaktfull_pagination.dart new file mode 100644 index 0000000..f123d38 --- /dev/null +++ b/lib/src/components/pagination/impaktfull_pagination.dart @@ -0,0 +1,169 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +typedef ImpaktfullPaginationPageChangeCallback = void Function( + int page, int countPerPage); + +class ImpaktfullPagination extends StatelessWidget { + static const _defaultCountPerPage = 20; + static const _maxAmountOfPages = 7; + + final int currentPage; + final int count; + final int countPerPage; + final ImpaktfullPaginationPageChangeCallback onPageChange; + final bool showFirstLastButtons; + + const ImpaktfullPagination({ + required this.currentPage, + required this.count, + required this.onPageChange, + this.countPerPage = _defaultCountPerPage, + this.showFirstLastButtons = false, + super.key, + }); + + bool get isFirstPage => currentPage == 1; + + bool get hasPreviousPage => currentPage > 1; + + bool get hasNextPage => currentPage < _totalAmountOfPages; + + bool get isLastPage => currentPage == _totalAmountOfPages; + + int get _totalAmountOfPages => (count / countPerPage).ceil(); + + int get _amountOfPages { + if (!exceededMaxAmountOfPages) return _totalAmountOfPages; + var amountOfPages = _maxAmountOfPages; + if (exceededMaxAmountOfPagesStart) { + amountOfPages--; + } + if (exceededMaxAmountOfPagesEnd) { + amountOfPages--; + } + return amountOfPages; + } + + int get offset => ((_maxAmountOfPages / 2).ceil() - 1); + + bool get exceededMaxAmountOfPages => _totalAmountOfPages > _maxAmountOfPages; + + bool get exceededMaxAmountOfPagesStart => + (currentPage - 1) > offset && exceededMaxAmountOfPages; + + bool get exceededMaxAmountOfPagesEnd => + (currentPage <= _totalAmountOfPages - offset) && exceededMaxAmountOfPages; + + List<_PageItem> get _pages { + return List.generate(_amountOfPages, (index) { + final indexOffset = index + 1; + int value = currentPage - offset + indexOffset; + if (!exceededMaxAmountOfPagesStart) { + value = indexOffset; + } + if (!exceededMaxAmountOfPagesEnd) { + value = _totalAmountOfPages - _amountOfPages + indexOffset; + } + return _PageItem( + value: value, + label: (value).toString(), + ); + }); + } + + @override + Widget build(BuildContext context) { + return ImpaktfullThemeLocalizer(builder: (context, theme) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (showFirstLastButtons) ...[ + ImpaktfullButton.secondary( + label: '<<', + onTap: isFirstPage ? null : _goToFirstPage, + ), + ], + ImpaktfullButton.secondary( + label: '<', + onTap: hasPreviousPage ? _goPageBack : null, + ), + if (exceededMaxAmountOfPagesStart) ...[ + ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 48, + minHeight: 48, + ), + child: Center( + child: Text( + '...', + style: theme.textStyles.onCanvasPrimary.button, + ), + ), + ), + ], + for (final page in _pages) ...[ + if (page.value == currentPage) ...[ + ImpaktfullButton.accent( + label: page.label, + onTap: () => _onPageTapped(page.value), + ), + ] else ...[ + ImpaktfullButton.secondary( + label: page.label, + onTap: () => _onPageTapped(page.value), + ), + ], + ], + if (exceededMaxAmountOfPagesEnd) ...[ + ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 48, + minHeight: 48, + ), + child: Center( + child: Text( + '...', + style: theme.textStyles.onCanvasPrimary.button, + ), + ), + ), + ], + ImpaktfullButton.secondary( + label: '>', + onTap: hasNextPage ? _goPageForward : null, + ), + if (showFirstLastButtons) ...[ + ImpaktfullButton.secondary( + label: '>>', + onTap: isLastPage ? null : _goToLastPage, + ), + ], + ], + ); + }); + } + + void _goToFirstPage() => _onPageTapped(1); + + void _goPageBack() => _onPageTapped(currentPage - 1); + + void _onPageTapped(int page) { + if (page < 1) return; + if (page > _totalAmountOfPages) return; + onPageChange(page, countPerPage); + } + + void _goPageForward() => _onPageTapped(currentPage + 1); + + void _goToLastPage() => _onPageTapped((count / countPerPage).ceil()); +} + +class _PageItem { + final int value; + final String label; + + const _PageItem({ + required this.value, + required this.label, + }); +} diff --git a/lib/src/components/radio_button/impaktfull_radio_button.dart b/lib/src/components/radio_button/impaktfull_radio_button.dart index c01c153..b7c5985 100644 --- a/lib/src/components/radio_button/impaktfull_radio_button.dart +++ b/lib/src/components/radio_button/impaktfull_radio_button.dart @@ -19,6 +19,8 @@ class ImpaktfullRadioButton extends StatelessWidget { builder: (context, theme) => Center( child: ImpaktfullTouchFeedback( onTap: () => onSelected(value), + color: theme.colors.card, + borderRadius: BorderRadius.circular(9999), child: SizedBox( width: 24, height: 24, @@ -32,7 +34,6 @@ class ImpaktfullRadioButton extends StatelessWidget { color: theme.colors.accent2, width: theme.dimens.borderWidth, ), - color: theme.colors.card, ), ), ), diff --git a/lib/src/components/refresh_indicator/impaktfull_refresh_indicator.dart b/lib/src/components/refresh_indicator/impaktfull_refresh_indicator.dart new file mode 100644 index 0000000..8a0a5c4 --- /dev/null +++ b/lib/src/components/refresh_indicator/impaktfull_refresh_indicator.dart @@ -0,0 +1,26 @@ +import 'package:flutter/foundation.dart'; +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +class ImpaktfullRefreshIndicator extends StatelessWidget { + final AsyncCallback? onRefresh; + final Widget child; + const ImpaktfullRefreshIndicator({ + required this.onRefresh, + required this.child, + super.key, + }); + + @override + Widget build(BuildContext context) { + if (onRefresh == null) return child; + return ImpaktfullThemeLocalizer( + builder: (context, theme) { + return RefreshIndicator( + color: theme.colors.accent1, + onRefresh: onRefresh!, + child: child, + ); + }, + ); + } +} diff --git a/lib/src/components/screen/impaktfull_navbar_action.dart b/lib/src/components/screen/impaktfull_navbar_action.dart index 86432b7..9fc0c22 100644 --- a/lib/src/components/screen/impaktfull_navbar_action.dart +++ b/lib/src/components/screen/impaktfull_navbar_action.dart @@ -1,7 +1,4 @@ -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:impaktfull_ui/src/components/icon/impaktfull_svg_icon.dart'; +import 'package:impaktfull_ui/impaktfull_ui.dart'; class ImpaktfullNavbarAction extends StatelessWidget { final String svgIcon; @@ -15,18 +12,11 @@ class ImpaktfullNavbarAction extends StatelessWidget { @override Widget build(BuildContext context) { - if (Platform.isAndroid) { - return IconButton( - icon: ImpaktfullSvgIcon( - asset: svgIcon, - ), - onPressed: onTap, - ); - } - return GestureDetector( - onTap: onTap, - child: ColoredBox( - color: Colors.transparent, + return ImpaktfullThemeLocalizer( + builder: (context, theme) => ImpaktfullTouchFeedback( + borderRadius: BorderRadius.circular(999), + onTap: onTap, + color: theme.colors.primary, child: Padding( padding: const EdgeInsets.all(12), child: ImpaktfullSvgIcon( diff --git a/lib/src/components/screen/impaktfull_screen.dart b/lib/src/components/screen/impaktfull_screen.dart index 844f453..570c52a 100644 --- a/lib/src/components/screen/impaktfull_screen.dart +++ b/lib/src/components/screen/impaktfull_screen.dart @@ -10,6 +10,8 @@ class ImpaktfullScreen extends StatelessWidget { final List actions; final Widget? bottomNavBarChild; final Widget? bottomAction; + final Alignment fabAlignment; + final Widget? fab; final VoidCallback? onBackTapped; final VoidCallback? onPopInvoked; @@ -22,6 +24,8 @@ class ImpaktfullScreen extends StatelessWidget { this.onBackTapped, this.onPopInvoked, this.bottomAction, + this.fabAlignment = Alignment.bottomRight, + this.fab, super.key, }); @@ -47,7 +51,24 @@ class ImpaktfullScreen extends StatelessWidget { child: MediaQuery.removePadding( context: context, removeTop: true, - child: child, + child: Column( + children: [ + Expanded( + child: Stack( + alignment: fabAlignment, + children: [ + child, + if (fab != null) + Padding( + padding: const EdgeInsets.all(16), + child: fab!, + ), + ], + ), + ), + if (bottomAction != null) bottomAction!, + ], + ), ), ), ], diff --git a/lib/src/components/switch/impaktfull_switch.dart b/lib/src/components/switch/impaktfull_switch.dart index bff0d9a..3bebcd2 100644 --- a/lib/src/components/switch/impaktfull_switch.dart +++ b/lib/src/components/switch/impaktfull_switch.dart @@ -16,14 +16,14 @@ class ImpaktfullSwitch extends StatelessWidget { builder: (context, theme) => Center( child: ImpaktfullTouchFeedback( onTap: () => onChanged(!value), + color: theme.colors.card, + borderRadius: BorderRadius.circular(theme.dimens.switchBorderRadius), child: AnimatedContainer( duration: theme.durations.short, curve: Curves.easeInOut, decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - theme.dimens.switchBorderRadius, - ), - color: theme.colors.card, + borderRadius: + BorderRadius.circular(theme.dimens.switchBorderRadius), border: Border.all( color: value ? theme.colors.accent1 : theme.colors.accent2, width: theme.dimens.borderWidth, diff --git a/lib/src/components/touch_feedback/impaktfull_touch_feedback.dart b/lib/src/components/touch_feedback/impaktfull_touch_feedback.dart index 183b5fd..fd67a35 100644 --- a/lib/src/components/touch_feedback/impaktfull_touch_feedback.dart +++ b/lib/src/components/touch_feedback/impaktfull_touch_feedback.dart @@ -1,21 +1,121 @@ import 'package:flutter/material.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme.dart'; +import 'package:impaktfull_ui/src/theme/impaktfull_theme_localizer.dart'; class ImpaktfullTouchFeedback extends StatelessWidget { final VoidCallback? onTap; final Widget child; + final BorderRadius? borderRadius; + final Color? color; + final List shadow; const ImpaktfullTouchFeedback({ required this.onTap, required this.child, + this.borderRadius, + this.color, + this.shadow = const [], super.key, }); @override Widget build(BuildContext context) { - if (onTap == null) return child; - return GestureDetector( - onTap: onTap, - child: child, + return ImpaktfullThemeLocalizer( + builder: (context, theme) => PlatfomTouchFeedback( + onTap: onTap, + borderRadius: borderRadius, + color: color, + hoverColor: _getHoverColor(theme), + androidSplashColor: _getSplashColor(theme), + shadow: shadow, + child: child, + ), + ); + } + + Color _getHoverColor(ImpaktfullTheme theme) { + final themeColors = [ + theme.textStyles.onCanvasPrimary.button.color, + theme.colors.accent1, + ]; + if (color != null && themeColors.contains(color)) { + return Colors.white.withOpacity(0.1); + } else { + return theme.colors.accent1.withOpacity(0.1); + } + } + + Color _getSplashColor(ImpaktfullTheme theme) { + final themeColors = [ + theme.textStyles.onCanvasPrimary.button.color, + theme.colors.accent1, + ]; + if (color != null && themeColors.contains(color)) { + return Colors.white.withOpacity(0.25); + } else { + return theme.colors.accent1.withOpacity(0.25); + } + } +} + +class PlatfomTouchFeedback extends StatelessWidget { + final Widget child; + final VoidCallback? onTap; + final VoidCallback? onLongTap; + final BorderRadius? borderRadius; + final Color? color; + final Color? hoverColor; + final Color? androidSplashColor; + final List shadow; + + const PlatfomTouchFeedback({ + required this.onTap, + required this.child, + this.onLongTap, + this.borderRadius, + this.color, + this.hoverColor, + this.androidSplashColor, + this.shadow = const [], + super.key, + }); + + @override + Widget build(BuildContext context) { + if (onTap == null) { + return Container( + decoration: BoxDecoration( + borderRadius: borderRadius, + color: color ?? Colors.transparent, + boxShadow: shadow, + ), + child: child, + ); + } + final isAndroid = Theme.of(context).platform == TargetPlatform.android; + return ImpaktfullThemeLocalizer( + builder: (contex, theme) => Container( + decoration: BoxDecoration( + boxShadow: shadow, + ), + child: Material( + borderRadius: borderRadius, + color: color ?? Colors.transparent, + child: InkWell( + borderRadius: borderRadius, + onTap: onTap, + onLongPress: onLongTap, + hoverColor: hoverColor, + splashColor: androidSplashColor, + splashFactory: + isAndroid ? theme.splashFactory : NoSplash.splashFactory, + child: ColoredBox( + color: Colors.transparent, + child: child, + ), + ), + ), + ), ); } } diff --git a/lib/src/impaktfull_app.dart b/lib/src/impaktfull_app.dart index be79937..0ea578d 100644 --- a/lib/src/impaktfull_app.dart +++ b/lib/src/impaktfull_app.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:impaktfull_ui/src/theme/impaktfull_theme.dart'; import 'package:impaktfull_ui/src/theme/theme_configurator.dart'; @@ -16,6 +17,8 @@ class ImpaktfullApp extends StatelessWidget { final GlobalKey? navigatorKey; final String? initialRoute; final RouteFactory? onGenerateRoute; + final Widget Function(BuildContext context, Widget app)? builder; + final bool showDebugFlag; const ImpaktfullApp({ required this.title, @@ -30,6 +33,8 @@ class ImpaktfullApp extends StatelessWidget { this.initialRoute, this.onGenerateRoute, this.navigatorObservers = const [], + this.builder, + this.showDebugFlag = kDebugMode, super.key, }); @@ -82,21 +87,30 @@ class ImpaktfullApp extends StatelessWidget { } }, ), - app: MaterialApp( - title: title, - home: home, - locale: locale, - theme: materialLightTheme, - darkTheme: materialDarkTheme, - supportedLocales: supportedLocales, - localizationsDelegates: localizationsDelegates, - navigatorKey: navigatorKey, - initialRoute: initialRoute, - onGenerateRoute: onGenerateRoute, - navigatorObservers: [ - SnackyNavigationObserver(), - ...navigatorObservers, - ], + app: Builder( + builder: (context) { + final app = MaterialApp( + title: title, + home: home, + debugShowCheckedModeBanner: showDebugFlag, + locale: locale, + theme: materialLightTheme, + darkTheme: materialDarkTheme, + supportedLocales: supportedLocales, + localizationsDelegates: localizationsDelegates, + navigatorKey: navigatorKey, + initialRoute: initialRoute, + onGenerateRoute: onGenerateRoute, + navigatorObservers: [ + SnackyNavigationObserver(), + ...navigatorObservers, + ], + ); + if (builder == null) { + return app; + } + return builder!(context, app); + }, ), ); } diff --git a/lib/src/theme/impaktfull_branding.dart b/lib/src/theme/impaktfull_branding.dart index 47bf217..fafa7b9 100644 --- a/lib/src/theme/impaktfull_branding.dart +++ b/lib/src/theme/impaktfull_branding.dart @@ -24,6 +24,7 @@ class ImpaktfullBranding { static const info = Color(0xFF7D64F2); static const warning = Color(0xFFC28E3F); static const divider = Color(0x26425563); + static const shadow = Color.fromARGB(10, 0, 0, 0); //Fonts static const font = 'Ubuntu'; diff --git a/lib/src/theme/impaktfull_theme.dart b/lib/src/theme/impaktfull_theme.dart index bed89f0..2c31678 100644 --- a/lib/src/theme/impaktfull_theme.dart +++ b/lib/src/theme/impaktfull_theme.dart @@ -1,17 +1,25 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; import 'package:impaktfull_ui/src/theme/impaktfull_branding.dart'; import 'package:impaktfull_ui/src/theme/theme_configurator.dart'; +import 'package:impaktfull_ui/src/util/test_util.dart'; class ImpaktfullTheme { + static InteractiveInkFeatureFactory get _defaultSplashFactory => + TestUtil.isInTest + ? InkSparkle.constantTurbulenceSeedSplashFactory + : InkSparkle.splashFactory; + bool get useDarkStatusBar => (theme.colors.primary.computeLuminance() > 0.179) ? true : false; static ImpaktfullTheme impaktfullBranding({ ImpaktfullShadowTheme? shadows, + ImpaktfullBorderTheme? borders, ImpaktfullAssets? assets, ImpaktfullDimens? dimens, ImpaktfullDurations? durations, ImpaktfullLocalizations? localizations, + InteractiveInkFeatureFactory? splashFactory, }) => ImpaktfullTheme( colors: const ImpaktfullColorTheme( @@ -30,21 +38,8 @@ class ImpaktfullTheme { 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, - ), - ), + shadows: shadows ?? ImpaktfullShadowTheme.getDefaults(), + borders: borders ?? ImpaktfullBorderTheme.getDefaults(), textStyles: ImpaktfullTextStylesTheme( onCanvasPrimary: ImpaktfullTextStyleTheme.getTextStyleTheme( ImpaktfullBranding.textOnCanvasPrimary, ImpaktfullBranding.font), @@ -64,6 +59,7 @@ class ImpaktfullTheme { dimens: dimens ?? ImpaktfullDimens.getDefaults(), durations: durations ?? ImpaktfullDurations.getDefaults(), localizations: localizations ?? ImpaktfullLocalizations.getDefaults(), + splashFactory: splashFactory ?? _defaultSplashFactory, ); static ImpaktfullTheme fromColors({ @@ -72,10 +68,12 @@ class ImpaktfullTheme { required Color accent2, Color? accent3, ImpaktfullShadowTheme? shadows, + ImpaktfullBorderTheme? borders, ImpaktfullAssets? assets, ImpaktfullDimens? dimens, ImpaktfullDurations? durations, ImpaktfullLocalizations? localizations, + InteractiveInkFeatureFactory? splashFactory, }) => ImpaktfullTheme( colors: ImpaktfullColorTheme( @@ -94,21 +92,8 @@ class ImpaktfullTheme { 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, - ), - ), + shadows: shadows ?? ImpaktfullShadowTheme.getDefaults(), + borders: borders ?? ImpaktfullBorderTheme.getDefaults(), textStyles: ImpaktfullTextStylesTheme( onCanvasPrimary: ImpaktfullTextStyleTheme.getTextStyleTheme( ImpaktfullBranding.textOnCanvasPrimary, ImpaktfullBranding.font), @@ -128,24 +113,29 @@ class ImpaktfullTheme { dimens: dimens ?? ImpaktfullDimens.getDefaults(), durations: durations ?? ImpaktfullDurations.getDefaults(), localizations: localizations ?? ImpaktfullLocalizations.getDefaults(), + splashFactory: splashFactory ?? _defaultSplashFactory, ); final ImpaktfullColorTheme colors; final ImpaktfullShadowTheme shadows; + final ImpaktfullBorderTheme borders; final ImpaktfullTextStylesTheme textStyles; final ImpaktfullAssets assets; final ImpaktfullDimens dimens; final ImpaktfullDurations durations; final ImpaktfullLocalizations localizations; + final InteractiveInkFeatureFactory splashFactory; const ImpaktfullTheme({ required this.colors, required this.shadows, + required this.borders, required this.textStyles, required this.assets, required this.dimens, required this.durations, required this.localizations, + this.splashFactory = InkSparkle.constantTurbulenceSeedSplashFactory, }); static ImpaktfullTheme of(BuildContext context) => theme; @@ -186,13 +176,74 @@ class ImpaktfullColorTheme { } class ImpaktfullShadowTheme { - final BoxShadow card; - final BoxShadow selectedCard; + final BoxShadow? card; + final BoxShadow? selectedCard; + final BoxShadow? bottomNavigation; + final BoxShadow? button; const ImpaktfullShadowTheme({ required this.card, required this.selectedCard, + required this.bottomNavigation, + required this.button, + }); + + static ImpaktfullShadowTheme getDefaults({ + Color? accent1, + Color? selectedColor, + }) => + ImpaktfullShadowTheme( + card: const BoxShadow( + color: ImpaktfullBranding.shadow, + blurRadius: 20, + offset: Offset(0, 1), + spreadRadius: 4, + ), + selectedCard: BoxShadow( + color: selectedColor ?? + accent1?.withOpacity(0.4) ?? + ImpaktfullBranding.accent1.withOpacity(0.4), + blurRadius: 20, + offset: const Offset(0, 1), + spreadRadius: 4, + ), + bottomNavigation: const BoxShadow( + color: ImpaktfullBranding.shadow, + blurRadius: 20, + offset: Offset(0, 1), + spreadRadius: 4, + ), + button: const BoxShadow( + color: ImpaktfullBranding.shadow, + blurRadius: 20, + offset: Offset(0, 1), + spreadRadius: 4, + ), + ); +} + +class ImpaktfullBorderTheme { + final Border? card; + final Border? selectedCard; + + const ImpaktfullBorderTheme({ + required this.card, + required this.selectedCard, }); + + static ImpaktfullBorderTheme getDefaults({ + Color? accent1, + }) => + ImpaktfullBorderTheme( + card: Border.all( + color: Colors.transparent, + width: 2, + ), + selectedCard: Border.all( + color: accent1 ?? ImpaktfullBranding.accent1, + width: 2, + ), + ); } class ImpaktfullTextStylesTheme { diff --git a/lib/src/util/test_util.dart b/lib/src/util/test_util.dart new file mode 100644 index 0000000..850abe2 --- /dev/null +++ b/lib/src/util/test_util.dart @@ -0,0 +1,7 @@ +import 'dart:io'; + +class TestUtil { + TestUtil._(); + + static bool get isInTest => Platform.environment.containsKey('FLUTTER_TEST'); +}