mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-09-13 06:33:00 +08:00

Now we don't try to be smart and guess the sorting. The problem was what to do when we don't have the modified/created value. In those cases we were trying to use the fileLastModifed, but that could get quite tricky and it wasn't obvious to the user what was going on. From now on - if it doesn't have a modified field, it just goes to the end.
218 lines
6.0 KiB
Dart
218 lines
6.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:gitjournal/core/sorting_mode.dart';
|
|
import 'package:gitjournal/settings.dart';
|
|
import 'package:intl/intl.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';
|
|
import 'package:provider/provider.dart';
|
|
|
|
typedef void NoteSelectedFunction(Note note);
|
|
|
|
class JournalList extends StatefulWidget {
|
|
final NoteSelectedFunction noteSelectedFunction;
|
|
final NotesFolderReadOnly folder;
|
|
final String emptyText;
|
|
|
|
JournalList({
|
|
@required this.folder,
|
|
@required this.noteSelectedFunction,
|
|
@required this.emptyText,
|
|
});
|
|
|
|
@override
|
|
_JournalListState createState() => _JournalListState();
|
|
}
|
|
|
|
class _JournalListState extends State<JournalList> {
|
|
var _listKey = GlobalKey<AnimatedListState>();
|
|
var deletedViaDismissed = <String>[];
|
|
|
|
@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<double> 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<double> animation) {
|
|
var viewItem = IconDismissable(
|
|
key: ValueKey("JournalList_" + note.filePath),
|
|
child: Hero(
|
|
tag: note.filePath,
|
|
child: _buildRow(context, note),
|
|
flightShuttleBuilder: (BuildContext flightContext,
|
|
Animation<double> 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<StateContainer>(context, listen: false);
|
|
stateContainer.removeNote(note);
|
|
|
|
var snackBar = buildUndoDeleteSnackbar(context, note);
|
|
Scaffold.of(context).showSnackBar(snackBar);
|
|
},
|
|
);
|
|
|
|
return SizeTransition(
|
|
key: ValueKey("JournalList_tr_" + note.filePath),
|
|
axis: Axis.vertical,
|
|
sizeFactor: animation,
|
|
child: viewItem,
|
|
);
|
|
}
|
|
|
|
Widget _buildRow(BuildContext context, Note note) {
|
|
var textTheme = Theme.of(context).textTheme;
|
|
var title = note.canHaveMetadata ? note.title : note.fileName;
|
|
Widget titleWidget = Text(title, style: textTheme.title);
|
|
if (title.isEmpty) {
|
|
DateTime date;
|
|
if (Settings.instance.sortingMode == SortingMode.Modified) {
|
|
date = note.modified;
|
|
} else if (Settings.instance.sortingMode == SortingMode.Created) {
|
|
date = note.created;
|
|
}
|
|
if (date != null) {
|
|
var formatter = DateFormat('dd MMM, yyyy ');
|
|
var dateStr = formatter.format(date);
|
|
|
|
var timeFormatter = DateFormat('Hm');
|
|
var time = timeFormatter.format(date);
|
|
|
|
var timeColor = textTheme.body1.color.withAlpha(100);
|
|
|
|
titleWidget = Row(
|
|
children: <Widget>[
|
|
Text(dateStr, style: textTheme.title),
|
|
Text(time, style: textTheme.body1.copyWith(color: timeColor)),
|
|
],
|
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
textBaseline: TextBaseline.alphabetic,
|
|
);
|
|
} else {
|
|
titleWidget = Text(note.fileName, style: textTheme.title);
|
|
}
|
|
}
|
|
|
|
var children = <Widget>[
|
|
const SizedBox(height: 8.0),
|
|
Text(
|
|
note.summary,
|
|
maxLines: 3,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: textTheme.body1,
|
|
),
|
|
];
|
|
|
|
var tile = ListTile(
|
|
isThreeLine: true,
|
|
title: titleWidget,
|
|
subtitle: Column(
|
|
children: children,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
),
|
|
onTap: () => widget.noteSelectedFunction(note),
|
|
);
|
|
|
|
var dc = Theme.of(context).dividerColor;
|
|
var divider = Container(
|
|
height: 1.0,
|
|
child: Divider(color: dc.withOpacity(dc.opacity / 3)),
|
|
);
|
|
|
|
return Column(
|
|
children: <Widget>[
|
|
divider,
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 16.0, bottom: 16.0),
|
|
child: tile,
|
|
),
|
|
divider,
|
|
],
|
|
);
|
|
}
|
|
}
|