258 lines
7.7 KiB
Dart
258 lines
7.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
|
|
|
import 'main.dart';
|
|
import 'store.dart';
|
|
import 'db.dart';
|
|
import 'generated/l10n.dart';
|
|
|
|
class NoteWidget extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Observer(builder: (context) {
|
|
switch (settings.noteView) {
|
|
case ViewStyle.Table: return NoteTable();
|
|
case ViewStyle.List: return NoteList();
|
|
case ViewStyle.Auto: return OrientationBuilder(builder: (context, orientation) {
|
|
if (orientation == Orientation.portrait) return NoteList();
|
|
else return NoteTable();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class NoteTable extends StatefulWidget {
|
|
NoteTable();
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _NoteTableState();
|
|
}
|
|
|
|
class _NoteTableState extends State<NoteTable> with AutomaticKeepAliveClientMixin {
|
|
@override
|
|
bool get wantKeepAlive => true; //Set to true
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return SingleChildScrollView(
|
|
padding: EdgeInsets.all(8),
|
|
scrollDirection: Axis.vertical,
|
|
child: Observer(builder: (context) {
|
|
// ignore: unused_local_variable
|
|
final _unused = active.sortedNotes;
|
|
return PaginatedDataTable(
|
|
columns: [
|
|
DataColumn(
|
|
label: settings.showConfirmations
|
|
? Text(S.of(context).confs)
|
|
: Text(S.of(context).height),
|
|
onSort: (_, __) {
|
|
setState(() {
|
|
settings.toggleShowConfirmations();
|
|
});
|
|
}),
|
|
DataColumn(
|
|
label: Text(S.of(context).datetime +
|
|
active.noteSortConfig.getIndicator("time")),
|
|
onSort: (_, __) {
|
|
setState(() {
|
|
active.sortNotes("time");
|
|
});
|
|
},
|
|
),
|
|
DataColumn(
|
|
numeric: true,
|
|
label: Text(S.of(context).amount +
|
|
active.noteSortConfig.getIndicator("amount")),
|
|
onSort: (_, __) {
|
|
setState(() {
|
|
active.sortNotes("amount");
|
|
});
|
|
},
|
|
),
|
|
],
|
|
header: Text(S.of(context).selectNotesToExcludeFromPayments,
|
|
style: Theme.of(context).textTheme.bodyMedium),
|
|
actions: [
|
|
IconButton(onPressed: _selectInverse, icon: Icon(MdiIcons.selectInverse)),
|
|
],
|
|
columnSpacing: 16,
|
|
showCheckboxColumn: false,
|
|
availableRowsPerPage: [5, 10, 25, 100],
|
|
onRowsPerPageChanged: (int? value) {
|
|
settings.setRowsPerPage(value ?? 25);
|
|
},
|
|
showFirstLastButtons: true,
|
|
rowsPerPage: settings.rowsPerPage,
|
|
source: NotesDataSource(context, _onRowSelected),
|
|
);
|
|
}));
|
|
}
|
|
|
|
_onRowSelected(Note note) {
|
|
active.excludeNote(note);
|
|
}
|
|
|
|
_selectInverse() {
|
|
active.invertExcludedNotes();
|
|
}
|
|
}
|
|
|
|
class NotesDataSource extends DataTableSource {
|
|
final BuildContext context;
|
|
final Function(Note) onRowSelected;
|
|
|
|
NotesDataSource(this.context, this.onRowSelected);
|
|
|
|
@override
|
|
DataRow getRow(int index) {
|
|
final note = active.sortedNotes[index];
|
|
final theme = Theme.of(context);
|
|
final confsOrHeight = settings.showConfirmations
|
|
? syncStatus.latestHeight - note.height + 1
|
|
: note.height;
|
|
|
|
var style = theme.textTheme.bodyMedium!;
|
|
if (!confirmed(note.height))
|
|
style = style.copyWith(color: style.color!.withOpacity(0.5));
|
|
|
|
if (note.spent)
|
|
style = style.merge(TextStyle(decoration: TextDecoration.lineThrough));
|
|
if (note.orchard)
|
|
style = style.merge(TextStyle(color: theme.primaryColor));
|
|
|
|
final amountStyle = weightFromAmount(style, note.value);
|
|
|
|
return DataRow.byIndex(
|
|
index: index,
|
|
selected: note.excluded,
|
|
color: MaterialStateColor.resolveWith((states) =>
|
|
states.contains(MaterialState.selected)
|
|
? theme.primaryColor.withOpacity(0.5)
|
|
: theme.colorScheme.background),
|
|
cells: [
|
|
DataCell(Text("$confsOrHeight", style: style)),
|
|
DataCell(Text("${noteDateFormat.format(note.timestamp)}", style: style)),
|
|
DataCell(Text(decimalFormat(note.value, 8), style: amountStyle)),
|
|
],
|
|
onSelectChanged: (selected) => _noteSelected(note, selected),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool get isRowCountApproximate => false;
|
|
|
|
@override
|
|
int get rowCount => active.notes.length;
|
|
|
|
@override
|
|
int get selectedRowCount => 0;
|
|
|
|
void _noteSelected(Note note, bool? selected) {
|
|
note.excluded = !note.excluded;
|
|
notifyListeners();
|
|
onRowSelected(note);
|
|
}
|
|
}
|
|
|
|
class NoteList extends StatefulWidget {
|
|
@override
|
|
NoteListState createState() => NoteListState();
|
|
}
|
|
|
|
class NoteListState extends State<NoteList> with AutomaticKeepAliveClientMixin {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
final s = S.of(context);
|
|
return Observer(builder: (context) {
|
|
final notes = active.sortedNotes;
|
|
return Padding(padding: EdgeInsets.all(16), child: CustomScrollView(
|
|
key: UniqueKey(),
|
|
slivers: [
|
|
SliverToBoxAdapter(child: ListTile(
|
|
onTap: _onInvert,
|
|
title: Text(s.selectNotesToExcludeFromPayments),
|
|
trailing: Icon(Icons.select_all),
|
|
)),
|
|
SliverFixedExtentList(
|
|
itemExtent: 50,
|
|
delegate: SliverChildBuilderDelegate((context, index) {
|
|
return NoteItem(notes[index], index);
|
|
}, childCount: notes.length))
|
|
],
|
|
));
|
|
});
|
|
}
|
|
|
|
_onInvert() {
|
|
active.invertExcludedNotes();
|
|
}
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
}
|
|
|
|
class NoteItem extends StatefulWidget {
|
|
final Note note;
|
|
final int index;
|
|
NoteItem(this.note, this.index);
|
|
|
|
@override
|
|
NoteItemState createState() => NoteItemState();
|
|
}
|
|
|
|
class NoteItemState extends State<NoteItem> {
|
|
var excluded = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
excluded = widget.note.excluded;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final note = widget.note;
|
|
final timestamp = humanizeDateTime(note.timestamp);
|
|
final confirmations = syncStatus.latestHeight - note.height + 1;
|
|
var style = theme.textTheme.titleLarge!;
|
|
if (!confirmed(note.height))
|
|
style = style.merge(TextStyle(color: style.color!.withOpacity(0.5)));
|
|
if (note.spent)
|
|
style = style.merge(TextStyle(decoration: TextDecoration.lineThrough));
|
|
if (note.orchard)
|
|
style = style.merge(TextStyle(color: theme.primaryColor));
|
|
|
|
final amountStyle = weightFromAmount(style, note.value);
|
|
|
|
return GestureDetector(onTap: _onSelected, behavior: HitTestBehavior.opaque, child:
|
|
ColoredBox(color: excluded ? theme.primaryColor.withOpacity(0.5) : theme.colorScheme.background, child:
|
|
Padding(padding: EdgeInsets.all(8), child:
|
|
Row(children: [
|
|
Column(children: [Text("${note.height}", style: theme.textTheme.bodySmall),
|
|
Text("$confirmations", style: theme.textTheme.bodyMedium),
|
|
]),
|
|
Expanded(child: Center(child: Text("${note.value}", style: amountStyle))),
|
|
Text("$timestamp"),
|
|
]))));
|
|
}
|
|
|
|
_onSelected() {
|
|
setState(() {
|
|
excluded = !excluded;
|
|
widget.note.excluded = excluded;
|
|
active.excludeNote(widget.note);
|
|
});
|
|
}
|
|
}
|
|
|
|
bool confirmed(int height) {
|
|
return syncStatus.latestHeight - height >= settings.anchorOffset;
|
|
}
|
|
|