mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-08-24 17:31:06 +08:00

Instead of the legacy Provider.of. Less scope of bugs this way and the code is so much nicer to read.
187 lines
5.0 KiB
Dart
187 lines
5.0 KiB
Dart
/*
|
|
* SPDX-FileCopyrightText: 2019-2021 Vishesh Handa <me@vhanda.in>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:gitjournal/core/folder/notes_folder.dart';
|
|
import 'package:gitjournal/core/note.dart';
|
|
import 'package:gitjournal/core/notes/note.dart';
|
|
import 'package:gitjournal/repository.dart';
|
|
import 'package:gitjournal/settings/settings.dart';
|
|
import 'package:gitjournal/utils/utils.dart';
|
|
import 'package:gitjournal/widgets/icon_dismissable.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'empty_text_sliver.dart';
|
|
|
|
typedef NoteTileBuilder = Widget Function(
|
|
BuildContext context,
|
|
Note note,
|
|
bool isSelected,
|
|
);
|
|
|
|
class FolderListView extends StatefulWidget {
|
|
final NoteTileBuilder noteTileBuilder;
|
|
final NoteBoolPropertyFunction isNoteSelected;
|
|
final NotesFolder folder;
|
|
final String? emptyText;
|
|
final String searchTerm;
|
|
|
|
const FolderListView({
|
|
required this.folder,
|
|
required this.noteTileBuilder,
|
|
required this.emptyText,
|
|
required this.isNoteSelected,
|
|
required this.searchTerm,
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
_FolderListViewState createState() => _FolderListViewState();
|
|
}
|
|
|
|
class _FolderListViewState extends State<FolderListView> {
|
|
final _listKey = GlobalKey<SliverAnimatedListState>();
|
|
final _deletedViaDismissed = <String>[];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_addListeners(widget.folder, this);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_removeListeners(widget.folder, this);
|
|
super.dispose();
|
|
}
|
|
|
|
static void _addListeners(NotesFolder folder, _FolderListViewState st) {
|
|
folder.addNoteAddedListener(st._noteAdded);
|
|
folder.addNoteRemovedListener(st._noteRemoved);
|
|
folder.addListener(st._folderChanged);
|
|
}
|
|
|
|
static void _removeListeners(NotesFolder folder, _FolderListViewState st) {
|
|
folder.removeNoteAddedListener(st._noteAdded);
|
|
folder.removeNoteRemovedListener(st._noteRemoved);
|
|
folder.removeListener(st._folderChanged);
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(FolderListView oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
if (oldWidget.folder != widget.folder) {
|
|
_removeListeners(oldWidget.folder, this);
|
|
_addListeners(widget.folder, this);
|
|
}
|
|
}
|
|
|
|
void _noteAdded(int index, Note _) {
|
|
assert(index != -1);
|
|
if (_listKey.currentState == null) {
|
|
return;
|
|
}
|
|
_listKey.currentState!.insertItem(index);
|
|
}
|
|
|
|
void _noteRemoved(int index, Note note) {
|
|
assert(index != -1);
|
|
if (_listKey.currentState == null) {
|
|
return;
|
|
}
|
|
_listKey.currentState!.removeItem(index, (context, animation) {
|
|
var i = _deletedViaDismissed.indexWhere((path) => path == note.filePath);
|
|
if (i == -1) {
|
|
return _buildNote(note, widget.isNoteSelected(note), animation);
|
|
} else {
|
|
_deletedViaDismissed.removeAt(i);
|
|
return Container();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _folderChanged() {
|
|
setState(() {});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (widget.folder.isEmpty) {
|
|
if (widget.emptyText != null) {
|
|
return EmptyTextSliver(emptyText: widget.emptyText!);
|
|
} else {
|
|
return const SliverToBoxAdapter(child: SizedBox());
|
|
}
|
|
}
|
|
|
|
return SliverPadding(
|
|
padding: const EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 12.0 + 48.0),
|
|
sliver: SliverAnimatedList(
|
|
key: _listKey,
|
|
itemBuilder: _buildItem,
|
|
initialItemCount: widget.folder.notes.length,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildItem(BuildContext context, int i, Animation<double> animation) {
|
|
if (i >= widget.folder.notes.length) {
|
|
return Container();
|
|
}
|
|
|
|
var note = widget.folder.notes[i];
|
|
return _buildNote(note, widget.isNoteSelected(note), animation);
|
|
}
|
|
|
|
Widget _buildNote(
|
|
Note note,
|
|
bool selected,
|
|
Animation<double> animation,
|
|
) {
|
|
var settings = context.watch<Settings>();
|
|
Widget viewItem = Hero(
|
|
tag: note.filePath,
|
|
child: widget.noteTileBuilder(context, note, selected),
|
|
flightShuttleBuilder: (BuildContext flightContext,
|
|
Animation<double> animation,
|
|
HeroFlightDirection flightDirection,
|
|
BuildContext fromHeroContext,
|
|
BuildContext toHeroContext) =>
|
|
Material(child: toHeroContext.widget),
|
|
);
|
|
|
|
assert(note.file.oid.isNotEmpty);
|
|
|
|
if (settings.swipeToDelete) {
|
|
viewItem = IconDismissable(
|
|
key: ValueKey("FolderListView_${note.filePath}"),
|
|
backgroundColor: Colors.red[800]!,
|
|
iconData: Icons.delete,
|
|
onDismissed: (direction) {
|
|
_deletedViaDismissed.add(note.filePath);
|
|
|
|
var repo = context.read<GitJournalRepo>();
|
|
repo.removeNote(note);
|
|
|
|
var snackBar = buildUndoDeleteSnackbar(context, repo, note);
|
|
ScaffoldMessenger.of(context)
|
|
..removeCurrentSnackBar()
|
|
..showSnackBar(snackBar);
|
|
},
|
|
child: viewItem,
|
|
);
|
|
}
|
|
|
|
return SizeTransition(
|
|
key: ValueKey("FolderListView_tr_${note.filePath}"),
|
|
axis: Axis.vertical,
|
|
sizeFactor: animation,
|
|
child: viewItem,
|
|
);
|
|
}
|
|
}
|