Add functionality to edit sheets with save and restore
This commit is contained in:
70
lib/edit_bottom_sheet.dart
Normal file
70
lib/edit_bottom_sheet.dart
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sheetless/sheet.dart';
|
||||||
|
|
||||||
|
typedef SheetEditedCallback = void Function(String newName, String newComposer);
|
||||||
|
|
||||||
|
class EditItemBottomSheet extends StatefulWidget {
|
||||||
|
final Sheet sheet;
|
||||||
|
final SheetEditedCallback onSheetEdited;
|
||||||
|
|
||||||
|
const EditItemBottomSheet({
|
||||||
|
super.key,
|
||||||
|
required this.sheet,
|
||||||
|
required this.onSheetEdited,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EditItemBottomSheet> createState() => _EditItemBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditItemBottomSheetState extends State<EditItemBottomSheet> {
|
||||||
|
late TextEditingController sheetNameController;
|
||||||
|
late TextEditingController composerNameController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
sheetNameController = TextEditingController(text: widget.sheet.name);
|
||||||
|
composerNameController = TextEditingController(
|
||||||
|
text: widget.sheet.composerName,
|
||||||
|
);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
16,
|
||||||
|
MediaQuery.of(context).viewInsets.bottom + 16,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
controller: sheetNameController,
|
||||||
|
decoration: InputDecoration(labelText: "Sheet"),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: composerNameController,
|
||||||
|
decoration: InputDecoration(labelText: "Composer"),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: check text fields are not empty
|
||||||
|
// TODO: save on pressing enter
|
||||||
|
widget.onSheetEdited(
|
||||||
|
sheetNameController.text,
|
||||||
|
composerNameController.text,
|
||||||
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: Text("Save"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,11 @@ class _MyHomePageState extends State<MyHomePage> with FullScreenListener {
|
|||||||
log.info("${sheets.length} sheets fetched");
|
log.info("${sheets.length} sheets fetched");
|
||||||
final sheetsSorted = await sortSheetsByAccessTime(sheets);
|
final sheetsSorted = await sortSheetsByAccessTime(sheets);
|
||||||
log.info("${sheetsSorted.length} sheets sorted");
|
log.info("${sheetsSorted.length} sheets sorted");
|
||||||
|
|
||||||
|
final changeQueue = await _storageHelper.readChangeQueue();
|
||||||
|
changeQueue.applyToSheets(sheetsSorted);
|
||||||
|
log.info("${changeQueue.length()} changes applied");
|
||||||
|
|
||||||
return sheetsSorted;
|
return sheetsSorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import 'dart:async';
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sheetless/edit_bottom_sheet.dart';
|
||||||
import 'package:sheetless/storage_helper.dart';
|
import 'package:sheetless/storage_helper.dart';
|
||||||
|
import 'package:sheetless/upload_queue.dart';
|
||||||
|
|
||||||
class Sheet {
|
class Sheet {
|
||||||
final String uuid;
|
final String uuid;
|
||||||
final String name;
|
String name;
|
||||||
final String composerUuid;
|
String composerUuid;
|
||||||
final String composerName;
|
String composerName;
|
||||||
|
|
||||||
Sheet({
|
Sheet({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
@@ -92,6 +94,40 @@ class _SheetsWidgetState extends State<SheetsWidget> {
|
|||||||
_searchController.clear();
|
_searchController.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _openEditSheet(BuildContext context, Sheet sheet) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (_) => EditItemBottomSheet(
|
||||||
|
sheet: sheet,
|
||||||
|
onSheetEdited: (String newName, String newComposer) {
|
||||||
|
if (newName != sheet.name) {
|
||||||
|
storageHelper.writeChange(
|
||||||
|
Change(
|
||||||
|
type: ChangeType.sheetNameChange,
|
||||||
|
sheetUuid: sheet.uuid,
|
||||||
|
value: newName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (newComposer != sheet.composerName) {
|
||||||
|
storageHelper.writeChange(
|
||||||
|
Change(
|
||||||
|
type: ChangeType.composerNameChange,
|
||||||
|
sheetUuid: sheet.uuid,
|
||||||
|
value: newComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
sheet.name = newName;
|
||||||
|
sheet.composerName = newComposer;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
@@ -134,6 +170,7 @@ class _SheetsWidgetState extends State<SheetsWidget> {
|
|||||||
widget.sheets.remove(sheet);
|
widget.sheets.remove(sheet);
|
||||||
widget.sheets.insert(0, sheet);
|
widget.sheets.insert(0, sheet);
|
||||||
}),
|
}),
|
||||||
|
onLongPress: () => _openEditSheet(context, sheet),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:sheetless/upload_queue.dart';
|
||||||
|
|
||||||
enum SecureStorageKey { url, jwt, email }
|
enum SecureStorageKey { url, jwt, email }
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ class Config {
|
|||||||
class StorageHelper {
|
class StorageHelper {
|
||||||
final sheetAccessTimesBox = "sheetAccessTimes";
|
final sheetAccessTimesBox = "sheetAccessTimes";
|
||||||
final configBox = "config";
|
final configBox = "config";
|
||||||
|
final changeQueueBox = "changeQueue";
|
||||||
|
|
||||||
late FlutterSecureStorage secureStorage;
|
late FlutterSecureStorage secureStorage;
|
||||||
|
|
||||||
@@ -60,4 +62,23 @@ class StorageHelper {
|
|||||||
final box = await Hive.openBox(sheetAccessTimesBox);
|
final box = await Hive.openBox(sheetAccessTimesBox);
|
||||||
await box.put(uuid, datetime.toIso8601String());
|
await box.put(uuid, datetime.toIso8601String());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> writeChange(Change change) async {
|
||||||
|
final box = await Hive.openBox(changeQueueBox);
|
||||||
|
box.add(change.toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ChangeQueue> readChangeQueue() async {
|
||||||
|
final box = await Hive.openBox(changeQueueBox);
|
||||||
|
ChangeQueue queue = ChangeQueue();
|
||||||
|
for (Map<dynamic, dynamic> map in box.values) {
|
||||||
|
queue.addChange(Change.fromMap(map));
|
||||||
|
}
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteOldestChange(Change change) async {
|
||||||
|
final box = await Hive.openBox(changeQueueBox);
|
||||||
|
box.deleteAt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
lib/upload_queue.dart
Normal file
66
lib/upload_queue.dart
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:sheetless/sheet.dart';
|
||||||
|
|
||||||
|
enum ChangeType {
|
||||||
|
sheetNameChange,
|
||||||
|
composerNameChange,
|
||||||
|
addTagChange,
|
||||||
|
removeTagChange,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Change {
|
||||||
|
final ChangeType type;
|
||||||
|
final String sheetUuid;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
Change({required this.type, required this.sheetUuid, required this.value});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() => {
|
||||||
|
"type": type.index,
|
||||||
|
"sheetUuid": sheetUuid,
|
||||||
|
"value": value,
|
||||||
|
};
|
||||||
|
|
||||||
|
factory Change.fromMap(Map<dynamic, dynamic> map) {
|
||||||
|
return Change(
|
||||||
|
type: ChangeType
|
||||||
|
.values[map["type"]], // TODO: this will create problems if new enums are added
|
||||||
|
sheetUuid: map["sheetUuid"],
|
||||||
|
value: map["value"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangeQueue {
|
||||||
|
// Queue with oldest change first
|
||||||
|
final Queue<Change> queue = Queue();
|
||||||
|
|
||||||
|
ChangeQueue() {}
|
||||||
|
|
||||||
|
void addChange(Change change) {
|
||||||
|
queue.addLast(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
int length() {
|
||||||
|
return queue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyToSheets(List<Sheet> sheets) {
|
||||||
|
for (Change change in queue) {
|
||||||
|
var sheet = sheets.where((sheet) => sheet.uuid == change.sheetUuid).first;
|
||||||
|
switch (change.type) {
|
||||||
|
case ChangeType.sheetNameChange:
|
||||||
|
sheet.name = change.value;
|
||||||
|
case ChangeType.composerNameChange:
|
||||||
|
sheet.composerName = change.value;
|
||||||
|
case ChangeType.addTagChange:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
throw UnimplementedError();
|
||||||
|
case ChangeType.removeTagChange:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user