import 'dart:convert'; import 'package:meta/meta.dart'; import 'package:gitjournal/core/note.dart'; import 'package:gitjournal/error_reporting.dart'; class ChecklistItem { bool checked; String text; String pre; bool upperCase; int lineNo; ChecklistItem({ @required this.checked, @required this.text, this.pre = '', this.upperCase = false, this.lineNo = -1, }); @override String toString() => '$pre- [$_x] $text'; String get _x => checked ? upperCase ? 'X' : 'x' : ' '; @override bool operator ==(Object other) => identical(this, other) || other is ChecklistItem && runtimeType == other.runtimeType && checked == other.checked && text == other.text && pre == other.pre && upperCase == other.upperCase && lineNo == other.lineNo; @override int get hashCode => text.hashCode ^ pre.hashCode ^ checked.hashCode; } class Checklist { static final _pattern = RegExp( r'^(.*)- \[([ xX])\] ?(.*)$', multiLine: false, ); // FIXME: Added on 2020-05-01: Remove after 3-4 months static final _oldPattern = RegExp( r'^ *\[([ xX])\] +(.*)$', multiLine: false, ); Note _note; List items = []; List _lines; bool endsWithNewLine; Checklist(this._note) { _lines = LineSplitter.split(_note.body).toList(); endsWithNewLine = _note.body.endsWith('\n'); for (var i = 0; i < _lines.length; i++) { var line = _lines[i]; var oldPatMatch = _oldPattern.firstMatch(line); if (oldPatMatch != null) { line = '- ' + line; } var match = _pattern.firstMatch(line); if (match == null) { continue; } var pre = match.group(1); var state = match.group(2); var post = match.group(3); var item = ChecklistItem( pre: pre, checked: state != ' ', upperCase: state == 'X', text: post, lineNo: i, ); items.add(item); } } Note get note { for (var item in items) { _lines[item.lineNo] = item.toString(); } _note.body = _lines.join('\n'); if (endsWithNewLine) { _note.body += '\n'; } return _note; } @override String toString() { return [ '[', items.map((x) => x.toString()).join(', '), ']', ].join(' '); } ChecklistItem buildItem(bool value, String text) { var item = ChecklistItem(checked: value, text: text); return item; } void removeItem(ChecklistItem item) { assert(items.contains(item)); var i = items.indexOf(item); assert(i != -1); if (i == -1) { logException( Exception('Checklist removeItem does not exist'), StackTrace.current, ); return; } removeAt(i); } ChecklistItem removeAt(int index) { assert(index >= 0 && index <= items.length); var item = items[index]; items.removeAt(index); _lines.removeAt(item.lineNo); for (var j = index; j < items.length; j++) { items[j].lineNo -= 1; } return item; } void addItem(ChecklistItem item) { assert(item.lineNo == -1); if (items.isEmpty) { item.lineNo = _lines.length; items.add(item); _lines.add(item.toString()); return; } var prevItem = items.last; item.lineNo = prevItem.lineNo + 1; items.add(item); _lines.insert(item.lineNo, item.toString()); } void insertItem(int index, ChecklistItem item) { assert(index <= items.length); if (index == 0 && items.isEmpty) { addItem(item); return; } if (index == 0) { var nextItem = items[0]; item.lineNo = nextItem.lineNo; _lines.insert(item.lineNo, item.toString()); for (var item in items) { item.lineNo++; } items.insert(0, item); return; } if (index == items.length) { var prevItem = items.last; item.lineNo = prevItem.lineNo + 1; items.add(item); _lines.insert(item.lineNo, item.toString()); return; } var prevItem = items[index]; item.lineNo = prevItem.lineNo; _lines.insert(item.lineNo, item.toString()); for (var i = index; i < items.length; i++) { items[i].lineNo++; } items.insert(index, item); } }