diff --git a/catalyst_voices/lib/pages/spaces/drawer/spaces_drawer.dart b/catalyst_voices/lib/pages/spaces/drawer/spaces_drawer.dart index 187d3543308..719c6a1ff42 100644 --- a/catalyst_voices/lib/pages/spaces/drawer/spaces_drawer.dart +++ b/catalyst_voices/lib/pages/spaces/drawer/spaces_drawer.dart @@ -12,7 +12,7 @@ import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:flutter/material.dart'; -class SpacesDrawer extends StatelessWidget { +class SpacesDrawer extends StatefulWidget { final Space space; final Map spacesShortcutsActivators; final bool isUnlocked; @@ -24,18 +24,55 @@ class SpacesDrawer extends StatelessWidget { this.isUnlocked = false, }); + @override + State createState() => _SpacesDrawerState(); +} + +class _SpacesDrawerState extends State { + late final PageController _pageController; + + @override + void initState() { + super.initState(); + + final initialPage = Space.values.indexOf(widget.space); + _pageController = PageController(initialPage: initialPage); + } + + @override + void didUpdateWidget(covariant SpacesDrawer oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.space != oldWidget.space) { + final page = Space.values.indexOf(widget.space); + unawaited( + _pageController.animateToPage( + page, + duration: const Duration(milliseconds: 150), + curve: Curves.easeIn, + ), + ); + } + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return VoicesDrawer( bottom: VoicesDrawerSpaceChooser( - currentSpace: space, + currentSpace: widget.space, onChanged: (space) => space.go(context), onOverallTap: () { Scaffold.of(context).closeDrawer(); unawaited(const OverallSpacesRoute().push(context)); }, builder: (context, value, child) { - final shortcutActivator = spacesShortcutsActivators[value]; + final shortcutActivator = widget.spacesShortcutsActivators[value]; return VoicesPlainTooltip( message: value.localizedName(context.l10n), @@ -46,15 +83,41 @@ class SpacesDrawer extends StatelessWidget { ); }, ), - children: [ - _menuBuilder(), - ], + child: Column( + children: [ + const SizedBox(height: 12), + const BrandHeader(), + Expanded( + child: PageView.builder( + physics: const NeverScrollableScrollPhysics(), + controller: _pageController, + itemCount: Space.values.length, + itemBuilder: (context, index) { + final space = Space.values[index]; + + return Padding( + key: ValueKey('Drawer${space}MenuKey'), + padding: const EdgeInsets.symmetric(horizontal: 12), + child: _menuBuilder( + context, + space: space, + ), + ); + }, + ), + ), + const SizedBox(height: 12), + ], + ), ); } - Widget _menuBuilder() { + Widget _menuBuilder( + BuildContext context, { + required Space space, + }) { return switch (space) { - _ when !isUnlocked => GuestMenu(space: space), + _ when !widget.isUnlocked => GuestMenu(space: space), Space.treasury => const IndividualPrivateCampaigns(), Space.workspace => const MyPrivateProposals(), Space.voting => const VotingRounds(), diff --git a/catalyst_voices/lib/widgets/avatars/voices_avatar.dart b/catalyst_voices/lib/widgets/avatars/voices_avatar.dart index f75da4608fe..b2570c6ed5e 100644 --- a/catalyst_voices/lib/widgets/avatars/voices_avatar.dart +++ b/catalyst_voices/lib/widgets/avatars/voices_avatar.dart @@ -58,6 +58,7 @@ class VoicesAvatar extends StatelessWidget { child: DefaultTextStyle( style: Theme.of(context).textTheme.bodyLarge!.copyWith( fontSize: 18, + height: 1, color: foregroundColor ?? Theme.of(context).colorScheme.primary, ), diff --git a/catalyst_voices/lib/widgets/drawer/voices_drawer.dart b/catalyst_voices/lib/widgets/drawer/voices_drawer.dart index 54d5df78b87..730851c1b04 100644 --- a/catalyst_voices/lib/widgets/drawer/voices_drawer.dart +++ b/catalyst_voices/lib/widgets/drawer/voices_drawer.dart @@ -6,24 +6,22 @@ import 'package:flutter/material.dart'; /// A custom [Drawer] component that implements the Voices style /// navigation drawer. /// -/// By default it has a header with a logo and a close button. -/// To provide menu items fill in the [children] list. /// To add a sticky bottom menu item provide [bottom] widget. /// /// The [VoicesDrawer] is indented to be used as the [Scaffold.drawer]. /// Menu items should primarily be constructed as [VoicesListTile]s. class VoicesDrawer extends StatelessWidget { - /// The menu items displayed from the top to the bottom in a vertical list. - final List children; - /// The sticky menu item at the bottom. final Widget? bottom; + /// This widget is main "body" of [VoicesDrawer]. + final Widget child; + /// The default constructor for the [VoicesDrawer]. const VoicesDrawer({ super.key, - required this.children, this.bottom, + required this.child, }); @override @@ -45,16 +43,7 @@ class VoicesDrawer extends StatelessWidget { shape: const RoundedRectangleBorder(), child: Column( children: [ - Expanded( - child: ListView( - physics: const ClampingScrollPhysics(), - padding: const EdgeInsets.all(12), - children: [ - const _Header(), - ...children, - ], - ), - ), + Expanded(child: child), if (bottom != null) Padding( padding: const EdgeInsets.only( @@ -72,27 +61,6 @@ class VoicesDrawer extends StatelessWidget { } } -class _Header extends StatelessWidget { - const _Header(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Theme.of(context).brandAssets.brand.logo(context).buildPicture(), - IconButton( - onPressed: Navigator.of(context).pop, - icon: VoicesAssets.icons.x.buildIcon(size: 22), - ), - ], - ), - ); - } -} - /// A builder that builds menu items for the [VoicesDrawerChooser]. /// /// The builder might provide a completely different widget diff --git a/catalyst_voices/lib/widgets/headers/brand_header.dart b/catalyst_voices/lib/widgets/headers/brand_header.dart new file mode 100644 index 00000000000..5cd46afd663 --- /dev/null +++ b/catalyst_voices/lib/widgets/headers/brand_header.dart @@ -0,0 +1,29 @@ +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:flutter/material.dart'; + +class BrandHeader extends StatelessWidget { + final EdgeInsetsGeometry padding; + + const BrandHeader({ + super.key, + this.padding = const EdgeInsets.all(8), + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Theme.of(context).brandAssets.brand.logo(context).buildPicture(), + IconButton( + onPressed: Navigator.of(context).pop, + icon: VoicesAssets.icons.x.buildIcon(size: 22), + ), + ], + ), + ); + } +} diff --git a/catalyst_voices/lib/widgets/widgets.dart b/catalyst_voices/lib/widgets/widgets.dart index a2761b94e0c..7b0172eefd2 100644 --- a/catalyst_voices/lib/widgets/widgets.dart +++ b/catalyst_voices/lib/widgets/widgets.dart @@ -27,6 +27,7 @@ export 'drawer/voices_drawer.dart'; export 'drawer/voices_drawer_space_chooser.dart'; export 'footers/links_page_footer.dart'; export 'footers/standard_links_page_footer.dart'; +export 'headers/brand_header.dart'; export 'headers/section_header.dart'; export 'headers/segment_header.dart'; export 'indicators/process_progress_indicator.dart'; diff --git a/catalyst_voices/uikit_example/lib/examples/voices_navigation_example.dart b/catalyst_voices/uikit_example/lib/examples/voices_navigation_example.dart index 326777275cc..5488d42a0e1 100644 --- a/catalyst_voices/uikit_example/lib/examples/voices_navigation_example.dart +++ b/catalyst_voices/uikit_example/lib/examples/voices_navigation_example.dart @@ -27,56 +27,61 @@ class VoicesNavigationExample extends StatelessWidget { body: const Center(child: Text('Content')), drawer: VoicesDrawer( bottom: const _DrawerChooser(), - children: [ - VoicesExpandableListTile( - title: const Text('My Dashboard'), - leading: VoicesAssets.icons.home.buildIcon(), - trailing: VoicesAssets.icons.eye.buildIcon(), - expandedChildren: [ - VoicesListTile( - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('My Catalyst Proposals'), - onTap: () {}, - ), - VoicesListTile( - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('My Actions'), - onTap: () {}, - ), - VoicesListTile( - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('Catalyst Campaign Timeline'), - onTap: () {}, - ), - ], - ), - const Divider(), - VoicesListTile( - leading: VoicesAssets.icons.user.buildIcon(), - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('Catalyst Roles'), - onTap: () => Navigator.pop(context), - ), - VoicesListTile( - leading: VoicesAssets.icons.annotation.buildIcon(), - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('Feedback'), - onTap: () => Navigator.pop(context), - ), - const Divider(), - VoicesListTile( - leading: VoicesAssets.icons.arrowRight.buildIcon(), - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('Catalyst Gitbook documentation'), - onTap: () => Navigator.pop(context), - ), - VoicesListTile( - leading: VoicesAssets.icons.arrowRight.buildIcon(), - trailing: VoicesAssets.icons.eye.buildIcon(), - title: const Text('Opportunity board'), - onTap: () => Navigator.pop(context), - ), - ], + child: ListView( + padding: const EdgeInsets.all(12), + physics: const ClampingScrollPhysics(), + children: [ + const BrandHeader(), + VoicesExpandableListTile( + title: const Text('My Dashboard'), + leading: VoicesAssets.icons.home.buildIcon(), + trailing: VoicesAssets.icons.eye.buildIcon(), + expandedChildren: [ + VoicesListTile( + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('My Catalyst Proposals'), + onTap: () {}, + ), + VoicesListTile( + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('My Actions'), + onTap: () {}, + ), + VoicesListTile( + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('Catalyst Campaign Timeline'), + onTap: () {}, + ), + ], + ), + const Divider(), + VoicesListTile( + leading: VoicesAssets.icons.user.buildIcon(), + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('Catalyst Roles'), + onTap: () => Navigator.pop(context), + ), + VoicesListTile( + leading: VoicesAssets.icons.annotation.buildIcon(), + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('Feedback'), + onTap: () => Navigator.pop(context), + ), + const Divider(), + VoicesListTile( + leading: VoicesAssets.icons.arrowRight.buildIcon(), + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('Catalyst Gitbook documentation'), + onTap: () => Navigator.pop(context), + ), + VoicesListTile( + leading: VoicesAssets.icons.arrowRight.buildIcon(), + trailing: VoicesAssets.icons.eye.buildIcon(), + title: const Text('Opportunity board'), + onTap: () => Navigator.pop(context), + ), + ], + ), ), ); }