diff --git a/lib/folder_views/list_view.dart b/lib/folder_views/list_view.dart new file mode 100644 index 00000000..486bfbab --- /dev/null +++ b/lib/folder_views/list_view.dart @@ -0,0 +1,151 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:gitjournal/core/note.dart'; +import 'package:gitjournal/core/notes_folder.dart'; +import 'package:gitjournal/state_container.dart'; +import 'package:gitjournal/utils.dart'; +import 'package:gitjournal/widgets/icon_dismissable.dart'; + +typedef void NoteSelectedFunction(Note note); +typedef Widget NoteTileBuilder(BuildContext context, Note note); + +class FolderListView extends StatefulWidget { + final NoteTileBuilder noteTileBuilder; + final NoteSelectedFunction noteSelectedFunction; + final NotesFolderReadOnly folder; + final String emptyText; + + FolderListView({ + @required this.folder, + @required this.noteTileBuilder, + @required this.noteSelectedFunction, + @required this.emptyText, + }); + + @override + _FolderListViewState createState() => _FolderListViewState(); +} + +class _FolderListViewState extends State { + var _listKey = GlobalKey(); + var deletedViaDismissed = []; + + @override + void initState() { + super.initState(); + + widget.folder.addNoteAddedListener(_noteAdded); + widget.folder.addNoteRemovedListener(_noteRemoved); + widget.folder.addListener(_folderChanged); + } + + @override + void dispose() { + widget.folder.removeNoteAddedListener(_noteAdded); + widget.folder.removeNoteRemovedListener(_noteRemoved); + widget.folder.removeListener(_folderChanged); + + super.dispose(); + } + + void _noteAdded(int index, Note _) { + if (_listKey.currentState == null) { + return; + } + _listKey.currentState.insertItem(index); + } + + void _noteRemoved(int index, Note note) { + if (_listKey.currentState == null) { + return; + } + _listKey.currentState.removeItem(index, (context, animation) { + var i = deletedViaDismissed.indexWhere((path) => path == note.filePath); + if (i == -1) { + return _buildNote(context, note, animation); + } else { + deletedViaDismissed.removeAt(i); + return Container(); + } + }); + } + + void _folderChanged() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + if (widget.folder.notes.isEmpty) { + return Center( + child: Text( + widget.emptyText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 28.0, + fontWeight: FontWeight.w300, + color: Colors.grey[350], + ), + ), + ); + } + + return AnimatedList( + key: _listKey, + itemBuilder: _buildItem, + initialItemCount: widget.folder.notes.length, + ); + } + + Widget _buildItem(BuildContext context, int i, Animation animation) { + // vHanda FIXME: Why does this method get called with i >= length ? + if (i >= widget.folder.notes.length) { + return Container(); + } + + var note = widget.folder.notes[i]; + return _buildNote(context, note, animation); + } + + Widget _buildNote( + BuildContext context, + Note note, + Animation animation, + ) { + var viewItem = IconDismissable( + key: ValueKey("FolderListView_" + note.filePath), + child: Hero( + tag: note.filePath, + child: widget.noteTileBuilder(context, note), + flightShuttleBuilder: (BuildContext flightContext, + Animation animation, + HeroFlightDirection flightDirection, + BuildContext fromHeroContext, + BuildContext toHeroContext) => + Material(child: toHeroContext.widget), + ), + backgroundColor: Colors.red[800], + iconData: Icons.delete, + onDismissed: (direction) { + deletedViaDismissed.add(note.filePath); + + var stateContainer = + Provider.of(context, listen: false); + stateContainer.removeNote(note); + + var snackBar = buildUndoDeleteSnackbar(context, note); + Scaffold.of(context).showSnackBar(snackBar); + }, + ); + + return SizeTransition( + key: ValueKey("FolderListView_tr_" + note.filePath), + axis: Axis.vertical, + sizeFactor: animation, + child: viewItem, + ); + } +} diff --git a/lib/folder_views/standard_view.dart b/lib/folder_views/standard_view.dart index 88ea662d..092020b6 100644 --- a/lib/folder_views/standard_view.dart +++ b/lib/folder_views/standard_view.dart @@ -1,20 +1,15 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:gitjournal/folder_views/list_view.dart'; import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; import 'package:gitjournal/core/sorting_mode.dart'; import 'package:gitjournal/settings.dart'; import 'package:gitjournal/core/note.dart'; import 'package:gitjournal/core/notes_folder.dart'; -import 'package:gitjournal/state_container.dart'; -import 'package:gitjournal/utils.dart'; -import 'package:gitjournal/widgets/icon_dismissable.dart'; -typedef void NoteSelectedFunction(Note note); - -class StandardView extends StatefulWidget { +class StandardView extends StatelessWidget { final NoteSelectedFunction noteSelectedFunction; final NotesFolderReadOnly folder; final String emptyText; @@ -26,123 +21,13 @@ class StandardView extends StatefulWidget { }); @override - _StandardViewState createState() => _StandardViewState(); -} - -class _StandardViewState extends State { - var _listKey = GlobalKey(); - var deletedViaDismissed = []; - - @override - void initState() { - super.initState(); - - widget.folder.addNoteAddedListener(_noteAdded); - widget.folder.addNoteRemovedListener(_noteRemoved); - widget.folder.addListener(_folderChanged); - } - - @override - void dispose() { - widget.folder.removeNoteAddedListener(_noteAdded); - widget.folder.removeNoteRemovedListener(_noteRemoved); - widget.folder.removeListener(_folderChanged); - - super.dispose(); - } - - void _noteAdded(int index, Note _) { - if (_listKey.currentState == null) { - return; - } - _listKey.currentState.insertItem(index); - } - - void _noteRemoved(int index, Note note) { - if (_listKey.currentState == null) { - return; - } - _listKey.currentState.removeItem(index, (context, animation) { - var i = deletedViaDismissed.indexWhere((path) => path == note.filePath); - if (i == -1) { - return _buildNote(context, note, animation); - } else { - deletedViaDismissed.removeAt(i); - return Container(); - } - }); - } - - void _folderChanged() { - setState(() {}); - } - @override Widget build(BuildContext context) { - if (widget.folder.notes.isEmpty) { - return Center( - child: Text( - widget.emptyText, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 28.0, - fontWeight: FontWeight.w300, - color: Colors.grey[350], - ), - ), - ); - } - - return AnimatedList( - key: _listKey, - itemBuilder: _buildItem, - initialItemCount: widget.folder.notes.length, - ); - } - - Widget _buildItem(BuildContext context, int i, Animation animation) { - // vHanda FIXME: Why does this method get called with i >= length ? - if (i >= widget.folder.notes.length) { - return Container(); - } - - var note = widget.folder.notes[i]; - return _buildNote(context, note, animation); - } - - Widget _buildNote( - BuildContext context, Note note, Animation animation) { - var viewItem = IconDismissable( - key: ValueKey("StandardView_" + note.filePath), - child: Hero( - tag: note.filePath, - child: _buildRow(context, note), - flightShuttleBuilder: (BuildContext flightContext, - Animation animation, - HeroFlightDirection flightDirection, - BuildContext fromHeroContext, - BuildContext toHeroContext) => - Material(child: toHeroContext.widget), - ), - backgroundColor: Colors.red[800], - iconData: Icons.delete, - onDismissed: (direction) { - deletedViaDismissed.add(note.filePath); - - var stateContainer = - Provider.of(context, listen: false); - stateContainer.removeNote(note); - - var snackBar = buildUndoDeleteSnackbar(context, note); - Scaffold.of(context).showSnackBar(snackBar); - }, - ); - - return SizeTransition( - key: ValueKey("StandardView_tr_" + note.filePath), - axis: Axis.vertical, - sizeFactor: animation, - child: viewItem, + return FolderListView( + folder: folder, + noteSelectedFunction: noteSelectedFunction, + emptyText: emptyText, + noteTileBuilder: _buildRow, ); } @@ -196,7 +81,7 @@ class _StandardViewState extends State { children: children, crossAxisAlignment: CrossAxisAlignment.start, ), - onTap: () => widget.noteSelectedFunction(note), + onTap: () => noteSelectedFunction(note), ); var dc = Theme.of(context).dividerColor;