diff --git a/apps/librarian/lib/core/api/models/issue_model.dart b/apps/librarian/lib/core/api/models/issue_model.dart index 01a08d2..c407747 100644 --- a/apps/librarian/lib/core/api/models/issue_model.dart +++ b/apps/librarian/lib/core/api/models/issue_model.dart @@ -1,13 +1,17 @@ class Issue { final IssueType type; final String title; + final String okTitle; final String? explanation; + final String? okExplanation; final String? instructions; final String? graphicUrl; const Issue({ required this.title, + required this.okTitle, this.explanation, + this.okExplanation, this.instructions, this.graphicUrl, required this.type, @@ -18,34 +22,49 @@ class Issue { } static final _reasonMap = { - 'duesNotPaid': const Issue( - title: "Dues Not Paid", - explanation: "Annual dues must be paid before borrowing.", - instructions: - "The borrower can pay their dues from the library's website or by scanning the QR code.", - graphicUrl: "qr_givebutter.png", - type: IssueType.duesNotPaid, - ), - 'overdueLoan': const Issue( - title: "Overdue Loan", - explanation: - "All overdue items must be returned before they can borrow again.", - type: IssueType.overdueLoan, - ), - 'suspended': const Issue( - title: "Suspended", - explanation: "This person has been suspended from borrowing.", - type: IssueType.suspended, - ), - 'needsLiabilityWaiver': const Issue( - title: "Needs Liability Waiver", - explanation: - "This borrower needs to sign our library's liability waiver before they can check anything out.", - type: IssueType.needsLiabilityWaiver, - ), + 'duesNotPaid': duesNotPaidIssue, + 'overdueLoan': overdueLoanIssue, + 'suspended': suspendedIssue, + 'needsLiabilityWaiver': needsLiabilityWaiverIssue, }; } +const duesNotPaidIssue = Issue( + title: "Dues Not Paid", + okTitle: "Dues Paid", + explanation: "Annual dues must be paid before borrowing.", + okExplanation: "Annual dues have been paid.", + instructions: + "The borrower can pay their dues from the library's website or by scanning the QR code.", + graphicUrl: "qr_givebutter.png", + type: IssueType.duesNotPaid, +); + +const overdueLoanIssue = Issue( + title: "Overdue Loan", + okTitle: "No Overdue Loans", + explanation: "All overdue items must be returned before borrowing again.", + okExplanation: "All loans have been returned.", + type: IssueType.overdueLoan, +); + +const needsLiabilityWaiverIssue = Issue( + title: "Needs Liability Waiver", + okTitle: "Liability Waiver Signed", + explanation: + "A liability waiver must be signed before checking anything out.", + okExplanation: "Liability waived!", + type: IssueType.needsLiabilityWaiver, +); + +const suspendedIssue = Issue( + title: "Suspended", + okTitle: "In Good Standing", + explanation: "This person is no longer a member of the library.", + okExplanation: "This member is in good standing.", + type: IssueType.suspended, +); + enum IssueType { duesNotPaid, overdueLoan, diff --git a/apps/librarian/lib/core/data/borrowers_repository.dart b/apps/librarian/lib/core/data/borrowers_repository.dart index 866df52..f24200f 100644 --- a/apps/librarian/lib/core/data/borrowers_repository.dart +++ b/apps/librarian/lib/core/data/borrowers_repository.dart @@ -1,6 +1,6 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/core/api/api.dart'; +import 'package:librarian_app/core/api/api.dart' as api; import 'package:librarian_app/core/api/models/payment_model.dart'; import '../api/models/borrower_model.dart'; @@ -10,7 +10,7 @@ class BorrowersRepository extends Notifier>> { Future> build() async => await getBorrowers(); Future> getBorrowers() async { - final response = await fetchBorrowers(); + final response = await api.fetchBorrowers(); return (response.data as List) .map((json) => BorrowerModel.fromJson(json)) .toList(); @@ -22,13 +22,13 @@ class BorrowersRepository extends Notifier>> { } Future getBorrowerDetails(String id) async { - final response = await fetchBorrower(id); + final response = await api.fetchBorrower(id); return BorrowerModel.fromJson(response.data as Map); } Future updateBorrower(String id, {String? email, String? phone}) async { try { - await updateBorrower(id, email: email, phone: phone); + await api.updateBorrower(id, email: email, phone: phone); ref.invalidateSelf(); return true; @@ -38,7 +38,7 @@ class BorrowersRepository extends Notifier>> { } Future> getPayments(String borrowerId) async { - final response = await fetchPayments(borrowerId: borrowerId); + final response = await api.fetchPayments(borrowerId: borrowerId); return (response.data as List) .map((e) => PaymentModel.fromJson(e)) .toList(); @@ -49,7 +49,7 @@ class BorrowersRepository extends Notifier>> { required double cash, }) async { try { - await recordCashPayment( + await api.recordCashPayment( cash: cash, borrowerId: borrowerId, ); diff --git a/apps/librarian/lib/modules/borrowers/details/borrower_details.dart b/apps/librarian/lib/modules/borrowers/details/borrower_details.dart index 763f4a9..cf9939a 100644 --- a/apps/librarian/lib/modules/borrowers/details/borrower_details.dart +++ b/apps/librarian/lib/modules/borrowers/details/borrower_details.dart @@ -26,7 +26,7 @@ class BorrowerDetails extends ConsumerWidget { final borrower = snapshot.data!; return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ContactCard( name: borrower.name, diff --git a/apps/librarian/lib/modules/borrowers/details/borrower_issues.dart b/apps/librarian/lib/modules/borrowers/details/borrower_issues.dart index 75e066b..35d3cd6 100644 --- a/apps/librarian/lib/modules/borrowers/details/borrower_issues.dart +++ b/apps/librarian/lib/modules/borrowers/details/borrower_issues.dart @@ -19,45 +19,103 @@ class BorrowerIssues extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return ListView.builder( - itemCount: issues.length, - itemBuilder: (context, index) { - final issue = issues[index]; - - return ListTile( - leading: const Icon( - Icons.warning_rounded, - color: Colors.amber, + return ListView( + shrinkWrap: true, + children: [ + _IssueTile( + duesNotPaidIssue, + isOk: !issues.contains(duesNotPaidIssue), + trailing: _PayDuesButton( + borrowerId: borrowerId, + onRecordCashPayment: onRecordCashPayment, ), - title: Text(issue.title), - subtitle: issue.explanation != null ? Text(issue.explanation!) : null, - trailing: issue.type == IssueType.duesNotPaid - ? OutlinedButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return DuesNotPaidDialog( - instructions: issue.instructions!, - imageUrl: issue.graphicUrl, - onConfirmPayment: (cash) async { - final result = await ref - .read(borrowersRepositoryProvider.notifier) - .recordPayment( - borrowerId: borrowerId, cash: cash); - - onRecordCashPayment(result); - }, - ); - }, - ); - }, - child: const Text('Fix'), - ) - : null, + ), + _IssueTile( + overdueLoanIssue, + isOk: !issues.contains(overdueLoanIssue), + ), + _IssueTile( + needsLiabilityWaiverIssue, + isOk: !issues.contains(needsLiabilityWaiverIssue), + ), + _IssueTile( + suspendedIssue, + isOk: !issues.contains(suspendedIssue), + ), + ], + ); + } +} + +class _IssueTile extends StatelessWidget { + const _IssueTile( + this.issue, { + this.isOk = false, + this.trailing, + }); + + final Issue issue; + final bool isOk; + final Widget? trailing; + + @override + Widget build(BuildContext context) { + if (isOk) { + return ListTile( + leading: const Icon( + Icons.check, + color: Colors.green, + ), + title: Text(issue.okTitle), + subtitle: + issue.okExplanation != null ? Text(issue.okExplanation!) : null, + ); + } + + return ListTile( + leading: const Icon( + Icons.warning_rounded, + color: Colors.amber, + ), + title: Text(issue.title), + subtitle: issue.explanation != null ? Text(issue.explanation!) : null, + trailing: trailing, + ); + } +} + +class _PayDuesButton extends ConsumerWidget { + const _PayDuesButton({ + required this.borrowerId, + required this.onRecordCashPayment, + }); + + final String borrowerId; + final void Function(bool success) onRecordCashPayment; + + @override + Widget build(BuildContext context, WidgetRef ref) { + const issue = duesNotPaidIssue; + return OutlinedButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DuesNotPaidDialog( + instructions: issue.instructions!, + imageUrl: issue.graphicUrl, + onConfirmPayment: (cash) async { + final result = await ref + .read(borrowersRepositoryProvider.notifier) + .recordPayment(borrowerId: borrowerId, cash: cash); + + onRecordCashPayment(result); + }, + ); + }, ); }, - shrinkWrap: true, + child: const Text('Pay Dues'), ); } } diff --git a/apps/librarian/lib/modules/borrowers/details/contact_card.dart b/apps/librarian/lib/modules/borrowers/details/contact_card.dart index 7306d23..9e7f61e 100644 --- a/apps/librarian/lib/modules/borrowers/details/contact_card.dart +++ b/apps/librarian/lib/modules/borrowers/details/contact_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/widgets/details_card/card_header.dart'; +import 'package:librarian_app/widgets/details_card/card_body.dart'; import 'package:librarian_app/widgets/details_card/details_card.dart'; import '../providers/edited_borrower_details_providers.dart'; @@ -21,10 +21,7 @@ class ContactCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return DetailsCard( - header: const CardHeader(title: 'Contact Details'), - showDivider: true, - body: Padding( - padding: const EdgeInsets.all(16.0), + body: CardBody( child: Column( children: [ TextField( @@ -34,7 +31,6 @@ class ContactCard extends ConsumerWidget { icon: Icon(Icons.person_rounded), labelText: 'Name', border: OutlineInputBorder(), - constraints: BoxConstraints(maxWidth: 500), ), ), const SizedBox(height: 16), @@ -46,7 +42,6 @@ class ContactCard extends ConsumerWidget { icon: Icon(Icons.email_rounded), labelText: 'Email', border: OutlineInputBorder(), - constraints: BoxConstraints(maxWidth: 500), ), onChanged: (value) { ref.read(emailProvider.notifier).state = value; @@ -61,7 +56,6 @@ class ContactCard extends ConsumerWidget { icon: Icon(Icons.phone_rounded), labelText: 'Phone', border: OutlineInputBorder(), - constraints: BoxConstraints(maxWidth: 500), ), inputFormatters: [ FilteringTextInputFormatter.digitsOnly, diff --git a/apps/librarian/lib/modules/borrowers/details/issues_card.dart b/apps/librarian/lib/modules/borrowers/details/issues_card.dart index fb7a251..036ecc9 100644 --- a/apps/librarian/lib/modules/borrowers/details/issues_card.dart +++ b/apps/librarian/lib/modules/borrowers/details/issues_card.dart @@ -19,21 +19,16 @@ class IssuesCard extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return DetailsCard( header: const CardHeader(title: 'Issues'), - showDivider: issues.isNotEmpty, - body: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: BorrowerIssues( - borrowerId: borrowerId, - issues: issues, - onRecordCashPayment: (success) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text(success ? 'Success!' : 'Failed to record payment'), - ), - ); - }, - ), + body: BorrowerIssues( + borrowerId: borrowerId, + issues: issues, + onRecordCashPayment: (success) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(success ? 'Success!' : 'Failed to record payment'), + ), + ); + }, ), ); } diff --git a/apps/librarian/lib/modules/borrowers/details/payments_card.dart b/apps/librarian/lib/modules/borrowers/details/payments_card.dart index f90abaf..2ed5aaa 100644 --- a/apps/librarian/lib/modules/borrowers/details/payments_card.dart +++ b/apps/librarian/lib/modules/borrowers/details/payments_card.dart @@ -6,6 +6,7 @@ import 'package:librarian_app/modules/borrowers/providers/selected_borrower_prov import 'package:librarian_app/widgets/details_card/card_body.dart'; import 'package:librarian_app/widgets/details_card/card_header.dart'; import 'package:librarian_app/widgets/details_card/details_card.dart'; +import 'package:librarian_app/widgets/hint_text.dart'; class PaymentsCard extends ConsumerWidget { const PaymentsCard({super.key}); @@ -25,20 +26,20 @@ class PaymentsCard extends ConsumerWidget { return DetailsCard( header: const CardHeader(title: 'Payments'), - showDivider: payments.isNotEmpty, - body: Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: ListView.builder( - itemBuilder: (context, i) { - return _PaymentListTile( - cash: payments[i].cash, - date: payments[i].date, - ); - }, - itemCount: payments.length, - shrinkWrap: true, - ), - ), + body: payments.isNotEmpty + ? ListView.separated( + itemBuilder: (context, i) { + return _PaymentListTile( + cash: payments[i].cash, + date: payments[i].date, + ); + }, + itemCount: payments.length, + separatorBuilder: (context, index) => + const Divider(height: 1), + shrinkWrap: true, + ) + : const CardBody(child: HintText('No recorded payments.')), ); }, ); diff --git a/apps/librarian/lib/modules/things/details/categories/categories_card.dart b/apps/librarian/lib/modules/things/details/categories/categories_card.dart index f051683..f4873ba 100644 --- a/apps/librarian/lib/modules/things/details/categories/categories_card.dart +++ b/apps/librarian/lib/modules/things/details/categories/categories_card.dart @@ -5,6 +5,7 @@ import 'package:librarian_app/widgets/details_card/card_header.dart'; import 'package:librarian_app/widgets/details_card/details_card.dart'; import 'package:librarian_app/modules/things/providers/edited_thing_details_providers.dart'; import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; +import 'package:librarian_app/widgets/hint_text.dart'; import '../../providers/thing_details_provider.dart'; @@ -46,7 +47,6 @@ class CategoriesCard extends ConsumerWidget { label: const Text('Add category'), ), ), - showDivider: categories.isNotEmpty, body: categories.isNotEmpty ? CardBody( child: Row( @@ -64,7 +64,10 @@ class CategoriesCard extends ConsumerWidget { }).toList(), ), ) - : null, + : const CardBody( + child: HintText( + 'Add a category to show this thing in the catalog.'), + ), ); }, ); diff --git a/apps/librarian/lib/modules/things/details/inventory/items_card.dart b/apps/librarian/lib/modules/things/details/inventory/items_card.dart index 414851f..9c773a4 100644 --- a/apps/librarian/lib/modules/things/details/inventory/items_card.dart +++ b/apps/librarian/lib/modules/things/details/inventory/items_card.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/things/providers/thing_details_controller_provider.dart'; +import 'package:librarian_app/widgets/details_card/card_body.dart'; import 'package:librarian_app/widgets/details_card/details_card.dart'; +import 'package:librarian_app/widgets/hint_text.dart'; import '../../../../core/api/models/item_model.dart'; import '../../../../widgets/details_card/card_header.dart'; @@ -49,59 +51,57 @@ class ItemsCard extends ConsumerWidget { const SizedBox(height: 8), ], ), - showDivider: items.isNotEmpty, body: items.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(bottom: 8), - child: ListView.separated( - shrinkWrap: true, - itemCount: items.length, - itemBuilder: (context, index) { - final item = items[index]; + ? ListView.separated( + shrinkWrap: true, + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; - return ListTile( - leading: getIcon(item), - onTap: () => onTap?.call(item), - title: Text('#${item.number}'), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(item.brand ?? 'Generic'), - const SizedBox(width: 16), - IconButton( - onPressed: onToggleHidden != null - ? () => onToggleHidden!(item.id, !item.hidden) - : null, - tooltip: onToggleHidden == null - ? null - : item.hidden - ? 'Unhide' - : 'Hide', - icon: item.hidden - ? const Icon(Icons.visibility_off) - : const Icon(Icons.visibility), + return ListTile( + leading: getIcon(item), + onTap: () => onTap?.call(item), + title: Text('#${item.number}'), + subtitle: Text(item.brand ?? 'Generic'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: onToggleHidden != null + ? () => onToggleHidden!(item.id, !item.hidden) + : null, + tooltip: onToggleHidden == null + ? null + : item.hidden + ? 'Unhide' + : 'Hide', + icon: item.hidden + ? const Icon(Icons.visibility_off) + : const Icon(Icons.visibility), + ), + const SizedBox(width: 16), + IconButton( + onPressed: () => deleteItem( + item.id, + item.number, + item.name, ), - const SizedBox(width: 16), - IconButton( - onPressed: () => deleteItem( - item.id, - item.number, - item.name, - ), - tooltip: 'Delete', - icon: const Icon( - Icons.delete_outline, - color: Colors.red, - ), + tooltip: 'Delete', + icon: const Icon( + Icons.delete_outline, + color: Colors.red, ), - ], - ), - ); - }, - separatorBuilder: (c, i) => const Divider(), - ), + ), + ], + ), + ); + }, + separatorBuilder: (c, i) => const Divider(height: 1), ) - : null, + : const CardBody( + child: HintText( + 'Since this thing has no items, it will be shown on the Wish List.'), + ), ); } diff --git a/apps/librarian/lib/modules/things/details/inventory_details.dart b/apps/librarian/lib/modules/things/details/inventory_details.dart index 4b2843f..fa056c9 100644 --- a/apps/librarian/lib/modules/things/details/inventory_details.dart +++ b/apps/librarian/lib/modules/things/details/inventory_details.dart @@ -5,8 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/dashboard/providers/end_drawer_provider.dart'; import 'package:librarian_app/modules/things/details/inventory/create_items/create_items_dialog.dart'; import 'package:librarian_app/modules/things/details/inventory/item_details_drawer/drawer.dart'; -import 'package:librarian_app/widgets/fields/checkbox_field.dart'; -import 'package:librarian_app/widgets/input_decoration.dart'; +import 'package:librarian_app/modules/things/details/thing_details/thing_details_card.dart'; import 'package:librarian_app/core/api/models/updated_image_model.dart'; import 'package:librarian_app/modules/things/details/inventory/item_details_page.dart'; import 'package:librarian_app/modules/things/providers/edited_thing_details_providers.dart'; @@ -37,7 +36,7 @@ class InventoryDetails extends ConsumerWidget { final details = snapshot.data!; return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Wrap( spacing: 16, @@ -66,45 +65,7 @@ class InventoryDetails extends ConsumerWidget { } }, ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextField( - controller: TextEditingController(text: details.name), - decoration: inputDecoration.copyWith(labelText: 'Name'), - onChanged: (value) => - ref.read(nameProvider.notifier).state = value, - ), - const SizedBox(height: 16), - TextField( - controller: - TextEditingController(text: details.spanishName), - decoration: - inputDecoration.copyWith(labelText: 'Name (Spanish)'), - onChanged: (value) => - ref.read(spanishNameProvider.notifier).state = value, - ), - const SizedBox(height: 32), - CheckboxField( - title: 'Hidden', - value: ref.watch(hiddenProvider) ?? details.hidden, - onChanged: (bool? value) { - ref.read(hiddenProvider.notifier).state = - value ?? false; - }, - ), - const SizedBox(height: 32), - CheckboxField( - title: 'Eye Protection Required', - value: ref.watch(eyeProtectionProvider) ?? - details.eyeProtection, - onChanged: (bool? value) { - ref.read(eyeProtectionProvider.notifier).state = - value ?? false; - }, - ), - ], - ), + ThingDetailsCard(details: details), ], ), const SizedBox(height: 32), diff --git a/apps/librarian/lib/modules/things/details/inventory_details_pane.dart b/apps/librarian/lib/modules/things/details/inventory_details_pane.dart index f484c60..57d9fc4 100644 --- a/apps/librarian/lib/modules/things/details/inventory_details_pane.dart +++ b/apps/librarian/lib/modules/things/details/inventory_details_pane.dart @@ -54,11 +54,16 @@ class InventoryDetailsPane extends ConsumerWidget { children: [ PaneHeader( child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - thingDetails.name, - style: const TextStyle(fontSize: 24), + Expanded( + child: Container( + margin: const EdgeInsets.only(right: 16.0), + child: Text( + thingDetails.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontSize: 24), + ), + ), ), Row( children: [ diff --git a/apps/librarian/lib/modules/things/details/thing_details/thing_details_card.dart b/apps/librarian/lib/modules/things/details/thing_details/thing_details_card.dart new file mode 100644 index 0000000..c41639b --- /dev/null +++ b/apps/librarian/lib/modules/things/details/thing_details/thing_details_card.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/detailed_thing_model.dart'; +import 'package:librarian_app/widgets/details_card/card_body.dart'; +import 'package:librarian_app/widgets/details_card/details_card.dart'; +import 'package:librarian_app/widgets/fields/checkbox_field.dart'; +import 'package:librarian_app/widgets/input_decoration.dart'; + +import '../../providers/edited_thing_details_providers.dart'; + +class ThingDetailsCard extends ConsumerWidget { + const ThingDetailsCard({super.key, required this.details}); + + final DetailedThingModel details; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return DetailsCard( + body: CardBody( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: TextEditingController(text: details.name), + decoration: inputDecoration.copyWith(labelText: 'Name'), + onChanged: (value) => ref.read(nameProvider.notifier).state = value, + ), + const SizedBox(height: 16), + TextField( + controller: TextEditingController(text: details.spanishName), + decoration: inputDecoration.copyWith(labelText: 'Name (Spanish)'), + onChanged: (value) => + ref.read(spanishNameProvider.notifier).state = value, + ), + const SizedBox(height: 32), + CheckboxField( + title: 'Hidden', + value: ref.watch(hiddenProvider) ?? details.hidden, + onChanged: (bool? value) { + ref.read(hiddenProvider.notifier).state = value ?? false; + }, + ), + const SizedBox(height: 16), + CheckboxField( + title: 'Eye Protection Required', + value: ref.watch(eyeProtectionProvider) ?? details.eyeProtection, + onChanged: (bool? value) { + ref.read(eyeProtectionProvider.notifier).state = value ?? false; + }, + ), + ], + ), + )); + } +} diff --git a/apps/librarian/lib/widgets/details_card/card_body.dart b/apps/librarian/lib/widgets/details_card/card_body.dart index 5782cc4..d6158af 100644 --- a/apps/librarian/lib/widgets/details_card/card_body.dart +++ b/apps/librarian/lib/widgets/details_card/card_body.dart @@ -11,7 +11,7 @@ class CardBody extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.only(left: 16, top: 8, right: 16, bottom: 16), + padding: const EdgeInsets.all(16.0), child: child, ); } diff --git a/apps/librarian/lib/widgets/hint_text.dart b/apps/librarian/lib/widgets/hint_text.dart new file mode 100644 index 0000000..e846da1 --- /dev/null +++ b/apps/librarian/lib/widgets/hint_text.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class HintText extends StatelessWidget { + const HintText(this.text, {super.key}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + text, + style: + Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey), + ); + } +} diff --git a/apps/librarian/pubspec.yaml b/apps/librarian/pubspec.yaml index 1995317..0e66d0e 100644 --- a/apps/librarian/pubspec.yaml +++ b/apps/librarian/pubspec.yaml @@ -10,7 +10,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. -version: 1.0.0+13 +version: 1.0.0+14 environment: sdk: '>=3.0.0'