Add functionality to edit sheets with save and restore

This commit is contained in:
2025-11-15 22:26:33 +01:00
parent 31ecd3c820
commit 3edc3229e9
5 changed files with 202 additions and 3 deletions

View 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"),
),
],
),
);
}
}

View File

@@ -64,6 +64,11 @@ class _MyHomePageState extends State<MyHomePage> with FullScreenListener {
log.info("${sheets.length} sheets fetched");
final sheetsSorted = await sortSheetsByAccessTime(sheets);
log.info("${sheetsSorted.length} sheets sorted");
final changeQueue = await _storageHelper.readChangeQueue();
changeQueue.applyToSheets(sheetsSorted);
log.info("${changeQueue.length()} changes applied");
return sheetsSorted;
}

View File

@@ -2,13 +2,15 @@ import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:sheetless/edit_bottom_sheet.dart';
import 'package:sheetless/storage_helper.dart';
import 'package:sheetless/upload_queue.dart';
class Sheet {
final String uuid;
final String name;
final String composerUuid;
final String composerName;
String name;
String composerUuid;
String composerName;
Sheet({
required this.uuid,
@@ -92,6 +94,40 @@ class _SheetsWidgetState extends State<SheetsWidget> {
_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
Widget build(BuildContext context) {
return Column(
@@ -134,6 +170,7 @@ class _SheetsWidgetState extends State<SheetsWidget> {
widget.sheets.remove(sheet);
widget.sheets.insert(0, sheet);
}),
onLongPress: () => _openEditSheet(context, sheet),
);
},
),

View File

@@ -1,5 +1,6 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:sheetless/upload_queue.dart';
enum SecureStorageKey { url, jwt, email }
@@ -18,6 +19,7 @@ class Config {
class StorageHelper {
final sheetAccessTimesBox = "sheetAccessTimes";
final configBox = "config";
final changeQueueBox = "changeQueue";
late FlutterSecureStorage secureStorage;
@@ -60,4 +62,23 @@ class StorageHelper {
final box = await Hive.openBox(sheetAccessTimesBox);
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
View 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();
}
}
}
}