From 8cdee9cb00aa2062091170343d0edb97804c2d11 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Wed, 14 Jun 2023 17:44:30 +0200 Subject: [PATCH] neon: refactor Drawer make neon use a NavigationDrawer migrating the drawer to m3 --- packages/neon/neon/lib/neon.dart | 2 + packages/neon/neon/lib/src/pages/home.dart | 150 +------------ .../neon/neon/lib/src/widgets/drawer.dart | 210 ++++++++++++++++++ packages/neon/neon/pubspec.yaml | 1 + 4 files changed, 214 insertions(+), 149 deletions(-) create mode 100644 packages/neon/neon/lib/src/widgets/drawer.dart diff --git a/packages/neon/neon/lib/neon.dart b/packages/neon/neon/lib/neon.dart index b84514e6..a2093959 100644 --- a/packages/neon/neon/lib/neon.dart +++ b/packages/neon/neon/lib/neon.dart @@ -25,6 +25,8 @@ import 'package:neon/src/blocs/blocs.dart'; import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/push_notification.dart'; import 'package:neon/src/router.dart'; +import 'package:neon/src/widgets/drawer.dart'; +import 'package:neon/src/widgets/drawer_destination.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as p; diff --git a/packages/neon/neon/lib/src/pages/home.dart b/packages/neon/neon/lib/src/pages/home.dart index 2b394b69..9eef37df 100644 --- a/packages/neon/neon/lib/src/pages/home.dart +++ b/packages/neon/neon/lib/src/pages/home.dart @@ -189,155 +189,7 @@ class _HomePageState extends State { final drawerAlwaysVisible = navigationMode == NavigationMode.drawerAlwaysVisible; - final drawer = Builder( - builder: (final context) => Drawer( - child: Column( - children: [ - Expanded( - child: Scrollbar( - controller: drawerScrollController, - interactive: true, - child: ListView( - controller: drawerScrollController, - // Needed for the drawer header to also render in the statusbar - padding: EdgeInsets.zero, - children: [ - Builder( - builder: (final context) { - if (accountsSnapshot.hasData) { - return DrawerHeader( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (capabilities.data != null) ...[ - if (capabilities.data!.capabilities.theming?.name != null) ...[ - Text( - capabilities.data!.capabilities.theming!.name!, - style: DefaultTextStyle.of(context).style.copyWith( - color: Theme.of(context).appBarTheme.foregroundColor, - ), - ), - ], - if (capabilities.data!.capabilities.theming?.logo != null) ...[ - Flexible( - child: NeonCachedUrlImage( - url: capabilities.data!.capabilities.theming!.logo!, - ), - ), - ], - ] else ...[ - NeonException( - capabilities.error, - onRetry: _capabilitiesBloc.refresh, - ), - NeonLinearProgressIndicator( - visible: capabilities.loading, - ), - ], - if (accounts.length != 1) ...[ - DropdownButtonHideUnderline( - child: DropdownButton( - isExpanded: true, - dropdownColor: Theme.of(context).colorScheme.primary, - iconEnabledColor: Theme.of(context).colorScheme.onBackground, - value: _account.id, - items: accounts - .map>( - (final account) => DropdownMenuItem( - value: account.id, - child: NeonAccountTile( - account: account, - dense: true, - textColor: - Theme.of(context).appBarTheme.foregroundColor, - ), - ), - ) - .toList(), - onChanged: (final id) { - if (id != null) { - _accountsBloc.setActiveAccount(accounts.find(id)!); - } - }, - ), - ), - ], - ], - ), - ); - } - return Container(); - }, - ), - NeonException( - appImplementations.error, - onRetry: _appsBloc.refresh, - ), - NeonLinearProgressIndicator( - visible: appImplementations.loading, - ), - if (appImplementations.data != null) ...[ - for (final appImplementation in appImplementations.data!) ...[ - StreamBuilder( - stream: appImplementation.getUnreadCounter(_appsBloc), - builder: (final context, final unreadCounterSnapshot) { - final unreadCount = unreadCounterSnapshot.data ?? 0; - - return ListTile( - key: Key('app-${appImplementation.id}'), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(appImplementation.name(context)), - if (unreadCount > 0) ...[ - Text( - unreadCount.toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - ], - ], - ), - leading: appImplementation.buildIcon(), - minLeadingWidth: 0, - onTap: () async { - await _appsBloc.setActiveApp(appImplementation.id); - - if (!mounted) { - return; - } - Scaffold.maybeOf(context)?.closeDrawer(); - }, - ); - }, - ), - ], - ], - ], - ), - ), - ), - ListTile( - key: const Key('settings'), - title: Text(AppLocalizations.of(context).settings), - leading: const Icon(Icons.settings), - minLeadingWidth: 0, - onTap: () async { - Scaffold.maybeOf(context)?.closeDrawer(); - const SettingsRoute().go(context); - }, - ), - ], - ), - ), - ); + const drawer = NeonDrawer(); final appBar = AppBar( automaticallyImplyLeading: !drawerAlwaysVisible, title: Column( diff --git a/packages/neon/neon/lib/src/widgets/drawer.dart b/packages/neon/neon/lib/src/widgets/drawer.dart new file mode 100644 index 00000000..8cb8cec0 --- /dev/null +++ b/packages/neon/neon/lib/src/widgets/drawer.dart @@ -0,0 +1,210 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:meta/meta.dart'; +import 'package:neon/l10n/localizations.dart'; +import 'package:neon/neon.dart'; +import 'package:neon/src/router.dart'; +import 'package:neon/src/widgets/drawer_destination.dart'; +import 'package:provider/provider.dart'; + +@internal +class NeonDrawer extends StatelessWidget { + const NeonDrawer({ + super.key, + }); + + @override + Widget build(final BuildContext context) { + final accountsBloc = Provider.of(context, listen: false); + final appsBloc = accountsBloc.activeAppsBloc; + + return StreamBuilder( + stream: appsBloc.appImplementations, + builder: (final context, final snapshot) { + if (snapshot.data?.data == null) { + return Container(); + } + + return _NeonDrawer( + apps: snapshot.data!.data!, + ); + }, + ); + } +} + +class _NeonDrawer extends StatefulWidget { + const _NeonDrawer({ + required this.apps, + }); + + final Iterable apps; + + @override + State<_NeonDrawer> createState() => __NeonDrawerState(); +} + +class __NeonDrawerState extends State<_NeonDrawer> with SingleTickerProviderStateMixin { + late TabController _tabController; + late AccountsBloc _accountsBloc; + late AppsBloc _appsBloc; + late List _apps; + + int _activeApp = 0; + + @override + void initState() { + super.initState(); + + _accountsBloc = Provider.of(context, listen: false); + _appsBloc = _accountsBloc.activeAppsBloc; + + _apps = widget.apps.toList(); + _activeApp = _apps.indexWhere((final app) => app.id == _appsBloc.activeAppID.valueOrNull); + + _tabController = TabController( + vsync: this, + length: widget.apps.length, + ); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + void onAppChange(final int index) { + Scaffold.maybeOf(context)?.closeDrawer(); + + // selected item is not a registered app like the SettingsPage + if (index >= _apps.length) { + const SettingsRoute().go(context); + return; + } + + setState(() { + _activeApp = index; + }); + + unawaited(_appsBloc.setActiveApp(_apps[index].id)); + //context.goNamed(apps[index].routeName); + } + + @override + Widget build(final BuildContext context) { + final appDestinations = _apps.map( + (final app) => NavigationDrawerDestinationExtension.fromNeonDestination( + app.destination(context), + ), + ); + + final drawer = NavigationDrawer( + selectedIndex: _activeApp, + onDestinationSelected: onAppChange, + children: [ + const NeonDrawerHeader(), + ...appDestinations, + NavigationDrawerDestination( + icon: const Icon(Icons.settings), + label: Text(AppLocalizations.of(context).settings), + ), + ], + ); + + return drawer; + } +} + +@internal +class NeonDrawerHeader extends StatelessWidget { + const NeonDrawerHeader({super.key}); + + @override + Widget build(final BuildContext context) { + final accountsBloc = Provider.of(context, listen: false); + final capabilitiesBloc = accountsBloc.activeCapabilitiesBloc; + + final accountSelecor = StreamBuilder>( + stream: accountsBloc.accounts, + builder: (final context, final accountsSnapshot) { + final accounts = accountsSnapshot.data; + if (accounts == null || accounts.length <= 1) { + return const SizedBox.shrink(); + } + + final items = accounts.map((final account) { + final child = NeonAccountTile( + account: account, + dense: true, + textColor: Theme.of(context).appBarTheme.foregroundColor, + ); + + return DropdownMenuItem( + value: account, + child: child, + ); + }).toList(); + + return DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + dropdownColor: Theme.of(context).colorScheme.primary, + iconEnabledColor: Theme.of(context).colorScheme.onBackground, + value: accountsBloc.activeAccount.value, + items: items, + onChanged: (final account) { + if (account == null) { + return; + } + + accountsBloc.setActiveAccount(account); + }, + ), + ); + }, + ); + + return ResultBuilder( + stream: capabilitiesBloc.capabilities, + builder: (final context, final capabilities) => DrawerHeader( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (capabilities.data != null) ...[ + if (capabilities.data!.capabilities.theming?.name != null) ...[ + Text( + capabilities.data!.capabilities.theming!.name!, + style: DefaultTextStyle.of(context).style.copyWith( + color: Theme.of(context).appBarTheme.foregroundColor, + ), + ), + ], + if (capabilities.data!.capabilities.theming?.logo != null) ...[ + Flexible( + child: NeonCachedUrlImage( + url: capabilities.data!.capabilities.theming!.logo!, + ), + ), + ], + ] else ...[ + NeonException( + capabilities.error, + onRetry: capabilitiesBloc.refresh, + ), + NeonLinearProgressIndicator( + visible: capabilities.loading, + ), + ], + accountSelecor, + ], + ), + ), + ); + } +} diff --git a/packages/neon/neon/pubspec.yaml b/packages/neon/neon/pubspec.yaml index f0d6865c..8d0b84aa 100644 --- a/packages/neon/neon/pubspec.yaml +++ b/packages/neon/neon/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: intl: ^0.18.0 json_annotation: ^4.8.1 material_design_icons_flutter: ^7.0.7296 + meta: ^1.9.1 nextcloud: git: url: https://github.com/provokateurin/nextcloud-neon