diff --git a/lib/models/checklist.dart b/lib/models/checklist.dart index 364717a..d1c51f5 100644 --- a/lib/models/checklist.dart +++ b/lib/models/checklist.dart @@ -46,6 +46,7 @@ class ListItem { final int id; final int listId; final String name; + final String? description; final int? categoryId; final String? quantity; final bool done; @@ -64,6 +65,7 @@ class ListItem { required this.id, required this.listId, required this.name, + this.description, this.categoryId, this.quantity, required this.done, @@ -83,6 +85,7 @@ class ListItem { id: json['id'] as int, listId: json['listId'] as int, name: json['name'] as String, + description: json['description'] as String?, categoryId: json['categoryId'] as int?, quantity: json['quantity'] as String?, done: json['done'] as bool, @@ -102,6 +105,7 @@ class ListItem { 'id': id, 'listId': listId, 'name': name, + 'description': description, 'categoryId': categoryId, 'quantity': quantity, 'done': done, @@ -121,6 +125,7 @@ class ListItem { id: id, listId: listId, name: name, + description: description, categoryId: categoryId, quantity: quantity, done: done ?? this.done, diff --git a/lib/views/checklists/checklist_item_tile.dart b/lib/views/checklists/checklist_item_tile.dart index ca74596..6bd5c1d 100644 --- a/lib/views/checklists/checklist_item_tile.dart +++ b/lib/views/checklists/checklist_item_tile.dart @@ -133,17 +133,6 @@ class ChecklistItemTile extends StatelessWidget { final badges = []; final theme = Theme.of(context); - if (item.quantity != null && item.quantity!.isNotEmpty) { - badges.add( - _Badge( - icon: Icons.close, - label: item.quantity!, - color: theme.colorScheme.surfaceContainerHighest, - textColor: theme.colorScheme.onSurface, - ), - ); - } - if (category != null) { final catColor = _parseColor(category!.color) ?? theme.colorScheme.primary; @@ -157,6 +146,17 @@ class ChecklistItemTile extends StatelessWidget { ); } + if (item.quantity != null && item.quantity!.isNotEmpty) { + badges.add( + _Badge( + icon: Icons.close, + label: item.quantity!, + color: theme.colorScheme.surfaceContainerHighest, + textColor: theme.colorScheme.onSurface, + ), + ); + } + if (item.rrule != null && item.rrule!.isNotEmpty) { badges.add( _Badge( diff --git a/lib/views/checklists/item_detail_view.dart b/lib/views/checklists/item_detail_view.dart index afede9f..fc7088b 100644 --- a/lib/views/checklists/item_detail_view.dart +++ b/lib/views/checklists/item_detail_view.dart @@ -1,5 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import 'package:pantry/i18n.dart'; import 'package:pantry/models/category.dart' as models; import 'package:pantry/models/checklist.dart'; @@ -7,6 +8,7 @@ import 'package:pantry/services/auth_service.dart'; import 'package:pantry/services/checklist_service.dart'; import 'package:pantry/utils/category_icons.dart'; import 'package:pantry/utils/rrule.dart'; +import 'package:pantry/utils/text_direction.dart'; import 'checklists_controller.dart'; import 'item_form_view.dart'; @@ -30,6 +32,8 @@ class ItemDetailView extends StatelessWidget { final hasImage = item.imageFileId != null; final v = m.checklists.viewItem; + final nameDir = detectTextDirection(item.name); + return Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: () { @@ -47,7 +51,10 @@ class ItemDetailView extends StatelessWidget { SliverAppBar( expandedHeight: hasImage ? 280 : 0, pinned: true, - title: Text(item.name), + title: Directionality( + textDirection: nameDir, + child: Text(item.name), + ), flexibleSpace: hasImage ? FlexibleSpaceBar( background: _CoverImage( @@ -62,6 +69,22 @@ class ItemDetailView extends StatelessWidget { padding: const EdgeInsets.all(16), sliver: SliverList.list( children: [ + if (item.description != null && + item.description!.isNotEmpty) ...[ + Directionality( + textDirection: detectTextDirection(item.description), + child: MarkdownBody( + data: item.description!, + shrinkWrap: true, + styleSheet: MarkdownStyleSheet.fromTheme(theme).copyWith( + p: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + ), + const SizedBox(height: 16), + ], if (item.quantity != null && item.quantity!.isNotEmpty) ...[ _DetailRow( label: v.quantity, diff --git a/lib/views/checklists/item_form_view.dart b/lib/views/checklists/item_form_view.dart index b0b9b6c..40d5c96 100644 --- a/lib/views/checklists/item_form_view.dart +++ b/lib/views/checklists/item_form_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:pantry/i18n.dart'; import 'package:pantry/models/category.dart' as models; import 'package:pantry/models/checklist.dart'; +import 'package:pantry/utils/text_direction.dart'; import 'package:pantry/widgets/category_picker.dart'; import 'package:pantry/widgets/recurrence_dialog.dart'; import 'package:pantry/widgets/repeat_button.dart'; @@ -27,6 +28,8 @@ class _ItemFormViewState extends State { String? _rrule; bool _repeatFromCompletion = false; bool _saving = false; + TextDirection _nameDir = TextDirection.ltr; + TextDirection _descriptionDir = TextDirection.ltr; bool get _isEditing => widget.item != null; @@ -39,11 +42,23 @@ class _ItemFormViewState extends State { super.initState(); final item = widget.item; _nameController = TextEditingController(text: item?.name ?? ''); - _descriptionController = TextEditingController(); + _descriptionController = TextEditingController( + text: item?.description ?? '', + ); _quantityController = TextEditingController(text: item?.quantity ?? ''); _selectedCategoryId = item?.categoryId; _rrule = item?.rrule; _repeatFromCompletion = item?.repeatFromCompletion ?? false; + _nameDir = detectTextDirection(item?.name); + _nameController.addListener(() { + final dir = detectTextDirection(_nameController.text); + if (dir != _nameDir) setState(() => _nameDir = dir); + }); + _descriptionDir = detectTextDirection(item?.description); + _descriptionController.addListener(() { + final dir = detectTextDirection(_descriptionController.text); + if (dir != _descriptionDir) setState(() => _descriptionDir = dir); + }); } @override @@ -121,6 +136,7 @@ class _ItemFormViewState extends State { ), autofocus: !_isEditing, textCapitalization: TextCapitalization.sentences, + textDirection: _nameDir, textInputAction: TextInputAction.next, ), const SizedBox(height: 16), @@ -131,6 +147,7 @@ class _ItemFormViewState extends State { border: const OutlineInputBorder(), ), textCapitalization: TextCapitalization.sentences, + textDirection: _descriptionDir, maxLines: 3, minLines: 2, ), diff --git a/test/helpers/test_models.dart b/test/helpers/test_models.dart index 8760172..ae15dde 100644 --- a/test/helpers/test_models.dart +++ b/test/helpers/test_models.dart @@ -153,6 +153,7 @@ ListItem makeListItem({ int id = 1, int listId = 1, String name = 'Milk', + String? description, int? categoryId, String? quantity, bool done = false, @@ -170,6 +171,7 @@ ListItem makeListItem({ id: id, listId: listId, name: name, + description: description, categoryId: categoryId, quantity: quantity, done: done,