f9b8831c71
- Add workDate field to ProgrammerTaskWorkLog (defaults to Manila day of createdAt) - Add deltaJsonToPlainText function to quill_field.dart for Delta/plain text conversion - Implement buildDayRows function to generate day sheet rows with proper sorting - Implement DaySheetRow serialization and deserialization - Add comprehensive tests for day rows builder and workDate field - Verify backward compatibility via existing programmer_task_ledger_test Co-Authored-By: claude-flow <ruv@ruv.net>
165 lines
5.2 KiB
Dart
165 lines
5.2 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_quill/flutter_quill.dart' as quill;
|
|
|
|
/// Convert a stored Delta JSON or plain text string to trimmed plain text.
|
|
/// - Delta JSON (starts with '[') is decoded and converted to plain text
|
|
/// - Non-JSON strings are returned trimmed as-is
|
|
/// - null returns empty string
|
|
String deltaJsonToPlainText(String? stored) {
|
|
if (stored == null) return '';
|
|
final trimmed = stored.trim();
|
|
if (trimmed.isEmpty) return '';
|
|
if (trimmed.startsWith('[')) {
|
|
try {
|
|
final decoded = jsonDecode(trimmed);
|
|
if (decoded is List) {
|
|
final doc = quill.Document.fromJson(decoded);
|
|
return doc.toPlainText().trim();
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
/// Parses a stored Delta-JSON string into a [quill.QuillController], or returns
|
|
/// an empty one. Callers own the controller and must dispose it.
|
|
quill.QuillController quillControllerFromDelta(String? deltaJson) {
|
|
if (deltaJson != null && deltaJson.isNotEmpty) {
|
|
try {
|
|
final decoded = jsonDecode(deltaJson);
|
|
if (decoded is List) {
|
|
return quill.QuillController(
|
|
document: quill.Document.fromJson(decoded),
|
|
selection: const TextSelection.collapsed(offset: 0),
|
|
);
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
return quill.QuillController.basic();
|
|
}
|
|
|
|
/// Serializes a controller's document to a Delta-JSON string for storage.
|
|
String quillToDeltaJson(quill.QuillController controller) =>
|
|
jsonEncode(controller.document.toDelta().toJson());
|
|
|
|
/// The controller's plain text (trimmed) — for empty checks and previews.
|
|
String quillPlainText(quill.QuillController controller) =>
|
|
controller.document.toPlainText().trim();
|
|
|
|
/// Bordered rich-text editor with a minimal toolbar (bold/italic/underline +
|
|
/// bullet/numbered lists). The caller owns [controller] and disposes it.
|
|
class QuillFieldEditor extends StatelessWidget {
|
|
const QuillFieldEditor({
|
|
super.key,
|
|
required this.controller,
|
|
this.placeholder,
|
|
this.height = 140,
|
|
});
|
|
|
|
final quill.QuillController controller;
|
|
final String? placeholder;
|
|
final double height;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
return Container(
|
|
height: height,
|
|
clipBehavior: Clip.antiAlias,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: cs.outlineVariant),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
SizedBox(
|
|
height: 40,
|
|
child: quill.QuillSimpleToolbar(
|
|
controller: controller,
|
|
config: const quill.QuillSimpleToolbarConfig(
|
|
multiRowsDisplay: false,
|
|
showAlignmentButtons: false,
|
|
showBackgroundColorButton: false,
|
|
showCenterAlignment: false,
|
|
showColorButton: false,
|
|
showDividers: false,
|
|
showFontFamily: false,
|
|
showFontSize: false,
|
|
showHeaderStyle: false,
|
|
showIndent: false,
|
|
showInlineCode: false,
|
|
showLeftAlignment: false,
|
|
showLink: false,
|
|
showQuote: false,
|
|
showRightAlignment: false,
|
|
showSearchButton: false,
|
|
showCodeBlock: false,
|
|
showDirection: false,
|
|
showJustifyAlignment: false,
|
|
showListCheck: false,
|
|
showSubscript: false,
|
|
showSuperscript: false,
|
|
showStrikeThrough: false,
|
|
showSmallButton: false,
|
|
showClearFormat: false,
|
|
showRedo: false,
|
|
showUndo: false,
|
|
),
|
|
),
|
|
),
|
|
const Divider(height: 1),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
|
child: quill.QuillEditor.basic(
|
|
controller: controller,
|
|
config: quill.QuillEditorConfig(
|
|
placeholder: placeholder,
|
|
expands: true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Renders a stored value read-only. Interprets a Delta-JSON string as rich
|
|
/// text; falls back to a plain [Text] for legacy plain-string values.
|
|
class QuillReadOnly extends StatelessWidget {
|
|
const QuillReadOnly(this.deltaOrText, {super.key, this.style});
|
|
|
|
final String deltaOrText;
|
|
final TextStyle? style;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final trimmed = deltaOrText.trim();
|
|
if (trimmed.startsWith('[')) {
|
|
try {
|
|
final decoded = jsonDecode(trimmed);
|
|
if (decoded is List) {
|
|
final controller = quill.QuillController(
|
|
document: quill.Document.fromJson(decoded),
|
|
selection: const TextSelection.collapsed(offset: 0),
|
|
);
|
|
return IgnorePointer(
|
|
child: quill.QuillEditor.basic(
|
|
controller: controller,
|
|
config: const quill.QuillEditorConfig(
|
|
showCursor: false,
|
|
autoFocus: false,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
return Text(deltaOrText, style: style);
|
|
}
|
|
}
|