From 550aaf81e5a06a9ef0981e42ae5948d5e9133543 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 2 Nov 2023 15:09:24 +0100 Subject: [PATCH 1/5] perf(neon): do not use the spread operator for building lists Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/src/app.dart | 6 +- packages/neon/neon/lib/src/pages/login.dart | 83 +++--- .../lib/src/pages/login_check_account.dart | 3 +- .../src/pages/login_check_server_status.dart | 3 +- .../neon/neon/lib/src/pages/login_flow.dart | 45 +-- .../lib/src/pages/nextcloud_app_settings.dart | 39 +-- .../neon/neon/lib/src/pages/settings.dart | 281 +++++++++--------- .../neon/lib/src/widgets/account_tile.dart | 7 +- .../neon/neon/lib/src/widgets/app_bar.dart | 118 ++++---- .../src/widgets/unified_search_results.dart | 6 +- 10 files changed, 303 insertions(+), 288 deletions(-) diff --git a/packages/neon/neon/lib/src/app.dart b/packages/neon/neon/lib/src/app.dart index 856eb204..fa071d50 100644 --- a/packages/neon/neon/lib/src/app.dart +++ b/packages/neon/neon/lib/src/app.dart @@ -120,13 +120,13 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra await tray.trayManager.setContextMenu( tray.Menu( items: [ - for (final app in _appImplementations) ...[ - tray.MenuItem( + ..._appImplementations.map( + (final app) => tray.MenuItem( key: 'app_${app.id}', label: app.nameFromLocalization(localizations), // TODO: Add icons which should work on macOS and Windows ), - ], + ), tray.MenuItem.separator(), tray.MenuItem( key: 'show_hide', diff --git a/packages/neon/neon/lib/src/pages/login.dart b/packages/neon/neon/lib/src/pages/login.dart index 438c72b6..bfbbcd0a 100644 --- a/packages/neon/neon/lib/src/pages/login.dart +++ b/packages/neon/neon/lib/src/pages/login.dart @@ -67,56 +67,55 @@ class _LoginPageState extends State { branding.name, style: Theme.of(context).textTheme.titleLarge, ), - if (branding.showLoginWithNextcloud) ...[ - const SizedBox( - height: 10, + if (branding.showLoginWithNextcloud) + Padding( + padding: const EdgeInsets.only(top: 10), + child: Text(NeonLocalizations.of(context).loginWorksWith), ), - Text(NeonLocalizations.of(context).loginWorksWith), - const SizedBox( - height: 10, - ), - Semantics( - label: NeonLocalizations.of(context).nextcloud, - child: const NextcloudLogo(), + if (branding.showLoginWithNextcloud) + Padding( + padding: const EdgeInsets.only(top: 10), + child: Semantics( + label: NeonLocalizations.of(context).nextcloud, + child: const NextcloudLogo(), + ), ), - ], - const SizedBox( - height: 50, - ), - Form( - key: _formKey, - child: TextFormField( - focusNode: _focusNode, - controller: _controller, - decoration: InputDecoration( - hintText: 'https://...', - labelText: NeonLocalizations.of(context).loginUsingServerAddress, - suffixIcon: IconButton( - icon: const Icon(Icons.arrow_forward), - onPressed: () { - login(_controller.text); - }, + Padding( + padding: const EdgeInsets.only(top: 50), + child: Form( + key: _formKey, + child: TextFormField( + focusNode: _focusNode, + controller: _controller, + decoration: InputDecoration( + hintText: 'https://...', + labelText: NeonLocalizations.of(context).loginUsingServerAddress, + suffixIcon: IconButton( + icon: const Icon(Icons.arrow_forward), + onPressed: () { + login(_controller.text); + }, + ), ), + keyboardType: TextInputType.url, + validator: (final input) => validateHttpUrl(context, input), + onFieldSubmitted: login, + autofillHints: const [AutofillHints.url], ), - keyboardType: TextInputType.url, - validator: (final input) => validateHttpUrl(context, input), - onFieldSubmitted: login, - autofillHints: const [AutofillHints.url], ), ), - if (NeonPlatform.instance.canUseCamera) ...[ - const SizedBox( - height: 50, - ), - IconButton( - tooltip: NeonLocalizations.of(context).loginUsingQRcode, - icon: const Icon( - Icons.qr_code_scanner_rounded, - size: 60, + if (NeonPlatform.instance.canUseCamera) + Padding( + padding: const EdgeInsets.only(top: 50), + child: IconButton( + tooltip: NeonLocalizations.of(context).loginUsingQRcode, + icon: const Icon( + Icons.qr_code_scanner_rounded, + size: 60, + ), + onPressed: () => const LoginQRcodeRoute().go(context), ), - onPressed: () => const LoginQRcodeRoute().go(context), ), - ], ], ), ), diff --git a/packages/neon/neon/lib/src/pages/login_check_account.dart b/packages/neon/neon/lib/src/pages/login_check_account.dart index c1142bc1..df689ad1 100644 --- a/packages/neon/neon/lib/src/pages/login_check_account.dart +++ b/packages/neon/neon/lib/src/pages/login_check_account.dart @@ -65,7 +65,7 @@ class _LoginCheckAccountPageState extends State { builder: (final context, final state) => Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (state.hasError) ...[ + if (state.hasError) Builder( builder: (final context) { final details = NeonError.getDetails(state.error); @@ -77,7 +77,6 @@ class _LoginCheckAccountPageState extends State { ); }, ), - ], _buildAccountTile(state), Align( alignment: Alignment.bottomRight, diff --git a/packages/neon/neon/lib/src/pages/login_check_server_status.dart b/packages/neon/neon/lib/src/pages/login_check_server_status.dart index 042b024f..88e1fd79 100644 --- a/packages/neon/neon/lib/src/pages/login_check_server_status.dart +++ b/packages/neon/neon/lib/src/pages/login_check_server_status.dart @@ -65,12 +65,11 @@ class _LoginCheckServerStatusPageState extends State return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - if (state.hasError) ...[ + if (state.hasError) NeonValidationTile( title: NeonError.getDetails(state.error).getText(context), state: ValidationState.failure, ), - ], _buildServerVersionTile(state), _buildMaintenanceModeTile(state), Align( diff --git a/packages/neon/neon/lib/src/pages/login_flow.dart b/packages/neon/neon/lib/src/pages/login_flow.dart index ef50d6c7..2302e4ec 100644 --- a/packages/neon/neon/lib/src/pages/login_flow.dart +++ b/packages/neon/neon/lib/src/pages/login_flow.dart @@ -6,6 +6,7 @@ import 'package:neon/src/blocs/login_flow.dart'; import 'package:neon/src/router.dart'; import 'package:neon/src/widgets/error.dart'; import 'package:neon/src/widgets/linear_progress_indicator.dart'; +import 'package:nextcloud/core.dart' as core; import 'package:url_launcher/url_launcher_string.dart'; @internal @@ -65,29 +66,35 @@ class _LoginFlowPageState extends State { subject: bloc.init, builder: (final context, final init) => Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ - NeonLinearProgressIndicator( - visible: init.isLoading, - ), - NeonError( - init.error, - onRetry: bloc.refresh, - ), - if (init.hasData) ...[ - Text(NeonLocalizations.of(context).loginSwitchToBrowserWindow), - const SizedBox( - height: 10, - ), - ElevatedButton( - onPressed: bloc.refresh, - child: Text(NeonLocalizations.of(context).loginOpenAgain), - ), - ], - ], + children: _buildChildren(init).toList(), ), ), ), ), ), ); + + Iterable _buildChildren(final Result init) sync* { + yield NeonLinearProgressIndicator( + visible: init.isLoading, + ); + + if (init.hasError) { + yield NeonError( + init.error, + onRetry: bloc.refresh, + ); + } + + if (init.hasData) { + yield Text(NeonLocalizations.of(context).loginSwitchToBrowserWindow); + yield Padding( + padding: const EdgeInsets.only(top: 10), + child: ElevatedButton( + onPressed: bloc.refresh, + child: Text(NeonLocalizations.of(context).loginOpenAgain), + ), + ); + } + } } diff --git a/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart b/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart index b26b5451..995aa977 100644 --- a/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart +++ b/packages/neon/neon/lib/src/pages/nextcloud_app_settings.dart @@ -39,23 +39,7 @@ class NextcloudAppSettingsPage extends StatelessWidget { ); final body = SettingsList( - categories: [ - for (final category in [...appImplementation.options.categories, null]) ...[ - if (appImplementation.options.options.where((final option) => option.category == category).isNotEmpty) ...[ - SettingsCategory( - title: Text( - category != null ? category.name(context) : NeonLocalizations.of(context).optionsCategoryOther, - ), - tiles: [ - for (final option - in appImplementation.options.options.where((final option) => option.category == category)) ...[ - OptionSettingsTile(option: option), - ], - ], - ), - ], - ], - ], + categories: _buildCategories(context).toList(), ); return Scaffold( @@ -71,4 +55,25 @@ class NextcloudAppSettingsPage extends StatelessWidget { ), ); } + + Iterable _buildCategories(final BuildContext context) sync* { + final appOptions = appImplementation.options; + final categories = [...appOptions.categories, null]; + + for (final category in categories) { + final matchedOptions = appOptions.options.where((final option) => option.category == category); + if (matchedOptions.isNotEmpty) { + yield SettingsCategory( + title: Text( + category != null ? category.name(context) : NeonLocalizations.of(context).optionsCategoryOther, + ), + tiles: [ + ...matchedOptions.map( + (final option) => OptionSettingsTile(option: option), + ), + ], + ); + } + } + } } diff --git a/packages/neon/neon/lib/src/pages/settings.dart b/packages/neon/neon/lib/src/pages/settings.dart index c2eb3098..dd35087c 100644 --- a/packages/neon/neon/lib/src/pages/settings.dart +++ b/packages/neon/neon/lib/src/pages/settings.dart @@ -14,7 +14,6 @@ import 'package:neon/src/settings/widgets/custom_settings_tile.dart'; import 'package:neon/src/settings/widgets/option_settings_tile.dart'; import 'package:neon/src/settings/widgets/settings_category.dart'; import 'package:neon/src/settings/widgets/settings_list.dart'; -import 'package:neon/src/settings/widgets/settings_tile.dart'; import 'package:neon/src/settings/widgets/text_settings_tile.dart'; import 'package:neon/src/theme/branding.dart'; import 'package:neon/src/theme/dialog.dart'; @@ -89,7 +88,6 @@ class _SettingsPageState extends State { final globalOptions = NeonProvider.of(context); final accountsBloc = NeonProvider.of(context); final appImplementations = NeonProvider.of>(context); - final branding = Branding.of(context); final appBar = AppBar( title: Text(NeonLocalizations.of(context).settings), @@ -116,24 +114,7 @@ class _SettingsPageState extends State { final body = SettingsList( initialCategory: widget.initialCategory?.name, categories: [ - SettingsCategory( - hasLeading: true, - title: Text(NeonLocalizations.of(context).settingsApps), - key: ValueKey(SettingsCategories.apps.name), - tiles: [ - for (final appImplementation in appImplementations) ...[ - if (appImplementation.options.options.isNotEmpty) ...[ - CustomSettingsTile( - leading: appImplementation.buildIcon(), - title: Text(appImplementation.name(context)), - onTap: () { - NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); - }, - ), - ], - ], - ], - ), + buildAppCategory(), SettingsCategory( title: Text(NeonLocalizations.of(context).optionsCategoryTheme), key: ValueKey(SettingsCategories.theme.name), @@ -159,7 +140,7 @@ class _SettingsPageState extends State { ], ), if (NeonPlatform.instance.canUsePushNotifications) buildNotificationsCategory(), - if (NeonPlatform.instance.canUseWindowManager) ...[ + if (NeonPlatform.instance.canUseWindowManager) SettingsCategory( title: Text(NeonLocalizations.of(context).optionsCategoryStartup), key: ValueKey(SettingsCategories.startup.name), @@ -172,8 +153,7 @@ class _SettingsPageState extends State { ), ], ), - ], - if (NeonPlatform.instance.canUseWindowManager && NeonPlatform.instance.canUseSystemTray) ...[ + if (NeonPlatform.instance.canUseWindowManager && NeonPlatform.instance.canUseSystemTray) SettingsCategory( title: Text(NeonLocalizations.of(context).optionsCategorySystemTray), key: ValueKey(SettingsCategories.systemTray.name), @@ -186,120 +166,8 @@ class _SettingsPageState extends State { ), ], ), - ], ...buildAccountCategory(), - SettingsCategory( - hasLeading: true, - title: Text(NeonLocalizations.of(context).optionsCategoryOther), - key: ValueKey(SettingsCategories.other.name), - tiles: [ - if (branding.sourceCodeURL != null) - CustomSettingsTile( - leading: Icon( - Icons.code, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(NeonLocalizations.of(context).sourceCode), - onTap: () async { - await launchUrlString( - branding.sourceCodeURL!, - mode: LaunchMode.externalApplication, - ); - }, - ), - if (branding.issueTrackerURL != null) - CustomSettingsTile( - leading: Icon( - MdiIcons.textBoxEditOutline, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(NeonLocalizations.of(context).issueTracker), - onTap: () async { - await launchUrlString( - branding.issueTrackerURL!, - mode: LaunchMode.externalApplication, - ); - }, - ), - CustomSettingsTile( - leading: Icon( - MdiIcons.scriptText, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(NeonLocalizations.of(context).licenses), - onTap: () async { - showLicensePage( - context: context, - applicationName: branding.name, - applicationIcon: branding.logo, - applicationLegalese: branding.legalese, - applicationVersion: NeonProvider.of(context).version, - ); - }, - ), - CustomSettingsTile( - leading: Icon( - MdiIcons.export, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(NeonLocalizations.of(context).settingsExport), - onTap: () async { - final settingsExportHelper = _buildSettingsExportHelper(context); - - try { - final fileName = 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json'; - - final data = settingsExportHelper.exportToFile(); - await saveFileWithPickDialog(fileName, data); - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - if (mounted) { - NeonError.showSnackbar(context, e); - } - } - }, - ), - CustomSettingsTile( - leading: Icon( - MdiIcons.import, - color: Theme.of(context).colorScheme.primary, - ), - title: Text(NeonLocalizations.of(context).settingsImport), - onTap: () async { - final settingsExportHelper = _buildSettingsExportHelper(context); - - try { - final result = await FilePicker.platform.pickFiles( - withReadStream: true, - ); - - if (result == null) { - return; - } - - if (!result.files.single.path!.endsWith('.json')) { - if (mounted) { - NeonError.showSnackbar( - context, - NeonLocalizations.of(context).settingsImportWrongFileExtension, - ); - } - return; - } - - await settingsExportHelper.applyFromFile(result.files.single.readStream); - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - if (mounted) { - NeonError.showSnackbar(context, e); - } - } - }, - ), - ], - ), + buildOtherCategory(), ], ); @@ -317,6 +185,30 @@ class _SettingsPageState extends State { ); } + Widget buildAppCategory() { + final appImplementations = NeonProvider.of>(context); + final appsWithOptions = appImplementations.where( + (final app) => app.options.options.isNotEmpty, + ); + + final tiles = appsWithOptions.map( + (final appImplementation) => CustomSettingsTile( + leading: appImplementation.buildIcon(), + title: Text(appImplementation.name(context)), + onTap: () { + NextcloudAppSettingsRoute(appid: appImplementation.id).go(context); + }, + ), + ); + + return SettingsCategory( + hasLeading: true, + title: Text(NeonLocalizations.of(context).settingsApps), + key: ValueKey(SettingsCategories.apps.name), + tiles: tiles.toList(), + ); + } + Widget buildNotificationsCategory() { final globalOptions = NeonProvider.of(context); @@ -416,6 +308,123 @@ class _SettingsPageState extends State { } } + Widget buildOtherCategory() { + final branding = Branding.of(context); + + return SettingsCategory( + hasLeading: true, + title: Text(NeonLocalizations.of(context).optionsCategoryOther), + key: ValueKey(SettingsCategories.other.name), + tiles: [ + if (branding.sourceCodeURL != null) + CustomSettingsTile( + leading: Icon( + Icons.code, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(NeonLocalizations.of(context).sourceCode), + onTap: () async { + await launchUrlString( + branding.sourceCodeURL!, + mode: LaunchMode.externalApplication, + ); + }, + ), + if (branding.issueTrackerURL != null) + CustomSettingsTile( + leading: Icon( + MdiIcons.textBoxEditOutline, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(NeonLocalizations.of(context).issueTracker), + onTap: () async { + await launchUrlString( + branding.issueTrackerURL!, + mode: LaunchMode.externalApplication, + ); + }, + ), + CustomSettingsTile( + leading: Icon( + MdiIcons.scriptText, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(NeonLocalizations.of(context).licenses), + onTap: () async { + showLicensePage( + context: context, + applicationName: branding.name, + applicationIcon: branding.logo, + applicationLegalese: branding.legalese, + applicationVersion: NeonProvider.of(context).version, + ); + }, + ), + CustomSettingsTile( + leading: Icon( + MdiIcons.export, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(NeonLocalizations.of(context).settingsExport), + onTap: () async { + final settingsExportHelper = _buildSettingsExportHelper(context); + + try { + final fileName = 'nextcloud-neon-settings-${DateTime.now().millisecondsSinceEpoch ~/ 1000}.json'; + + final data = settingsExportHelper.exportToFile(); + await saveFileWithPickDialog(fileName, data); + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + if (mounted) { + NeonError.showSnackbar(context, e); + } + } + }, + ), + CustomSettingsTile( + leading: Icon( + MdiIcons.import, + color: Theme.of(context).colorScheme.primary, + ), + title: Text(NeonLocalizations.of(context).settingsImport), + onTap: () async { + final settingsExportHelper = _buildSettingsExportHelper(context); + + try { + final result = await FilePicker.platform.pickFiles( + withReadStream: true, + ); + + if (result == null) { + return; + } + + if (!result.files.single.path!.endsWith('.json')) { + if (mounted) { + NeonError.showSnackbar( + context, + NeonLocalizations.of(context).settingsImportWrongFileExtension, + ); + } + return; + } + + await settingsExportHelper.applyFromFile(result.files.single.readStream); + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + if (mounted) { + NeonError.showSnackbar(context, e); + } + } + }, + ), + ], + ); + } + SettingsExportHelper _buildSettingsExportHelper(final BuildContext context) { final globalOptions = NeonProvider.of(context); final accountsBloc = NeonProvider.of(context); diff --git a/packages/neon/neon/lib/src/widgets/account_tile.dart b/packages/neon/neon/lib/src/widgets/account_tile.dart index fec541f8..a3c8614f 100644 --- a/packages/neon/neon/lib/src/widgets/account_tile.dart +++ b/packages/neon/neon/lib/src/widgets/account_tile.dart @@ -64,10 +64,11 @@ class NeonAccountTile extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - if (userDetails.isLoading) - const Expanded( - child: NeonLinearProgressIndicator(), + Expanded( + child: NeonLinearProgressIndicator( + visible: userDetails.isLoading, ), + ), if (userDetails.hasError) NeonError( userDetails.error, diff --git a/packages/neon/neon/lib/src/widgets/app_bar.dart b/packages/neon/neon/lib/src/widgets/app_bar.dart index ac06c62c..90ff34a9 100644 --- a/packages/neon/neon/lib/src/widgets/app_bar.dart +++ b/packages/neon/neon/lib/src/widgets/app_bar.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:intersperse/intersperse.dart'; import 'package:meta/meta.dart'; import 'package:neon/l10n/localizations.dart'; import 'package:neon/src/bloc/result.dart'; @@ -72,53 +73,47 @@ class _NeonAppBarState extends State { stream: unifiedSearchBloc.enabled, builder: (final context, final unifiedSearchEnabledSnapshot) { final unifiedSearchEnabled = unifiedSearchEnabledSnapshot.data ?? false; - return AppBar( - title: unifiedSearchEnabled - ? null - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (activeAppSnapshot.hasData) ...[ - Flexible( - child: Text( - activeAppSnapshot.requireData.name(context), - ), - ), - ], - if (appImplementations.hasError) ...[ - const SizedBox( - width: 8, - ), - NeonError( - appImplementations.error, - onRetry: appsBloc.refresh, - type: NeonErrorType.iconOnly, - ), - ], - if (appImplementations.isLoading) ...[ - const SizedBox( - width: 8, - ), - Expanded( - child: NeonLinearProgressIndicator( - color: Theme.of(context).appBarTheme.foregroundColor, - ), - ), - ], - ], - ), - if (accounts.length > 1) ...[ - Text( - account.humanReadableID, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ], + + Widget header = Row( + children: [ + if (activeAppSnapshot.hasData) + Flexible( + child: Text( + activeAppSnapshot.requireData.name(context), ), + ), + if (appImplementations.hasError) + NeonError( + appImplementations.error, + onRetry: appsBloc.refresh, + type: NeonErrorType.iconOnly, + ), + if (appImplementations.isLoading) + Expanded( + child: NeonLinearProgressIndicator( + color: Theme.of(context).appBarTheme.foregroundColor, + ), + ), + ].intersperse(const SizedBox(width: 8)).toList(), + ); + + if (accounts.length > 1) { + header = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + header, + Text( + account.humanReadableID, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ); + } + + return AppBar( + title: unifiedSearchEnabled ? null : header, actions: [ - if (unifiedSearchEnabled) ...[ + if (unifiedSearchEnabled) Flexible( child: SearchBar( focusNode: _searchBarFocusNode, @@ -137,10 +132,9 @@ class _NeonAppBarState extends State { ), ], ), - ), - ] else ...[ + ) + else const SearchIconButton(), - ], const NotificationIconButton(), const AccountSwitcherButton(), ], @@ -217,21 +211,25 @@ class _NotificationIconButtonState extends State { Future _openNotifications( final NotificationsAppInterface app, ) async { + Widget title = Text(app.name(context)); + + if (_accounts.length > 1) { + title = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title, + Text( + _account.humanReadableID, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ); + } + final page = Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(app.name(context)), - if (_accounts.length > 1) ...[ - Text( - _account.humanReadableID, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ], - ), + title: title, ), body: SafeArea( child: app.page, diff --git a/packages/neon/neon/lib/src/widgets/unified_search_results.dart b/packages/neon/neon/lib/src/widgets/unified_search_results.dart index 26949454..53d1a042 100644 --- a/packages/neon/neon/lib/src/widgets/unified_search_results.dart +++ b/packages/neon/neon/lib/src/widgets/unified_search_results.dart @@ -81,7 +81,7 @@ class NeonUnifiedSearchResults extends StatelessWidget { NeonLinearProgressIndicator( visible: result.isLoading, ), - if (entries.isEmpty) ...[ + if (entries.isEmpty) AdaptiveListTile( leading: const Icon( Icons.close, @@ -89,8 +89,7 @@ class NeonUnifiedSearchResults extends StatelessWidget { ), title: Text(NeonLocalizations.of(context).searchNoResults), ), - ], - for (final entry in entries) ...[ + for (final entry in entries) AdaptiveListTile( leading: NeonImageWrapper( size: const Size.square(largeIconSize), @@ -102,7 +101,6 @@ class NeonUnifiedSearchResults extends StatelessWidget { context.go(entry.resourceUrl); }, ), - ], ], ), ), From 84cc5744d1cda7eac97706bf3f52064a23caefb8 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 2 Nov 2023 15:26:44 +0100 Subject: [PATCH 2/5] perf(neon_files): do not use the spread operator for building lists Signed-off-by: Nikolas Rimikis --- .../neon_files/lib/dialogs/choose_create.dart | 3 +- packages/neon/neon_files/lib/options.dart | 11 +- .../neon/neon_files/lib/pages/details.dart | 122 +++++++++--------- .../neon/neon_files/lib/widgets/actions.dart | 9 +- .../lib/widgets/file_list_tile.dart | 19 ++- 5 files changed, 80 insertions(+), 84 deletions(-) diff --git a/packages/neon/neon_files/lib/dialogs/choose_create.dart b/packages/neon/neon_files/lib/dialogs/choose_create.dart index 38ad27d5..dbb68051 100644 --- a/packages/neon/neon_files/lib/dialogs/choose_create.dart +++ b/packages/neon/neon_files/lib/dialogs/choose_create.dart @@ -77,7 +77,7 @@ class _FilesChooseCreateDialogState extends State { } }, ), - if (NeonPlatform.instance.canUseCamera) ...[ + if (NeonPlatform.instance.canUseCamera) ListTile( leading: Icon( MdiIcons.cameraPlus, @@ -94,7 +94,6 @@ class _FilesChooseCreateDialogState extends State { } }, ), - ], ListTile( leading: Icon( MdiIcons.folderPlus, diff --git a/packages/neon/neon_files/lib/options.dart b/packages/neon/neon_files/lib/options.dart index 2e8d5ab0..f7799500 100644 --- a/packages/neon/neon_files/lib/options.dart +++ b/packages/neon/neon_files/lib/options.dart @@ -67,9 +67,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions { label: (final context) => FilesLocalizations.of(context).optionsUploadQueueParallelism, defaultValue: 4, values: { - for (var i = 1; i <= 16; i = i * 2) ...{ - i: (final _) => i.toString(), - }, + for (var i = 1; i <= 16; i = i * 2) i: (final _) => i.toString(), }, ); @@ -80,9 +78,7 @@ class FilesAppSpecificOptions extends NextcloudAppOptions { label: (final context) => FilesLocalizations.of(context).optionsDownloadQueueParallelism, defaultValue: 4, values: { - for (var i = 1; i <= 16; i = i * 2) ...{ - i: (final _) => i.toString(), - }, + for (var i = 1; i <= 16; i = i * 2) i: (final _) => i.toString(), }, ); @@ -96,9 +92,8 @@ class FilesAppSpecificOptions extends NextcloudAppOptions { 2 * 2024, 6 * 1024, 10 * 1024, - ]) ...{ + ]) _mb(i): (final _) => filesize(_mb(i)), - }, }; int _mb(final int i) => i * 1024 * 1024; diff --git a/packages/neon/neon_files/lib/pages/details.dart b/packages/neon/neon_files/lib/pages/details.dart index 36d50c1f..fab48f88 100644 --- a/packages/neon/neon_files/lib/pages/details.dart +++ b/packages/neon/neon_files/lib/pages/details.dart @@ -11,66 +11,72 @@ class FilesDetailsPage extends StatelessWidget { final FileDetails details; @override - Widget build(final BuildContext context) => Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: Text(details.name), - ), - body: SafeArea( - child: ListView( - primary: true, - children: [ - ColoredBox( - color: Theme.of(context).colorScheme.primary, - child: FilePreview( - bloc: bloc, - details: details, - color: Theme.of(context).colorScheme.onPrimary, - size: Size( - MediaQuery.of(context).size.width, - MediaQuery.of(context).size.height / 4, - ), + Widget build(final BuildContext context) { + final l10n = FilesLocalizations.of(context); + + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + title: Text(details.name), + ), + body: SafeArea( + child: ListView( + primary: true, + children: [ + ColoredBox( + color: Theme.of(context).colorScheme.primary, + child: FilePreview( + bloc: bloc, + details: details, + color: Theme.of(context).colorScheme.onPrimary, + size: Size( + MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height / 4, ), ), - DataTable( - headingRowHeight: 0, - columns: const [ - DataColumn(label: SizedBox()), - DataColumn(label: SizedBox()), - ], - rows: [ - for (final entry in { - details.isDirectory - ? FilesLocalizations.of(context).detailsFolderName - : FilesLocalizations.of(context).detailsFileName: details.name, - FilesLocalizations.of(context).detailsParentFolder: - details.path.length == 1 ? '/' : details.path.sublist(0, details.path.length - 1).join('/'), - if (details.size != null) ...{ - details.isDirectory - ? FilesLocalizations.of(context).detailsFolderSize - : FilesLocalizations.of(context).detailsFileSize: filesize(details.size, 1), - }, - if (details.lastModified != null) ...{ - FilesLocalizations.of(context).detailsLastModified: - details.lastModified!.toLocal().toIso8601String(), - }, - if (details.isFavorite != null) ...{ - FilesLocalizations.of(context).detailsIsFavorite: details.isFavorite! - ? FilesLocalizations.of(context).actionYes - : FilesLocalizations.of(context).actionNo, - }, - }.entries) ...[ - DataRow( - cells: [ - DataCell(Text(entry.key)), - DataCell(Text(entry.value)), - ], - ), - ], - ], - ), - ], - ), + ), + DataTable( + headingRowHeight: 0, + columns: const [ + DataColumn(label: SizedBox()), + DataColumn(label: SizedBox()), + ], + rows: [ + _buildDataRow( + details.isDirectory ? l10n.detailsFolderName : l10n.detailsFileName, + details.name, + ), + _buildDataRow( + l10n.detailsParentFolder, + details.path.length == 1 ? '/' : details.path.sublist(0, details.path.length - 1).join('/'), + ), + if (details.size != null) + _buildDataRow( + details.isDirectory ? l10n.detailsFolderSize : l10n.detailsFileSize, + filesize(details.size, 1), + ), + if (details.lastModified != null) + _buildDataRow( + l10n.detailsLastModified, + details.lastModified!.toLocal().toIso8601String(), + ), + if (details.isFavorite != null) + _buildDataRow( + l10n.detailsIsFavorite, + details.isFavorite! ? l10n.actionYes : l10n.actionNo, + ), + ], + ), + ], ), + ), + ); + } + + DataRow _buildDataRow(final String key, final String value) => DataRow( + cells: [ + DataCell(Text(key)), + DataCell(Text(value)), + ], ); } diff --git a/packages/neon/neon_files/lib/widgets/actions.dart b/packages/neon/neon_files/lib/widgets/actions.dart index aa9eb789..2022aa95 100644 --- a/packages/neon/neon_files/lib/widgets/actions.dart +++ b/packages/neon/neon_files/lib/widgets/actions.dart @@ -119,13 +119,12 @@ class FileActions extends StatelessWidget { @override Widget build(final BuildContext context) => PopupMenuButton( itemBuilder: (final context) => [ - if (!details.isDirectory && NeonPlatform.instance.canUseSharing) ...[ + if (!details.isDirectory && NeonPlatform.instance.canUseSharing) PopupMenuItem( value: FilesFileAction.share, child: Text(FilesLocalizations.of(context).actionShare), ), - ], - if (details.isFavorite != null) ...[ + if (details.isFavorite != null) PopupMenuItem( value: FilesFileAction.toggleFavorite, child: Text( @@ -134,7 +133,6 @@ class FileActions extends StatelessWidget { : FilesLocalizations.of(context).addToFavorites, ), ), - ], PopupMenuItem( value: FilesFileAction.details, child: Text(FilesLocalizations.of(context).details), @@ -152,12 +150,11 @@ class FileActions extends StatelessWidget { child: Text(FilesLocalizations.of(context).actionCopy), ), // TODO: https://github.com/provokateurin/nextcloud-neon/issues/4 - if (!details.isDirectory) ...[ + if (!details.isDirectory) PopupMenuItem( value: FilesFileAction.sync, child: Text(FilesLocalizations.of(context).actionSync), ), - ], PopupMenuItem( value: FilesFileAction.delete, child: Text(FilesLocalizations.of(context).actionDelete), diff --git a/packages/neon/neon_files/lib/widgets/file_list_tile.dart b/packages/neon/neon_files/lib/widgets/file_list_tile.dart index 85426f9f..0e1a0646 100644 --- a/packages/neon/neon_files/lib/widgets/file_list_tile.dart +++ b/packages/neon/neon_files/lib/widgets/file_list_tile.dart @@ -56,17 +56,16 @@ class FileListTile extends StatelessWidget { RelativeTime( date: details.lastModified!, ), - if (details.size != null && details.size! > 0) ...[ - const SizedBox( - width: 10, + if (details.size != null && details.size! > 0) + Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + filesize(details.size, 1), + style: DefaultTextStyle.of(context).style.copyWith( + color: Colors.grey, + ), + ), ), - Text( - filesize(details.size, 1), - style: DefaultTextStyle.of(context).style.copyWith( - color: Colors.grey, - ), - ), - ], ], ), leading: _FileIcon( From 4f4e161063328da71e04dfda062c1b106d07d255 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 2 Nov 2023 15:37:46 +0100 Subject: [PATCH 3/5] perf(neon_news): do not use the spread operator for building lists Signed-off-by: Nikolas Rimikis --- .../neon/neon_news/lib/dialogs/add_feed.dart | 25 +- .../neon/neon_news/lib/pages/article.dart | 4 +- .../neon_news/lib/widgets/articles_view.dart | 46 ++-- .../neon_news/lib/widgets/feeds_view.dart | 236 +++++++++--------- .../neon_notes/lib/dialogs/create_note.dart | 27 +- 5 files changed, 166 insertions(+), 172 deletions(-) diff --git a/packages/neon/neon_news/lib/dialogs/add_feed.dart b/packages/neon/neon_news/lib/dialogs/add_feed.dart index 4e99e8e1..53730abd 100644 --- a/packages/neon/neon_news/lib/dialogs/add_feed.dart +++ b/packages/neon/neon_news/lib/dialogs/add_feed.dart @@ -71,30 +71,29 @@ class _NewsAddFeedDialogState extends State { submit(); }, ), - if (widget.folderID == null) ...[ + if (widget.folderID == null && folders.hasError) Center( child: NeonError( folders.error, onRetry: widget.bloc.refresh, ), ), + if (widget.folderID == null) Center( child: NeonLinearProgressIndicator( visible: folders.isLoading, ), ), - if (folders.hasData) ...[ - NewsFolderSelect( - folders: folders.requireData, - value: folder, - onChanged: (final f) { - setState(() { - folder = f; - }); - }, - ), - ], - ], + if (widget.folderID == null && folders.hasData) + NewsFolderSelect( + folders: folders.requireData, + value: folder, + onChanged: (final f) { + setState(() { + folder = f; + }); + }, + ), ElevatedButton( onPressed: folders.hasData ? submit : null, child: Text(NewsLocalizations.of(context).feedAdd), diff --git a/packages/neon/neon_news/lib/pages/article.dart b/packages/neon/neon_news/lib/pages/article.dart index d088fe11..70b854d1 100644 --- a/packages/neon/neon_news/lib/pages/article.dart +++ b/packages/neon/neon_news/lib/pages/article.dart @@ -146,7 +146,7 @@ class _NewsArticlePageState extends State { ); }, ), - if (widget.url != null) ...[ + if (widget.url != null) IconButton( onPressed: () async { await launchUrlString( @@ -157,6 +157,7 @@ class _NewsArticlePageState extends State { tooltip: NewsLocalizations.of(context).articleOpenLink, icon: const Icon(Icons.open_in_new), ), + if (widget.url != null) IconButton( onPressed: () async { await Share.share(await _getURL()); @@ -164,7 +165,6 @@ class _NewsArticlePageState extends State { tooltip: NewsLocalizations.of(context).articleShare, icon: const Icon(Icons.share), ), - ], ], ), body: SafeArea( diff --git a/packages/neon/neon_news/lib/widgets/articles_view.dart b/packages/neon/neon_news/lib/widgets/articles_view.dart index 1a66b5e7..19f1d08e 100644 --- a/packages/neon/neon_news/lib/widgets/articles_view.dart +++ b/packages/neon/neon_news/lib/widgets/articles_view.dart @@ -63,30 +63,20 @@ class _NewsArticlesViewState extends State { isExpanded: true, value: selectedFilterTypeSnapshot.data, items: [ - FilterType.all, - FilterType.unread, - if (widget.bloc.listType == null) ...[ - FilterType.starred, - ], - ].map>( - (final a) { - late final String label; - switch (a) { - case FilterType.all: - label = NewsLocalizations.of(context).articlesFilterAll; - case FilterType.unread: - label = NewsLocalizations.of(context).articlesFilterUnread; - case FilterType.starred: - label = NewsLocalizations.of(context).articlesFilterStarred; - default: - throw Exception('FilterType $a should not be shown'); - } - return DropdownMenuItem( - value: a, - child: Text(label), - ); - }, - ).toList(), + _buildDropdownItem( + FilterType.all, + NewsLocalizations.of(context).articlesFilterAll, + ), + _buildDropdownItem( + FilterType.unread, + NewsLocalizations.of(context).articlesFilterUnread, + ), + if (widget.bloc.listType == null) + _buildDropdownItem( + FilterType.starred, + NewsLocalizations.of(context).articlesFilterStarred, + ), + ], onChanged: (final value) { widget.bloc.setFilterType(value!); }, @@ -99,6 +89,11 @@ class _NewsArticlesViewState extends State { ), ); + DropdownMenuItem _buildDropdownItem(final FilterType value, final String label) => DropdownMenuItem( + value: value, + child: Text(label), + ); + Widget _buildArticle( final BuildContext context, final news.Article article, @@ -116,13 +111,12 @@ class _NewsArticlesViewState extends State { : Theme.of(context).textTheme.titleMedium!.copyWith(color: Theme.of(context).disabledColor), ), ), - if (article.mediaThumbnail != null) ...[ + if (article.mediaThumbnail != null) NeonUrlImage( url: article.mediaThumbnail!, size: const Size(100, 50), fit: BoxFit.cover, ), - ], ], ), subtitle: Row( diff --git a/packages/neon/neon_news/lib/widgets/feeds_view.dart b/packages/neon/neon_news/lib/widgets/feeds_view.dart index f36b6672..d57ba8ad 100644 --- a/packages/neon/neon_news/lib/widgets/feeds_view.dart +++ b/packages/neon/neon_news/lib/widgets/feeds_view.dart @@ -42,128 +42,132 @@ class NewsFeedsView extends StatelessWidget { final BuildContext context, final news.Feed feed, final List folders, - ) => - ListTile( - title: Text( - feed.title, - style: feed.unreadCount! == 0 - ? Theme.of(context).textTheme.titleMedium!.copyWith(color: Theme.of(context).disabledColor) - : null, + ) { + Widget trailing = PopupMenuButton( + itemBuilder: (final context) => [ + PopupMenuItem( + value: NewsFeedAction.showURL, + child: Text(NewsLocalizations.of(context).feedShowURL), ), - subtitle: feed.unreadCount! > 0 - ? Text(NewsLocalizations.of(context).articlesUnread(feed.unreadCount!)) - : const SizedBox(), - leading: NewsFeedIcon(feed: feed), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (feed.updateErrorCount > 0) ...[ - IconButton( - onPressed: () async { - await showDialog( - context: context, - builder: (final context) => NewsFeedUpdateErrorDialog( - feed: feed, - ), - ); - }, - tooltip: NewsLocalizations.of(context).feedShowErrorMessage, - iconSize: 30, - icon: Text( - feed.updateErrorCount.toString(), - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), - ), - ), - ], - PopupMenuButton( - itemBuilder: (final context) => [ - PopupMenuItem( - value: NewsFeedAction.showURL, - child: Text(NewsLocalizations.of(context).feedShowURL), - ), - PopupMenuItem( - value: NewsFeedAction.delete, - child: Text(NewsLocalizations.of(context).actionDelete), - ), - PopupMenuItem( - value: NewsFeedAction.rename, - child: Text(NewsLocalizations.of(context).actionRename), - ), - if (folders.isNotEmpty) ...[ - PopupMenuItem( - value: NewsFeedAction.move, - child: Text(NewsLocalizations.of(context).actionMove), - ), - ], - ], - onSelected: (final action) async { - switch (action) { - case NewsFeedAction.showURL: - await showDialog( - context: context, - builder: (final context) => NewsFeedShowURLDialog( - feed: feed, - ), - ); - case NewsFeedAction.delete: - if (!context.mounted) { - return; - } - if (await showConfirmationDialog( - context, - NewsLocalizations.of(context).feedRemoveConfirm(feed.title), - )) { - bloc.removeFeed(feed.id); - } - case NewsFeedAction.rename: - if (!context.mounted) { - return; - } - final result = await showRenameDialog( - context: context, - title: NewsLocalizations.of(context).feedRename, - value: feed.title, - ); - if (result != null) { - bloc.renameFeed(feed.id, result); - } - case NewsFeedAction.move: - if (!context.mounted) { - return; - } - final result = await showDialog>( - context: context, - builder: (final context) => NewsMoveFeedDialog( - folders: folders, - feed: feed, - ), - ); - if (result != null) { - bloc.moveFeed(feed.id, result[0]); - } - } - }, - ), - ], + PopupMenuItem( + value: NewsFeedAction.delete, + child: Text(NewsLocalizations.of(context).actionDelete), ), - onLongPress: () { - if (feed.unreadCount! > 0) { - bloc.markFeedAsRead(feed.id); - } - }, - onTap: () async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (final context) => NewsFeedPage( - bloc: bloc, + PopupMenuItem( + value: NewsFeedAction.rename, + child: Text(NewsLocalizations.of(context).actionRename), + ), + if (folders.isNotEmpty) ...[ + PopupMenuItem( + value: NewsFeedAction.move, + child: Text(NewsLocalizations.of(context).actionMove), + ), + ], + ], + onSelected: (final action) async { + switch (action) { + case NewsFeedAction.showURL: + await showDialog( + context: context, + builder: (final context) => NewsFeedShowURLDialog( feed: feed, ), + ); + case NewsFeedAction.delete: + if (!context.mounted) { + return; + } + if (await showConfirmationDialog( + context, + NewsLocalizations.of(context).feedRemoveConfirm(feed.title), + )) { + bloc.removeFeed(feed.id); + } + case NewsFeedAction.rename: + if (!context.mounted) { + return; + } + final result = await showRenameDialog( + context: context, + title: NewsLocalizations.of(context).feedRename, + value: feed.title, + ); + if (result != null) { + bloc.renameFeed(feed.id, result); + } + case NewsFeedAction.move: + if (!context.mounted) { + return; + } + final result = await showDialog>( + context: context, + builder: (final context) => NewsMoveFeedDialog( + folders: folders, + feed: feed, + ), + ); + if (result != null) { + bloc.moveFeed(feed.id, result[0]); + } + } + }, + ); + if (feed.updateErrorCount > 0) { + trailing = Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () async { + await showDialog( + context: context, + builder: (final context) => NewsFeedUpdateErrorDialog( + feed: feed, + ), + ); + }, + tooltip: NewsLocalizations.of(context).feedShowErrorMessage, + iconSize: 30, + icon: Text( + feed.updateErrorCount.toString(), + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), ), - ); - }, + ), + trailing, + ], ); + } + + return ListTile( + title: Text( + feed.title, + style: feed.unreadCount! == 0 + ? Theme.of(context).textTheme.titleMedium!.copyWith(color: Theme.of(context).disabledColor) + : null, + ), + subtitle: feed.unreadCount! > 0 + ? Text(NewsLocalizations.of(context).articlesUnread(feed.unreadCount!)) + : const SizedBox(), + leading: NewsFeedIcon(feed: feed), + trailing: trailing, + onLongPress: () { + if (feed.unreadCount! > 0) { + bloc.markFeedAsRead(feed.id); + } + }, + onTap: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (final context) => NewsFeedPage( + bloc: bloc, + feed: feed, + ), + ), + ); + }, + ); + } } enum NewsFeedAction { diff --git a/packages/neon/neon_notes/lib/dialogs/create_note.dart b/packages/neon/neon_notes/lib/dialogs/create_note.dart index ea521f22..6f1c3add 100644 --- a/packages/neon/neon_notes/lib/dialogs/create_note.dart +++ b/packages/neon/neon_notes/lib/dialogs/create_note.dart @@ -53,28 +53,25 @@ class _NotesCreateNoteDialogState extends State { submit(); }, ), - if (widget.category == null) ...[ + if (widget.category == null && notes.hasError) Center( child: NeonError( notes.error, onRetry: widget.bloc.refresh, ), ), - Center( - child: NeonLinearProgressIndicator( - visible: notes.isLoading, - ), + if (widget.category == null && notes.isLoading) + const Center( + child: NeonLinearProgressIndicator(), + ), + if (widget.category == null && notes.hasData) + NotesCategorySelect( + categories: notes.requireData.map((final note) => note.category).toSet().toList(), + onChanged: (final category) { + selectedCategory = category; + }, + onSubmitted: submit, ), - if (notes.hasData) ...[ - NotesCategorySelect( - categories: notes.requireData.map((final note) => note.category).toSet().toList(), - onChanged: (final category) { - selectedCategory = category; - }, - onSubmitted: submit, - ), - ], - ], ElevatedButton( onPressed: submit, child: Text(NotesLocalizations.of(context).noteCreate), From 9b460d4b3aa75e1b57bfad804449e1c01ca3087a Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 2 Nov 2023 15:40:28 +0100 Subject: [PATCH 4/5] perf(neon_notes): do not use the spread operator for building lists Signed-off-by: Nikolas Rimikis --- .../neon_news/lib/widgets/feeds_view.dart | 3 +-- .../neon_notes/lib/dialogs/create_note.dart | 8 ++++--- .../lib/dialogs/select_category.dart | 14 +++++------ .../neon_notes/lib/widgets/notes_view.dart | 24 +++++++++---------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/neon/neon_news/lib/widgets/feeds_view.dart b/packages/neon/neon_news/lib/widgets/feeds_view.dart index d57ba8ad..cdb770d6 100644 --- a/packages/neon/neon_news/lib/widgets/feeds_view.dart +++ b/packages/neon/neon_news/lib/widgets/feeds_view.dart @@ -57,12 +57,11 @@ class NewsFeedsView extends StatelessWidget { value: NewsFeedAction.rename, child: Text(NewsLocalizations.of(context).actionRename), ), - if (folders.isNotEmpty) ...[ + if (folders.isNotEmpty) PopupMenuItem( value: NewsFeedAction.move, child: Text(NewsLocalizations.of(context).actionMove), ), - ], ], onSelected: (final action) async { switch (action) { diff --git a/packages/neon/neon_notes/lib/dialogs/create_note.dart b/packages/neon/neon_notes/lib/dialogs/create_note.dart index 6f1c3add..47a4db4a 100644 --- a/packages/neon/neon_notes/lib/dialogs/create_note.dart +++ b/packages/neon/neon_notes/lib/dialogs/create_note.dart @@ -60,9 +60,11 @@ class _NotesCreateNoteDialogState extends State { onRetry: widget.bloc.refresh, ), ), - if (widget.category == null && notes.isLoading) - const Center( - child: NeonLinearProgressIndicator(), + if (widget.category == null) + Center( + child: NeonLinearProgressIndicator( + visible: notes.isLoading, + ), ), if (widget.category == null && notes.hasData) NotesCategorySelect( diff --git a/packages/neon/neon_notes/lib/dialogs/select_category.dart b/packages/neon/neon_notes/lib/dialogs/select_category.dart index 04b4b275..436d25e4 100644 --- a/packages/neon/neon_notes/lib/dialogs/select_category.dart +++ b/packages/neon/neon_notes/lib/dialogs/select_category.dart @@ -36,18 +36,19 @@ class _NotesSelectCategoryDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Center( - child: NeonError( - notes.error, - onRetry: widget.bloc.refresh, + if (notes.hasError) + Center( + child: NeonError( + notes.error, + onRetry: widget.bloc.refresh, + ), ), - ), Center( child: NeonLinearProgressIndicator( visible: notes.isLoading, ), ), - if (notes.hasData) ...[ + if (notes.hasData) NotesCategorySelect( categories: notes.requireData.map((final note) => note.category).toSet().toList(), initialValue: widget.initialCategory, @@ -56,7 +57,6 @@ class _NotesSelectCategoryDialogState extends State { }, onSubmitted: submit, ), - ], ElevatedButton( onPressed: submit, child: Text(NotesLocalizations.of(context).noteSetCategory), diff --git a/packages/neon/neon_notes/lib/widgets/notes_view.dart b/packages/neon/neon_notes/lib/widgets/notes_view.dart index 7225ed17..d86c6805 100644 --- a/packages/neon/neon_notes/lib/widgets/notes_view.dart +++ b/packages/neon/neon_notes/lib/widgets/notes_view.dart @@ -45,20 +45,20 @@ class NotesView extends StatelessWidget { RelativeTime( date: DateTime.fromMillisecondsSinceEpoch(note.modified * 1000), ), - if (note.category.isNotEmpty) ...[ - const SizedBox( - width: 8, - ), - Icon( - MdiIcons.tag, - size: smallIconSize, - color: NotesCategoryColor.compute(note.category), + if (note.category.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Icon( + MdiIcons.tag, + size: smallIconSize, + color: NotesCategoryColor.compute(note.category), + ), ), - const SizedBox( - width: 2, + if (note.category.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text(note.category), ), - Text(note.category), - ], ], ), trailing: IconButton( From 22ac4ed496ffc45e9d1e99bc75f79ebfe64f2b78 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Thu, 2 Nov 2023 15:41:07 +0100 Subject: [PATCH 5/5] perf(neon_notifications): do not use the spread operator for building lists Signed-off-by: Nikolas Rimikis --- .../neon/neon_notifications/lib/pages/main.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/neon/neon_notifications/lib/pages/main.dart b/packages/neon/neon_notifications/lib/pages/main.dart index 7783f2d3..3b411922 100644 --- a/packages/neon/neon_notifications/lib/pages/main.dart +++ b/packages/neon/neon_notifications/lib/pages/main.dart @@ -61,15 +61,14 @@ class _NotificationsMainPageState extends State { subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (notification.message.isNotEmpty) ...[ - Text( - notification.message, - overflow: TextOverflow.ellipsis, - ), - const SizedBox( - height: 5, + if (notification.message.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Text( + notification.message, + overflow: TextOverflow.ellipsis, + ), ), - ], RelativeTime( date: DateTime.parse(notification.datetime), ),