Browse Source

neon: remove nested scaffolds

pull/296/head
Nikolas Rimikis 2 years ago
parent
commit
1c3efedb3f
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  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(
resizeToAvoidBottomInset: false,
body: Row(
children: [
if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[
drawer,
],
Expanded(
child: Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
drawer: navigationMode == NavigationMode.drawer ? drawer : null,
appBar: AppBar(
scrolledUnderElevation: navigationMode != NavigationMode.drawer ? 0 : null,
automaticallyImplyLeading: navigationMode == NavigationMode.drawer,
leadingWidth: isQuickBar ? kQuickBarWidth : null,
leading: isQuickBar
? Container(
padding: const EdgeInsets.all(5),
child: capabilities.data?.capabilities.theming?.logo != null
? NeonCachedUrlImage(
url: capabilities.data!.capabilities.theming!.logo!,
svgColor: Theme.of(context).iconTheme.color,
)
: null,
)
: null,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[
Flexible(
child: Text(
appImplementations.data!
.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,
return Row(
children: [
if (navigationMode == NavigationMode.drawerAlwaysVisible) ...[
drawer,
],
Expanded(
child: Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: false,
drawer: navigationMode == NavigationMode.drawer ? drawer : null,
appBar: AppBar(
scrolledUnderElevation: navigationMode != NavigationMode.drawer ? 0 : null,
automaticallyImplyLeading: navigationMode == NavigationMode.drawer,
leadingWidth: isQuickBar ? kQuickBarWidth : null,
leading: isQuickBar
? Container(
padding: const EdgeInsets.all(5),
child: capabilities.data?.capabilities.theming?.logo != null
? NeonCachedUrlImage(
url: capabilities.data!.capabilities.theming!.logo!,
svgColor: Theme.of(context).iconTheme.color,
)
: null,
)
: null,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (appImplementations.data != null && activeAppIDSnapshot.hasData) ...[
Flexible(
child: Text(
appImplementations.data!
.find(activeAppIDSnapshot.data!)!
.name(context),
),
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,
),
],
],
),
actions: [
if (notificationsAppImplementation.data != null) ...[
StreamBuilder<int>(
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,
);
},
);
},
),
if (accounts.length > 1) ...[
Text(
account.client.humanReadableID,
style: Theme.of(context).textTheme.bodySmall,
),
],
IconButton(
icon: IntrinsicWidth(
child: NeonAccountAvatar(
account: account,
),
),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => AccountSettingsPage(
bloc: _accountsBloc,
account: account,
),
],
),
actions: [
if (notificationsAppImplementation.data != null) ...[
StreamBuilder<int>(
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,
);
},
);
},
),
],
),
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,
),
IconButton(
icon: IntrinsicWidth(
child: NeonAccountAvatar(
account: account,
),
),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => AccountSettingsPage(
bloc: _accountsBloc,
account: account,
),
),
);
},
),
],
),
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) ...[
Expanded(
child: appImplementations.data!
.find(activeAppIDSnapshot.data!)!
.buildPage(context, _appsBloc),
),
],
),
] else ...[
if (activeAppIDSnapshot.hasData) ...[
Expanded(
child: appImplementations.data!
.find(activeAppIDSnapshot.data!)!
.buildPage(context, _appsBloc),
),
],
],
],
),
],
),
],
),
),
],
),
),
],
),
),
],
);
}
return Container();

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

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

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

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

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

@ -36,8 +36,10 @@ part 'sort/articles.dart';
part 'sort/feeds.dart';
part 'sort/folders.dart';
part 'widgets/articles_view.dart';
part 'widgets/feed_floating_action_button.dart';
part 'widgets/feed_icon.dart';
part 'widgets/feeds_view.dart';
part 'widgets/folder_floating_action_button.dart';
part 'widgets/folder_select.dart';
part 'widgets/folder_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,
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
Widget build(final BuildContext context) => Scaffold(
resizeToAvoidBottomInset: false,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (final index) {
setState(() {
_index = index;
});
},
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.newspaper),
label: AppLocalizations.of(context).newsArticles,
),
BottomNavigationBarItem(
icon: const Icon(Icons.folder),
label: AppLocalizations.of(context).newsFolders,
),
BottomNavigationBarItem(
icon: const Icon(Icons.rss_feed),
label: AppLocalizations.of(context).newsFeeds,
),
],
),
body: _index == 0
? NewsArticlesView(
bloc: widget.bloc.mainArticlesBloc,
newsBloc: widget.bloc,
)
: _index == 1
? NewsFoldersView(
bloc: widget.bloc,
)
: NewsFeedsView(
bloc: widget.bloc,
),
);
Widget build(final BuildContext context) {
final views = [
NewsArticlesView(
bloc: widget.bloc.mainArticlesBloc,
newsBloc: widget.bloc,
),
NewsFoldersView(
bloc: widget.bloc,
),
NewsFeedsView(
bloc: widget.bloc,
),
];
final floatingActionButtons = [
null,
NewsFolderFloatingActionButton(bloc: widget.bloc),
NewsFeedFloatingActionButton(bloc: widget.bloc),
];
return Scaffold(
resizeToAvoidBottomInset: false,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (final index) {
setState(() {
_index = index;
});
},
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.newspaper),
label: AppLocalizations.of(context).newsArticles,
),
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,
builder: (final context, final feeds) => ResultBuilder<List<NextcloudNewsArticle>>(
stream: widget.bloc.articles,
builder: (final context, final articles) => Scaffold(
resizeToAvoidBottomInset: false,
body: SortBoxBuilder<ArticlesSortProperty, NextcloudNewsArticle>(
sortBox: articlesSortBox,
sortPropertyOption: widget.newsBloc.options.articlesSortPropertyOption,
sortBoxOrderOption: widget.newsBloc.options.articlesSortBoxOrderOption,
input: articles.data,
builder: (final context, final sorted) => NeonListView<NextcloudNewsArticle>(
scrollKey: 'news-articles',
items: feeds.data == null ? null : sorted,
isLoading: articles.loading || feeds.loading,
error: articles.error ?? feeds.error,
onRefresh: () async {
await Future.wait([
widget.bloc.refresh(),
widget.newsBloc.refresh(),
]);
},
builder: (final context, final article) => _buildArticle(
context,
article,
feeds.data!.singleWhere((final feed) => feed.id == article.feedId),
),
topFixedChildren: [
StreamBuilder<FilterType>(
stream: widget.bloc.filterType,
builder: (final context, final selectedFilterTypeSnapshot) => Container(
margin: const EdgeInsets.symmetric(horizontal: 15),
child: DropdownButton<FilterType>(
isExpanded: true,
value: selectedFilterTypeSnapshot.data,
items: [
FilterType.all,
FilterType.unread,
if (widget.bloc.listType == null) ...[
FilterType.starred,
],
].map<DropdownMenuItem<FilterType>>(
(final a) {
late final String label;
switch (a) {
case FilterType.all:
label = AppLocalizations.of(context).newsFilterAll;
break;
case FilterType.unread:
label = AppLocalizations.of(context).newsFilterUnread;
break;
case FilterType.starred:
label = AppLocalizations.of(context).newsFilterStarred;
break;
default:
throw Exception('FilterType $a should not be shown');
}
return DropdownMenuItem(
value: a,
child: Text(label),
);
},
).toList(),
onChanged: (final value) {
widget.bloc.setFilterType(value!);
builder: (final context, final articles) => SortBoxBuilder<ArticlesSortProperty, NextcloudNewsArticle>(
sortBox: articlesSortBox,
sortPropertyOption: widget.newsBloc.options.articlesSortPropertyOption,
sortBoxOrderOption: widget.newsBloc.options.articlesSortBoxOrderOption,
input: articles.data,
builder: (final context, final sorted) => NeonListView<NextcloudNewsArticle>(
scrollKey: 'news-articles',
items: feeds.data == null ? null : sorted,
isLoading: articles.loading || feeds.loading,
error: articles.error ?? feeds.error,
onRefresh: () async {
await Future.wait([
widget.bloc.refresh(),
widget.newsBloc.refresh(),
]);
},
builder: (final context, final article) => _buildArticle(
context,
article,
feeds.data!.singleWhere((final feed) => feed.id == article.feedId),
),
topFixedChildren: [
StreamBuilder<FilterType>(
stream: widget.bloc.filterType,
builder: (final context, final selectedFilterTypeSnapshot) => Container(
margin: const EdgeInsets.symmetric(horizontal: 15),
child: DropdownButton<FilterType>(
isExpanded: true,
value: selectedFilterTypeSnapshot.data,
items: [
FilterType.all,
FilterType.unread,
if (widget.bloc.listType == null) ...[
FilterType.starred,
],
].map<DropdownMenuItem<FilterType>>(
(final a) {
late final String label;
switch (a) {
case FilterType.all:
label = AppLocalizations.of(context).newsFilterAll;
break;
case FilterType.unread:
label = AppLocalizations.of(context).newsFilterUnread;
break;
case FilterType.starred:
label = AppLocalizations.of(context).newsFilterStarred;
break;
default:
throw Exception('FilterType $a should not be shown');
}
return DropdownMenuItem(
value: a,
child: Text(label),
);
},
),
).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,
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
stream: bloc.feeds,
builder: (final context, final feeds) => Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: 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),
),
body: SortBoxBuilder<FeedsSortProperty, NextcloudNewsFeed>(
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!,
),
builder: (final context, final feeds) => SortBoxBuilder<FeedsSortProperty, NextcloudNewsFeed>(
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;
@override
Widget build(final BuildContext context) => Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: 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),
),
body: ResultBuilder<List<NextcloudNewsFolder>>(
stream: bloc.folders,
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
stream: bloc.feeds,
builder: (final context, final feeds) => SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
sortBox: foldersSortBox,
sortPropertyOption: bloc.options.foldersSortPropertyOption,
sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption,
input: feeds.data == null
? null
: folders.data
?.map(
(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,
),
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>(
stream: bloc.folders,
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
stream: bloc.feeds,
builder: (final context, final feeds) => SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
sortBox: foldersSortBox,
sortPropertyOption: bloc.options.foldersSortPropertyOption,
sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption,
input: feeds.data == null
? null
: folders.data
?.map(
(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 'widgets/categories_view.dart';
part 'widgets/category_select.dart';
part 'widgets/notes_floating_action_button.dart';
part 'widgets/notes_view.dart';
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,
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
Widget build(final BuildContext context) => Scaffold(
resizeToAvoidBottomInset: false,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (final index) {
setState(() {
_index = index;
});
},
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.note),
label: AppLocalizations.of(context).notesNotes,
),
BottomNavigationBarItem(
icon: const Icon(MdiIcons.tag),
label: AppLocalizations.of(context).notesCategories,
),
],
),
body: _index == 0
? NotesView(
bloc: widget.bloc,
)
: NotesCategoriesView(
bloc: widget.bloc,
),
);
Widget build(final BuildContext context) {
final views = [
NotesView(
bloc: widget.bloc,
),
NotesCategoriesView(
bloc: widget.bloc,
),
];
final floatingActionButtons = [
NotesFloatingActionButton(bloc: widget.bloc),
null,
];
return Scaffold(
resizeToAvoidBottomInset: false,
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (final index) {
setState(() {
_index = index;
});
},
items: [
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
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>(
stream: bloc.notes,
builder: (final context, final notes) => Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: 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),
),
body: SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
builder: (final context, final notes) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notes.data?.where((final note) => note.favorite && note.category == category).toList()
: notes.data?.where((final note) => note.favorite).toList(),
builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notes.data?.where((final note) => note.favorite && note.category == category).toList()
: notes.data?.where((final note) => note.favorite).toList(),
builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notes.data?.where((final note) => !note.favorite && note.category == category).toList()
: notes.data?.where((final note) => !note.favorite).toList(),
builder: (final context, final sortedNonFavorites) => NeonListView<NextcloudNotesNote>(
scrollKey: 'notes-notes',
withFloatingActionButton: true,
items: [
...?sortedFavorites,
...?sortedNonFavorites,
],
isLoading: notes.loading,
error: notes.error,
onRefresh: bloc.refresh,
builder: _buildNote,
),
? notes.data?.where((final note) => !note.favorite && note.category == category).toList()
: notes.data?.where((final note) => !note.favorite).toList(),
builder: (final context, final sortedNonFavorites) => NeonListView<NextcloudNotesNote>(
scrollKey: 'notes-notes',
withFloatingActionButton: true,
items: [
...?sortedFavorites,
...?sortedNonFavorites,
],
isLoading: notes.loading,
error: notes.error,
onRefresh: bloc.refresh,
builder: _buildNote,
),
),
),

Loading…
Cancel
Save