Browse Source

Merge pull request #296 from Leptopoda/fix/nested_scaffold

remove nested scaffolds
pull/302/head
Kate 2 years ago committed by GitHub
parent
commit
03d992831f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 268
      packages/neon/neon/lib/src/pages/home.dart
  2. 9
      packages/neon/neon/lib/src/router.dart
  3. 1
      packages/neon/neon_files/lib/dialogs/choose_folder.dart
  4. 50
      packages/neon/neon_files/lib/pages/main.dart
  5. 247
      packages/neon/neon_files/lib/widgets/browser_view.dart
  6. 2
      packages/neon/neon_news/lib/neon_news.dart
  7. 4
      packages/neon/neon_news/lib/pages/folder.dart
  8. 85
      packages/neon/neon_news/lib/pages/main.dart
  9. 125
      packages/neon/neon_news/lib/widgets/articles_view.dart
  10. 29
      packages/neon/neon_news/lib/widgets/feed_floating_action_button.dart
  11. 54
      packages/neon/neon_news/lib/widgets/feeds_view.dart
  12. 24
      packages/neon/neon_news/lib/widgets/folder_floating_action_button.dart
  13. 67
      packages/neon/neon_news/lib/widgets/folders_view.dart
  14. 1
      packages/neon/neon_notes/lib/neon_notes.dart
  15. 4
      packages/neon/neon_notes/lib/pages/category.dart
  16. 67
      packages/neon/neon_notes/lib/pages/main.dart
  17. 32
      packages/neon/neon_notes/lib/widgets/notes_floating_action_button.dart
  18. 63
      packages/neon/neon_notes/lib/widgets/notes_view.dart

268
packages/neon/neon/lib/src/pages/home.dart

@ -438,161 +438,157 @@ class _HomePageState extends State<HomePage> {
), ),
); );
return Scaffold( return Row(
resizeToAvoidBottomInset: false, children: [
body: Row( if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[
children: [ drawer,
if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[ ],
drawer, Expanded(
], child: Scaffold(
Expanded( key: _scaffoldKey,
child: Scaffold( resizeToAvoidBottomInset: false,
key: _scaffoldKey, drawer: navigationMode == NavigationMode.drawer ? drawer : null,
resizeToAvoidBottomInset: false, appBar: AppBar(
drawer: navigationMode == NavigationMode.drawer ? drawer : null, scrolledUnderElevation: navigationMode != NavigationMode.drawer ? 0 : null,
appBar: AppBar( automaticallyImplyLeading: navigationMode == NavigationMode.drawer,
scrolledUnderElevation: navigationMode != NavigationMode.drawer ? 0 : null, leadingWidth: isQuickBar ? kQuickBarWidth : null,
automaticallyImplyLeading: navigationMode == NavigationMode.drawer, leading: isQuickBar
leadingWidth: isQuickBar ? kQuickBarWidth : null, ? Container(
leading: isQuickBar padding: const EdgeInsets.all(5),
? Container( child: capabilities.data?.capabilities.theming?.logo != null
padding: const EdgeInsets.all(5), ? NeonCachedUrlImage(
child: capabilities.data?.capabilities.theming?.logo != null url: capabilities.data!.capabilities.theming!.logo!,
? NeonCachedUrlImage( svgColor: Theme.of(context).iconTheme.color,
url: capabilities.data!.capabilities.theming!.logo!, )
svgColor: Theme.of(context).iconTheme.color, : null,
) )
: null, : null,
) title: Column(
: null, crossAxisAlignment: CrossAxisAlignment.start,
title: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Row(
children: [ children: [
Row( if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[
children: [ Flexible(
if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[ child: Text(
Flexible( appImplementations.data!
child: Text( .find(activeAppIDSnapshot.data!)!
appImplementations.data! .name(context),
.find(activeAppIDSnapshot.data!)!
.name(context),
),
),
],
if (appImplementations.error != null) ...[
const SizedBox(
width: 8,
),
NeonException(
appImplementations.error,
onRetry: _appsBloc.refresh,
onlyIcon: true,
),
],
if (appImplementations.loading) ...[
const SizedBox(
width: 8,
), ),
Expanded( ),
child: NeonLinearProgressIndicator( ],
color: Theme.of(context).appBarTheme.foregroundColor, if (appImplementations.error != null) ...[
), const SizedBox(
width: 8,
),
NeonException(
appImplementations.error,
onRetry: _appsBloc.refresh,
onlyIcon: true,
),
],
if (appImplementations.loading) ...[
const SizedBox(
width: 8,
),
Expanded(
child: NeonLinearProgressIndicator(
color: Theme.of(context).appBarTheme.foregroundColor,
), ),
], ),
], ],
),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
),
], ],
], ),
), if (accounts.length > 1) ...[
actions: [ Text(
if (notificationsAppImplementation.data != null) ...[ account.client.humanReadableID,
StreamBuilder<int>( style: Theme.of(context).textTheme.bodySmall,
stream: notificationsAppImplementation.data!.getUnreadCounter(_appsBloc),
builder: (final context, final unreadCounterSnapshot) {
final unreadCount = unreadCounterSnapshot.data ?? 0;
return IconButton(
key: Key('app-${notificationsAppImplementation.data!.id}'),
icon: NeonAppImplementationIcon(
appImplementation: notificationsAppImplementation.data!,
unreadCount: unreadCount,
color: unreadCount > 0
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground,
size: const Size.square(kAvatarSize * 2 / 3),
),
onPressed: () async {
await _openNotifications(
notificationsAppImplementation.data!,
accounts,
account,
);
},
);
},
), ),
], ],
IconButton( ],
icon: IntrinsicWidth( ),
child: NeonAccountAvatar( actions: [
account: account, if (notificationsAppImplementation.data != null) ...[
), StreamBuilder<int>(
), stream: notificationsAppImplementation.data!.getUnreadCounter(_appsBloc),
onPressed: () async { builder: (final context, final unreadCounterSnapshot) {
await Navigator.of(context).push( final unreadCount = unreadCounterSnapshot.data ?? 0;
MaterialPageRoute( return IconButton(
builder: (final context) => AccountSettingsPage( key: Key('app-${notificationsAppImplementation.data!.id}'),
bloc: _accountsBloc, icon: NeonAppImplementationIcon(
account: account, appImplementation: notificationsAppImplementation.data!,
), unreadCount: unreadCount,
color: unreadCount > 0
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground,
size: const Size.square(kAvatarSize * 2 / 3),
), ),
onPressed: () async {
await _openNotifications(
notificationsAppImplementation.data!,
accounts,
account,
);
},
); );
}, },
), ),
], ],
), IconButton(
body: Row( icon: IntrinsicWidth(
children: [ child: NeonAccountAvatar(
if (navigationMode == NavigationMode.quickBar) ...[ account: account,
drawer, ),
], ),
Expanded( onPressed: () async {
child: Column( await Navigator.of(context).push(
children: [ MaterialPageRoute(
if (appImplementations.data != null) ...[ builder: (final context) => AccountSettingsPage(
if (appImplementations.data!.isEmpty) ...[ bloc: _accountsBloc,
Expanded( account: account,
child: Center( ),
child: Text( ),
AppLocalizations.of(context) );
.errorNoCompatibleNextcloudAppsFound, },
textAlign: TextAlign.center, ),
), ],
),
body: Row(
children: [
if (navigationMode == NavigationMode.quickBar) ...[
drawer,
],
Expanded(
child: Column(
children: [
if (appImplementations.data != null) ...[
if (appImplementations.data!.isEmpty) ...[
Expanded(
child: Center(
child: Text(
AppLocalizations.of(context).errorNoCompatibleNextcloudAppsFound,
textAlign: TextAlign.center,
), ),
), ),
] else ...[ ),
if (activeAppIDSnapshot.hasData) ...[ ] else ...[
Expanded( if (activeAppIDSnapshot.hasData) ...[
child: appImplementations.data! Expanded(
.find(activeAppIDSnapshot.data!)! child: appImplementations.data!
.buildPage(context, _appsBloc), .find(activeAppIDSnapshot.data!)!
), .buildPage(context, _appsBloc),
], ),
], ],
], ],
], ],
), ],
), ),
], ),
), ],
), ),
), ),
], ),
), ],
); );
} }
return Container(); return Container();

9
packages/neon/neon/lib/src/router.dart

@ -30,12 +30,9 @@ class AppRouter extends RouterDelegate<Account> with ChangeNotifier, PopNavigato
] else ...[ ] else ...[
MaterialPage( MaterialPage(
name: 'home', name: 'home',
child: Scaffold( child: HomePage(
resizeToAvoidBottomInset: false, key: Key(currentConfiguration!.id),
body: HomePage( account: currentConfiguration!,
key: Key(currentConfiguration!.id),
account: currentConfiguration!,
),
), ),
), ),
], ],

1
packages/neon/neon_files/lib/dialogs/choose_folder.dart

@ -26,7 +26,6 @@ class FilesChooseFolderDialog extends StatelessWidget {
bloc: bloc, bloc: bloc,
filesBloc: filesBloc, filesBloc: filesBloc,
enableFileActions: false, enableFileActions: false,
enableCreateActions: false,
onlyShowDirectories: true, onlyShowDirectories: true,
), ),
), ),

50
packages/neon/neon_files/lib/pages/main.dart

@ -23,25 +23,39 @@ class _FilesMainPageState extends State<FilesMainPage> {
} }
@override @override
Widget build(final BuildContext context) => FilesBrowserView( Widget build(final BuildContext context) => Scaffold(
bloc: widget.bloc.browser, body: FilesBrowserView(
filesBloc: widget.bloc, bloc: widget.bloc.browser,
onPickFile: (final details) async { filesBloc: widget.bloc,
final sizeWarning = widget.bloc.options.downloadSizeWarning.value; onPickFile: (final details) async {
if (sizeWarning != null && details.size > sizeWarning) { final sizeWarning = widget.bloc.options.downloadSizeWarning.value;
// ignore: use_build_context_synchronously if (sizeWarning != null && details.size > sizeWarning) {
if (!(await showConfirmationDialog(
context,
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
AppLocalizations.of(context).filesConfirmDownloadSizeWarning( if (!(await showConfirmationDialog(
filesize(sizeWarning), context,
filesize(details.size), // ignore: use_build_context_synchronously
), AppLocalizations.of(context).filesConfirmDownloadSizeWarning(
))) { filesize(sizeWarning),
return; filesize(details.size),
),
))) {
return;
}
} }
} widget.bloc.openFile(details.path, details.etag!, details.mimeType);
widget.bloc.openFile(details.path, details.etag!, details.mimeType); },
}, ),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await showDialog(
context: context,
builder: (final context) => FilesChooseCreateDialog(
bloc: widget.bloc,
basePath: widget.bloc.browser.path.value,
),
);
},
child: const Icon(Icons.add),
),
); );
} }

247
packages/neon/neon_files/lib/widgets/browser_view.dart

@ -6,7 +6,6 @@ class FilesBrowserView extends StatefulWidget {
required this.filesBloc, required this.filesBloc,
this.onPickFile, this.onPickFile,
this.enableFileActions = true, this.enableFileActions = true,
this.enableCreateActions = true,
this.onlyShowDirectories = false, this.onlyShowDirectories = false,
super.key, super.key,
// ignore: prefer_asserts_with_message // ignore: prefer_asserts_with_message
@ -16,7 +15,6 @@ class FilesBrowserView extends StatefulWidget {
final FilesBloc filesBloc; final FilesBloc filesBloc;
final Function(FileDetails)? onPickFile; final Function(FileDetails)? onPickFile;
final bool enableFileActions; final bool enableFileActions;
final bool enableCreateActions;
final bool onlyShowDirectories; final bool onlyShowDirectories;
@override @override
@ -55,149 +53,132 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
} }
return false; return false;
}, },
child: Scaffold( child: SortBoxBuilder<FilesSortProperty, WebDavFile>(
resizeToAvoidBottomInset: false, sortBox: filesSortBox,
floatingActionButton: widget.enableCreateActions sortPropertyOption: widget.bloc.options.filesSortPropertyOption,
? FloatingActionButton( sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption,
onPressed: () async { input: files.data,
await showDialog( builder: (final context, final sorted) => NeonListView<Widget>(
context: context, scrollKey: 'files-${pathSnapshot.data!.join('/')}',
builder: (final context) => FilesChooseCreateDialog( withFloatingActionButton: true,
bloc: widget.filesBloc, items: [
basePath: widget.bloc.path.value, for (final uploadTask in sorted == null
), ? <UploadTask>[]
); : uploadTasksSnapshot.data!.where(
}, (final task) =>
child: const Icon(Icons.add), sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty,
) )) ...[
: null, StreamBuilder<int>(
body: SortBoxBuilder<FilesSortProperty, WebDavFile>( stream: uploadTask.progress,
sortBox: filesSortBox, builder: (final context, final uploadTaskProgressSnapshot) =>
sortPropertyOption: widget.bloc.options.filesSortPropertyOption, !uploadTaskProgressSnapshot.hasData
sortBoxOrderOption: widget.bloc.options.filesSortBoxOrderOption, ? Container()
input: files.data, : _buildFile(
builder: (final context, final sorted) => NeonListView<Widget>( context: context,
scrollKey: 'files-${pathSnapshot.data!.join('/')}', details: FileDetails(
withFloatingActionButton: true, path: uploadTask.path,
items: [ isDirectory: false,
for (final uploadTask in sorted == null size: uploadTask.size,
? <UploadTask>[] etag: null,
: uploadTasksSnapshot.data!.where( mimeType: null,
(final task) => lastModified: uploadTask.lastModified,
sorted.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty, hasPreview: null,
)) ...[ isFavorite: null,
StreamBuilder<int>(
stream: uploadTask.progress,
builder: (final context, final uploadTaskProgressSnapshot) =>
!uploadTaskProgressSnapshot.hasData
? Container()
: _buildFile(
context: context,
details: FileDetails(
path: uploadTask.path,
isDirectory: false,
size: uploadTask.size,
etag: null,
mimeType: null,
lastModified: uploadTask.lastModified,
hasPreview: null,
isFavorite: null,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: null,
), ),
), uploadProgress: uploadTaskProgressSnapshot.data,
], downloadProgress: null,
if (sorted != null) ...[ ),
for (final file in sorted) ...[ ),
if (!widget.onlyShowDirectories || file.isDirectory) ...[ ],
Builder( if (sorted != null) ...[
builder: (final context) { for (final file in sorted) ...[
final matchingUploadTasks = uploadTasksSnapshot.data! if (!widget.onlyShowDirectories || file.isDirectory) ...[
.where((final task) => _pathMatchesFile(task.path, file.name)); Builder(
final matchingDownloadTasks = downloadTasksSnapshot.data! builder: (final context) {
.where((final task) => _pathMatchesFile(task.path, file.name)); final matchingUploadTasks = uploadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
final matchingDownloadTasks = downloadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
return StreamBuilder<int?>( return StreamBuilder<int?>(
stream: matchingUploadTasks.isNotEmpty stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress ? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) =>
StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
: Stream.value(null), : Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) => builder: (final context, final downloadTaskProgressSnapshot) => _buildFile(
StreamBuilder<int?>( context: context,
stream: matchingDownloadTasks.isNotEmpty details: FileDetails(
? matchingDownloadTasks.first.progress path: [...widget.bloc.path.value, file.name],
: Stream.value(null), isDirectory: matchingUploadTasks.isEmpty && file.isDirectory,
builder: (final context, final downloadTaskProgressSnapshot) => _buildFile( size: matchingUploadTasks.isNotEmpty
context: context, ? matchingUploadTasks.first.size
details: FileDetails( : file.size!,
path: [...widget.bloc.path.value, file.name], etag: matchingUploadTasks.isNotEmpty ? null : file.etag,
isDirectory: matchingUploadTasks.isEmpty && file.isDirectory, mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType,
size: matchingUploadTasks.isNotEmpty lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.size ? matchingUploadTasks.first.lastModified
: file.size!, : file.lastModified!,
etag: matchingUploadTasks.isNotEmpty ? null : file.etag, hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType, isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite,
lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified
: file.lastModified!,
hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data,
), ),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data,
), ),
); ),
}, );
), },
], ),
], ],
], ],
], ],
isLoading: files.loading, ],
error: files.error, isLoading: files.loading,
onRefresh: widget.bloc.refresh, error: files.error,
builder: (final context, final widget) => widget, onRefresh: widget.bloc.refresh,
topScrollingChildren: [ builder: (final context, final widget) => widget,
Align( topScrollingChildren: [
alignment: Alignment.topLeft, Align(
child: Container( alignment: Alignment.topLeft,
margin: const EdgeInsets.symmetric( child: Container(
horizontal: 10, margin: const EdgeInsets.symmetric(
), horizontal: 10,
child: Wrap( ),
crossAxisAlignment: WrapCrossAlignment.center, child: Wrap(
children: <Widget>[ crossAxisAlignment: WrapCrossAlignment.center,
SizedBox( children: <Widget>[
height: 40, SizedBox(
child: InkWell( height: 40,
onTap: () { child: InkWell(
widget.bloc.setPath([]); onTap: () {
}, widget.bloc.setPath([]);
child: const Icon(Icons.house), },
), child: const Icon(Icons.house),
),
),
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
InkWell(
onTap: () {
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
},
child: Text(pathSnapshot.data![i]),
), ),
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[ ],
InkWell( ]
onTap: () { .intersperse(
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1)); const Icon(
}, Icons.keyboard_arrow_right,
child: Text(pathSnapshot.data![i]), size: 40,
), ),
], )
] .toList(),
.intersperse(
const Icon(
Icons.keyboard_arrow_right,
size: 40,
),
)
.toList(),
),
), ),
), ),
], ),
), ],
), ),
), ),
), ),

2
packages/neon/neon_news/lib/neon_news.dart

@ -36,8 +36,10 @@ part 'sort/articles.dart';
part 'sort/feeds.dart'; part 'sort/feeds.dart';
part 'sort/folders.dart'; part 'sort/folders.dart';
part 'widgets/articles_view.dart'; part 'widgets/articles_view.dart';
part 'widgets/feed_floating_action_button.dart';
part 'widgets/feed_icon.dart'; part 'widgets/feed_icon.dart';
part 'widgets/feeds_view.dart'; part 'widgets/feeds_view.dart';
part 'widgets/folder_floating_action_button.dart';
part 'widgets/folder_select.dart'; part 'widgets/folder_select.dart';
part 'widgets/folder_view.dart'; part 'widgets/folder_view.dart';
part 'widgets/folders_view.dart'; part 'widgets/folders_view.dart';

4
packages/neon/neon_news/lib/pages/folder.dart

@ -20,5 +20,9 @@ class NewsFolderPage extends StatelessWidget {
bloc: bloc, bloc: bloc,
folder: folder, folder: folder,
), ),
floatingActionButton: NewsFeedFloatingActionButton(
bloc: bloc,
folderID: folder.id,
),
); );
} }

85
packages/neon/neon_news/lib/pages/main.dart

@ -25,41 +25,52 @@ class _NewsMainPageState extends State<NewsMainPage> {
} }
@override @override
Widget build(final BuildContext context) => Scaffold( Widget build(final BuildContext context) {
resizeToAvoidBottomInset: false, final views = [
bottomNavigationBar: BottomNavigationBar( NewsArticlesView(
currentIndex: _index, bloc: widget.bloc.mainArticlesBloc,
onTap: (final index) { newsBloc: widget.bloc,
setState(() { ),
_index = index; NewsFoldersView(
}); bloc: widget.bloc,
}, ),
items: [ NewsFeedsView(
BottomNavigationBarItem( bloc: widget.bloc,
icon: const Icon(Icons.newspaper), ),
label: AppLocalizations.of(context).newsArticles, ];
),
BottomNavigationBarItem( final floatingActionButtons = [
icon: const Icon(Icons.folder), null,
label: AppLocalizations.of(context).newsFolders, NewsFolderFloatingActionButton(bloc: widget.bloc),
), NewsFeedFloatingActionButton(bloc: widget.bloc),
BottomNavigationBarItem( ];
icon: const Icon(Icons.rss_feed),
label: AppLocalizations.of(context).newsFeeds, return Scaffold(
), resizeToAvoidBottomInset: false,
], bottomNavigationBar: BottomNavigationBar(
), currentIndex: _index,
body: _index == 0 onTap: (final index) {
? NewsArticlesView( setState(() {
bloc: widget.bloc.mainArticlesBloc, _index = index;
newsBloc: widget.bloc, });
) },
: _index == 1 items: [
? NewsFoldersView( BottomNavigationBarItem(
bloc: widget.bloc, icon: const Icon(Icons.newspaper),
) label: AppLocalizations.of(context).newsArticles,
: NewsFeedsView( ),
bloc: widget.bloc, BottomNavigationBarItem(
), icon: const Icon(Icons.folder),
); label: AppLocalizations.of(context).newsFolders,
),
BottomNavigationBarItem(
icon: const Icon(Icons.rss_feed),
label: AppLocalizations.of(context).newsFeeds,
),
],
),
body: views[_index],
floatingActionButton: floatingActionButtons[_index],
);
}
} }

125
packages/neon/neon_news/lib/widgets/articles_view.dart

@ -29,73 +29,70 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
stream: widget.newsBloc.feeds, stream: widget.newsBloc.feeds,
builder: (final context, final feeds) => ResultBuilder<List<NextcloudNewsArticle>>( builder: (final context, final feeds) => ResultBuilder<List<NextcloudNewsArticle>>(
stream: widget.bloc.articles, stream: widget.bloc.articles,
builder: (final context, final articles) => Scaffold( builder: (final context, final articles) => SortBoxBuilder<ArticlesSortProperty, NextcloudNewsArticle>(
resizeToAvoidBottomInset: false, sortBox: articlesSortBox,
body: SortBoxBuilder<ArticlesSortProperty, NextcloudNewsArticle>( sortPropertyOption: widget.newsBloc.options.articlesSortPropertyOption,
sortBox: articlesSortBox, sortBoxOrderOption: widget.newsBloc.options.articlesSortBoxOrderOption,
sortPropertyOption: widget.newsBloc.options.articlesSortPropertyOption, input: articles.data,
sortBoxOrderOption: widget.newsBloc.options.articlesSortBoxOrderOption, builder: (final context, final sorted) => NeonListView<NextcloudNewsArticle>(
input: articles.data, scrollKey: 'news-articles',
builder: (final context, final sorted) => NeonListView<NextcloudNewsArticle>( items: feeds.data == null ? null : sorted,
scrollKey: 'news-articles', isLoading: articles.loading || feeds.loading,
items: feeds.data == null ? null : sorted, error: articles.error ?? feeds.error,
isLoading: articles.loading || feeds.loading, onRefresh: () async {
error: articles.error ?? feeds.error, await Future.wait([
onRefresh: () async { widget.bloc.refresh(),
await Future.wait([ widget.newsBloc.refresh(),
widget.bloc.refresh(), ]);
widget.newsBloc.refresh(), },
]); builder: (final context, final article) => _buildArticle(
}, context,
builder: (final context, final article) => _buildArticle( article,
context, feeds.data!.singleWhere((final feed) => feed.id == article.feedId),
article, ),
feeds.data!.singleWhere((final feed) => feed.id == article.feedId), topFixedChildren: [
), StreamBuilder<FilterType>(
topFixedChildren: [ stream: widget.bloc.filterType,
StreamBuilder<FilterType>( builder: (final context, final selectedFilterTypeSnapshot) => Container(
stream: widget.bloc.filterType, margin: const EdgeInsets.symmetric(horizontal: 15),
builder: (final context, final selectedFilterTypeSnapshot) => Container( child: DropdownButton<FilterType>(
margin: const EdgeInsets.symmetric(horizontal: 15), isExpanded: true,
child: DropdownButton<FilterType>( value: selectedFilterTypeSnapshot.data,
isExpanded: true, items: [
value: selectedFilterTypeSnapshot.data, FilterType.all,
items: [ FilterType.unread,
FilterType.all, if (widget.bloc.listType == null) ...[
FilterType.unread, FilterType.starred,
if (widget.bloc.listType == null) ...[ ],
FilterType.starred, ].map<DropdownMenuItem<FilterType>>(
], (final a) {
].map<DropdownMenuItem<FilterType>>( late final String label;
(final a) { switch (a) {
late final String label; case FilterType.all:
switch (a) { label = AppLocalizations.of(context).newsFilterAll;
case FilterType.all: break;
label = AppLocalizations.of(context).newsFilterAll; case FilterType.unread:
break; label = AppLocalizations.of(context).newsFilterUnread;
case FilterType.unread: break;
label = AppLocalizations.of(context).newsFilterUnread; case FilterType.starred:
break; label = AppLocalizations.of(context).newsFilterStarred;
case FilterType.starred: break;
label = AppLocalizations.of(context).newsFilterStarred; default:
break; throw Exception('FilterType $a should not be shown');
default: }
throw Exception('FilterType $a should not be shown'); return DropdownMenuItem(
} value: a,
return DropdownMenuItem( child: Text(label),
value: a, );
child: Text(label),
);
},
).toList(),
onChanged: (final value) {
widget.bloc.setFilterType(value!);
}, },
), ).toList(),
onChanged: (final value) {
widget.bloc.setFilterType(value!);
},
), ),
), ),
], ),
), ],
), ),
), ),
), ),

29
packages/neon/neon_news/lib/widgets/feed_floating_action_button.dart

@ -0,0 +1,29 @@
part of '../neon_news.dart';
class NewsFeedFloatingActionButton extends StatelessWidget {
const NewsFeedFloatingActionButton({
required this.bloc,
this.folderID,
super.key,
});
final NewsBloc bloc;
final int? folderID;
@override
Widget build(final BuildContext context) => FloatingActionButton(
onPressed: () async {
final result = await showDialog<List>(
context: context,
builder: (final context) => NewsAddFeedDialog(
bloc: bloc,
folderID: folderID,
),
);
if (result != null) {
bloc.addFeed(result[0] as String, result[1] as int?);
}
},
child: const Icon(Icons.add),
);
}

54
packages/neon/neon_news/lib/widgets/feeds_view.dart

@ -15,42 +15,24 @@ class NewsFeedsView extends StatelessWidget {
stream: bloc.folders, stream: bloc.folders,
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>( builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
stream: bloc.feeds, stream: bloc.feeds,
builder: (final context, final feeds) => Scaffold( builder: (final context, final feeds) => SortBoxBuilder<FeedsSortProperty, NextcloudNewsFeed>(
resizeToAvoidBottomInset: false, sortBox: feedsSortBox,
floatingActionButton: FloatingActionButton( sortPropertyOption: bloc.options.feedsSortPropertyOption,
onPressed: () async { sortBoxOrderOption: bloc.options.feedsSortBoxOrderOption,
final result = await showDialog<List>( input: folders.data == null
context: context, ? null
builder: (final context) => NewsAddFeedDialog( : feeds.data?.where((final f) => folderID == null || f.folderId == folderID).toList(),
bloc: bloc, builder: (final context, final sorted) => NeonListView<NextcloudNewsFeed>(
folderID: folderID, scrollKey: 'news-feeds',
), withFloatingActionButton: true,
); items: sorted,
if (result != null) { isLoading: feeds.loading || folders.loading,
bloc.addFeed(result[0] as String, result[1] as int?); error: feeds.error ?? folders.error,
} onRefresh: bloc.refresh,
}, builder: (final context, final feed) => _buildFeed(
child: const Icon(Icons.add), context,
), feed,
body: SortBoxBuilder<FeedsSortProperty, NextcloudNewsFeed>( folders.data!,
sortBox: feedsSortBox,
sortPropertyOption: bloc.options.feedsSortPropertyOption,
sortBoxOrderOption: bloc.options.feedsSortBoxOrderOption,
input: folders.data == null
? null
: feeds.data?.where((final f) => folderID == null || f.folderId == folderID).toList(),
builder: (final context, final sorted) => NeonListView<NextcloudNewsFeed>(
scrollKey: 'news-feeds',
withFloatingActionButton: true,
items: sorted,
isLoading: feeds.loading || folders.loading,
error: feeds.error ?? folders.error,
onRefresh: bloc.refresh,
builder: (final context, final feed) => _buildFeed(
context,
feed,
folders.data!,
),
), ),
), ),
), ),

24
packages/neon/neon_news/lib/widgets/folder_floating_action_button.dart

@ -0,0 +1,24 @@
part of '../neon_news.dart';
class NewsFolderFloatingActionButton extends StatelessWidget {
const NewsFolderFloatingActionButton({
required this.bloc,
super.key,
});
final NewsBloc bloc;
@override
Widget build(final BuildContext context) => FloatingActionButton(
onPressed: () async {
final result = await showDialog<String>(
context: context,
builder: (final context) => const NewsCreateFolderDialog(),
);
if (result != null) {
bloc.createFolder(result);
}
},
child: const Icon(Icons.add),
);
}

67
packages/neon/neon_news/lib/widgets/folders_view.dart

@ -9,47 +9,32 @@ class NewsFoldersView extends StatelessWidget {
final NewsBloc bloc; final NewsBloc bloc;
@override @override
Widget build(final BuildContext context) => Scaffold( Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>(
resizeToAvoidBottomInset: false, stream: bloc.folders,
floatingActionButton: FloatingActionButton( builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
onPressed: () async { stream: bloc.feeds,
final result = await showDialog<String>( builder: (final context, final feeds) => SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
context: context, sortBox: foldersSortBox,
builder: (final context) => const NewsCreateFolderDialog(), sortPropertyOption: bloc.options.foldersSortPropertyOption,
); sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption,
if (result != null) { input: feeds.data == null
bloc.createFolder(result); ? null
} : folders.data
}, ?.map(
child: const Icon(Icons.add), (final folder) => FolderFeedsWrapper(
), folder,
body: ResultBuilder<List<NextcloudNewsFolder>>( feeds.data!.where((final feed) => feed.folderId == folder.id).toList(),
stream: bloc.folders, ),
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>( )
stream: bloc.feeds, .toList(),
builder: (final context, final feeds) => SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>( builder: (final context, final sorted) => NeonListView<FolderFeedsWrapper>(
sortBox: foldersSortBox, scrollKey: 'news-folders',
sortPropertyOption: bloc.options.foldersSortPropertyOption, withFloatingActionButton: true,
sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption, items: sorted,
input: feeds.data == null isLoading: feeds.loading || folders.loading,
? null error: feeds.error ?? folders.error,
: folders.data onRefresh: bloc.refresh,
?.map( builder: _buildFolder,
(final folder) => FolderFeedsWrapper(
folder,
feeds.data!.where((final feed) => feed.folderId == folder.id).toList(),
),
)
.toList(),
builder: (final context, final sorted) => NeonListView<FolderFeedsWrapper>(
scrollKey: 'news-folders',
withFloatingActionButton: true,
items: sorted,
isLoading: feeds.loading || folders.loading,
error: feeds.error ?? folders.error,
onRefresh: bloc.refresh,
builder: _buildFolder,
),
), ),
), ),
), ),

1
packages/neon/neon_notes/lib/neon_notes.dart

@ -31,6 +31,7 @@ part 'utils/category_color.dart';
part 'utils/exception_handler.dart'; part 'utils/exception_handler.dart';
part 'widgets/categories_view.dart'; part 'widgets/categories_view.dart';
part 'widgets/category_select.dart'; part 'widgets/category_select.dart';
part 'widgets/notes_floating_action_button.dart';
part 'widgets/notes_view.dart'; part 'widgets/notes_view.dart';
class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> { class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {

4
packages/neon/neon_notes/lib/pages/category.dart

@ -20,5 +20,9 @@ class NotesCategoryPage extends StatelessWidget {
bloc: bloc, bloc: bloc,
category: category.name, category: category.name,
), ),
floatingActionButton: NotesFloatingActionButton(
bloc: bloc,
category: category.name,
),
); );
} }

67
packages/neon/neon_notes/lib/pages/main.dart

@ -25,32 +25,43 @@ class _NotesMainPageState extends State<NotesMainPage> {
} }
@override @override
Widget build(final BuildContext context) => Scaffold( Widget build(final BuildContext context) {
resizeToAvoidBottomInset: false, final views = [
bottomNavigationBar: BottomNavigationBar( NotesView(
currentIndex: _index, bloc: widget.bloc,
onTap: (final index) { ),
setState(() { NotesCategoriesView(
_index = index; bloc: widget.bloc,
}); ),
}, ];
items: [
BottomNavigationBarItem( final floatingActionButtons = [
icon: const Icon(Icons.note), NotesFloatingActionButton(bloc: widget.bloc),
label: AppLocalizations.of(context).notesNotes, null,
), ];
BottomNavigationBarItem(
icon: const Icon(MdiIcons.tag), return Scaffold(
label: AppLocalizations.of(context).notesCategories, resizeToAvoidBottomInset: false,
), bottomNavigationBar: BottomNavigationBar(
], currentIndex: _index,
), onTap: (final index) {
body: _index == 0 setState(() {
? NotesView( _index = index;
bloc: widget.bloc, });
) },
: NotesCategoriesView( items: [
bloc: widget.bloc, BottomNavigationBarItem(
), icon: const Icon(Icons.note),
); label: AppLocalizations.of(context).notesNotes,
),
BottomNavigationBarItem(
icon: const Icon(MdiIcons.tag),
label: AppLocalizations.of(context).notesCategories,
),
],
),
body: views[_index],
floatingActionButton: floatingActionButtons[_index],
);
}
} }

32
packages/neon/neon_notes/lib/widgets/notes_floating_action_button.dart

@ -0,0 +1,32 @@
part of '../neon_notes.dart';
class NotesFloatingActionButton extends StatelessWidget {
const NotesFloatingActionButton({
required this.bloc,
this.category,
super.key,
});
final NotesBloc bloc;
final String? category;
@override
Widget build(final BuildContext context) => FloatingActionButton(
onPressed: () async {
final result = await showDialog<List>(
context: context,
builder: (final context) => NotesCreateNoteDialog(
bloc: bloc,
category: category,
),
);
if (result != null) {
bloc.createNote(
title: result[0] as String,
category: result[1] as String? ?? '',
);
}
},
child: const Icon(Icons.add),
);
}

63
packages/neon/neon_notes/lib/widgets/notes_view.dart

@ -13,52 +13,31 @@ class NotesView extends StatelessWidget {
@override @override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>( Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>(
stream: bloc.notes, stream: bloc.notes,
builder: (final context, final notes) => Scaffold( builder: (final context, final notes) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
resizeToAvoidBottomInset: false, sortBox: notesSortBox,
floatingActionButton: FloatingActionButton( sortPropertyOption: bloc.options.notesSortPropertyOption,
onPressed: () async { sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
final result = await showDialog<List>( input: category != null
context: context, ? notes.data?.where((final note) => note.favorite && note.category == category).toList()
builder: (final context) => NotesCreateNoteDialog( : notes.data?.where((final note) => note.favorite).toList(),
bloc: bloc, builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
category: category,
),
);
if (result != null) {
bloc.createNote(
title: result[0] as String,
category: result[1] as String? ?? '',
);
}
},
child: const Icon(Icons.add),
),
body: SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
sortBox: notesSortBox, sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption, sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption, sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null input: category != null
? notes.data?.where((final note) => note.favorite && note.category == category).toList() ? notes.data?.where((final note) => !note.favorite && note.category == category).toList()
: notes.data?.where((final note) => note.favorite).toList(), : notes.data?.where((final note) => !note.favorite).toList(),
builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>( builder: (final context, final sortedNonFavorites) => NeonListView<NextcloudNotesNote>(
sortBox: notesSortBox, scrollKey: 'notes-notes',
sortPropertyOption: bloc.options.notesSortPropertyOption, withFloatingActionButton: true,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption, items: [
input: category != null ...?sortedFavorites,
? notes.data?.where((final note) => !note.favorite && note.category == category).toList() ...?sortedNonFavorites,
: notes.data?.where((final note) => !note.favorite).toList(), ],
builder: (final context, final sortedNonFavorites) => NeonListView<NextcloudNotesNote>( isLoading: notes.loading,
scrollKey: 'notes-notes', error: notes.error,
withFloatingActionButton: true, onRefresh: bloc.refresh,
items: [ builder: _buildNote,
...?sortedFavorites,
...?sortedNonFavorites,
],
isLoading: notes.loading,
error: notes.error,
onRefresh: bloc.refresh,
builder: _buildNote,
),
), ),
), ),
), ),

Loading…
Cancel
Save