From 3b30302e2cd14a20e1d767cacf2cb4e12fa68c9c Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Sun, 10 Nov 2024 09:56:08 -0500 Subject: [PATCH 01/15] load members list from new root provider --- .../lib/core/api/models/borrower_model.dart | 2 +- .../members/list/members_list_view.dart | 53 +++++++++++-------- .../members/providers/borrowers_provider.dart | 36 +++++++++++++ apps/librarian/lib/providers/members.dart | 6 +++ apps/librarian/lib/widgets/skeleton.dart | 20 +++++++ 5 files changed, 94 insertions(+), 23 deletions(-) create mode 100644 apps/librarian/lib/providers/members.dart create mode 100644 apps/librarian/lib/widgets/skeleton.dart diff --git a/apps/librarian/lib/core/api/models/borrower_model.dart b/apps/librarian/lib/core/api/models/borrower_model.dart index 0ce01f6..d44ef24 100644 --- a/apps/librarian/lib/core/api/models/borrower_model.dart +++ b/apps/librarian/lib/core/api/models/borrower_model.dart @@ -9,7 +9,7 @@ class BorrowerModel { bool get active => issues.isEmpty; - BorrowerModel({ + const BorrowerModel({ required this.id, required this.name, required this.issues, diff --git a/apps/librarian/lib/modules/members/list/members_list_view.dart b/apps/librarian/lib/modules/members/list/members_list_view.dart index a9a79f1..47d51be 100644 --- a/apps/librarian/lib/modules/members/list/members_list_view.dart +++ b/apps/librarian/lib/modules/members/list/members_list_view.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/modules/members/providers/borrowers_provider.dart'; -import 'package:librarian_app/modules/members/providers/edited_borrower_details_providers.dart'; -import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/widgets/skeleton.dart'; -import '../../../core/api/models/borrower_model.dart'; import 'members_list.dart'; class MembersListView extends ConsumerWidget { @@ -14,31 +13,41 @@ class MembersListView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return FutureBuilder( - future: ref.watch(borrowersProvider), - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center(child: Text(snapshot.error!.toString())); - } - - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - - if (snapshot.data!.isEmpty) { + final membersListAsync = ref.watch(membersListProvider); + return membersListAsync.when( + loading: () { + return const Skeleton( + enabled: true, + child: MembersList( + borrowers: [ + dummyMember, + dummyMember, + dummyMember, + ], + ), + ); + }, + data: (list) { + if (list.members.isEmpty) { return const Center(child: Text('No results found')); } return MembersList( - borrowers: snapshot.data!, - selected: ref.watch(selectedBorrowerProvider), - onTap: (borrower) { - ref.read(borrowerDetailsEditorProvider).discardChanges(); - ref.read(selectedBorrowerProvider.notifier).state = borrower; - onTap?.call(borrower); - }, + borrowers: list.members, + selected: list.selected, + onTap: list.onTap, ); }, + error: (error, stackTrace) { + return Center(child: Text(stackTrace.toString())); + }, + skipLoadingOnReload: true, ); } } + +const dummyMember = BorrowerModel( + id: '', + name: 'Member', + issues: [], +); diff --git a/apps/librarian/lib/modules/members/providers/borrowers_provider.dart b/apps/librarian/lib/modules/members/providers/borrowers_provider.dart index dffd8a1..58f718d 100644 --- a/apps/librarian/lib/modules/members/providers/borrowers_provider.dart +++ b/apps/librarian/lib/modules/members/providers/borrowers_provider.dart @@ -2,6 +2,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/modules/members/providers/borrowers_filter_provider.dart'; import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/providers/members.dart'; + +import 'edited_borrower_details_providers.dart'; final borrowersProvider = Provider>>((ref) async { final searchFilter = ref.watch(borrowersFilterProvider); @@ -15,3 +19,35 @@ final borrowersProvider = Provider>>((ref) async { .where((b) => b.name.toLowerCase().contains(searchFilter.toLowerCase())) .toList(); }); + +final membersListProvider = FutureProvider((ref) async { + final filter = ref.watch(borrowersFilterProvider); + final members = await ref.watch(membersProvider); + + final filteredMembers = filter == null || filter.isEmpty + ? members + : members + .where((b) => b.name.toLowerCase().contains(filter.toLowerCase())) + .toList(); + + return MembersListViewModel( + selected: ref.watch(selectedBorrowerProvider), + members: filteredMembers, + onTap: (member) { + ref.read(borrowerDetailsEditorProvider).discardChanges(); + ref.read(selectedBorrowerProvider.notifier).state = member; + }, + ); +}); + +class MembersListViewModel { + const MembersListViewModel({ + required this.selected, + required this.members, + required this.onTap, + }); + + final BorrowerModel? selected; + final List members; + final void Function(BorrowerModel) onTap; +} diff --git a/apps/librarian/lib/providers/members.dart b/apps/librarian/lib/providers/members.dart new file mode 100644 index 0000000..ed3e600 --- /dev/null +++ b/apps/librarian/lib/providers/members.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/data/borrowers_repository.dart'; + +final membersProvider = Provider((ref) async { + return await BorrowersRepository().getBorrowers(); +}); diff --git a/apps/librarian/lib/widgets/skeleton.dart b/apps/librarian/lib/widgets/skeleton.dart new file mode 100644 index 0000000..1e5d2e6 --- /dev/null +++ b/apps/librarian/lib/widgets/skeleton.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:skeletonizer/skeletonizer.dart'; + +class Skeleton extends StatelessWidget { + const Skeleton({ + super.key, + required this.enabled, + required this.child, + }); + + final bool enabled; + final Widget child; + + @override + Widget build(BuildContext context) { + return Skeletonizer( + child: child, + ); + } +} From bd12393104735ef2de7ea91ca575f75362099322 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Sun, 10 Nov 2024 22:19:17 -0500 Subject: [PATCH 02/15] load members from new provider during checkout --- .../loans/checkout/stepper/borrower/borrower_step.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart index 3455b68..4ad809d 100644 --- a/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart +++ b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart @@ -4,6 +4,7 @@ import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/modules/members/details/issues.dart'; import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/loans/checkout/stepper/borrower/borrower_search_delegate.dart'; +import 'package:librarian_app/providers/members.dart'; Step buildBorrowerStep({ required BuildContext context, @@ -80,8 +81,8 @@ class _SelectBorrowerTextFieldState onTap: () { setState(() => _isLoading = true); - ref.invalidate(borrowersRepositoryProvider); - ref.read(borrowersRepositoryProvider).then((borrowers) async { + ref.invalidate(membersProvider); + ref.read(membersProvider).then((borrowers) async { return await showSearch( context: context, delegate: BorrowerSearchDelegate(borrowers), From 6703aab9573725364837936f0d1ca19a6dd256cb Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Sun, 10 Nov 2024 22:20:00 -0500 Subject: [PATCH 03/15] in debug, allow due dates in the past --- .../loans/checkout/stepper/confirm/checkout_details.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart b/apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart index a747019..872042d 100644 --- a/apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart +++ b/apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/widgets/detail.dart'; @@ -17,11 +18,13 @@ class CheckoutDetails extends StatelessWidget { final DateTime dueDate; final void Function(DateTime newDate) onDueDateUpdated; + DateTime get _twoWeeksAgo => DateTime.now().add(const Duration(days: -14)); + void showDateSelection(BuildContext context) async { showDatePicker( context: context, initialDate: dueDate, - firstDate: dueDate, + firstDate: kDebugMode ? _twoWeeksAgo : dueDate, lastDate: dueDate.add(const Duration(days: 14)), ).then((value) { if (value == null) return; From 60b36a0add33d4ed415c59e0274a126580db6c42 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 20:40:45 -0500 Subject: [PATCH 04/15] connect member details to new provider --- .../members/details/member_details.dart | 94 +++++++++++++------ .../providers/borrower_details_provider.dart | 39 ++++++++ apps/librarian/lib/widgets/skeleton.dart | 1 + 3 files changed, 105 insertions(+), 29 deletions(-) diff --git a/apps/librarian/lib/modules/members/details/member_details.dart b/apps/librarian/lib/modules/members/details/member_details.dart index b8886dc..0f6ec4d 100644 --- a/apps/librarian/lib/modules/members/details/member_details.dart +++ b/apps/librarian/lib/modules/members/details/member_details.dart @@ -1,48 +1,84 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/issue_model.dart'; import 'package:librarian_app/modules/members/details/contact_card.dart'; import 'package:librarian_app/modules/members/providers/borrower_details_provider.dart'; import 'package:librarian_app/modules/members/details/issues_card.dart'; import 'package:librarian_app/modules/members/details/payments_card.dart'; +import 'package:librarian_app/widgets/skeleton.dart'; class MemberDetails extends ConsumerWidget { const MemberDetails({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final borrowerDetails = ref.watch(borrowerDetailsProvider); - - return FutureBuilder( - future: borrowerDetails, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - - if (snapshot.hasError) { - return Center(child: Text(snapshot.error.toString())); + final details = ref.watch(memberDetailsProvider); + return details.when( + data: (model) { + if (model == null) { + return const SizedBox.shrink(); } - final borrower = snapshot.data!; - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ContactCard( - name: borrower.name, - email: borrower.email, - phone: borrower.phone, - ), - const SizedBox(height: 32), - IssuesCard( - borrowerId: borrower.id, - issues: borrower.issues, - ), - const SizedBox(height: 32), - const PaymentsCard(), - ], + return _Details( + id: model.id, + name: model.name, + email: model.email, + phone: model.phone, + issues: model.issues, ); }, + loading: () { + return const Skeleton( + enabled: true, + child: _Details( + id: '', + name: '', + email: '', + phone: '', + issues: [], + ), + ); + }, + error: (error, stackTrace) { + return Center(child: Text(stackTrace.toString())); + }, + ); + } +} + +class _Details extends StatelessWidget { + const _Details({ + required this.id, + required this.name, + required this.email, + required this.phone, + required this.issues, + }); + + final String id; + final String name; + final String? email; + final String? phone; + final List issues; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ContactCard( + name: name, + email: email, + phone: phone, + ), + const SizedBox(height: 32), + IssuesCard( + borrowerId: id, + issues: issues, + ), + const SizedBox(height: 32), + const PaymentsCard(), + ], ); } } diff --git a/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart b/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart index 41b41a8..5155f94 100644 --- a/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart +++ b/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart @@ -1,4 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/issue_model.dart'; +import 'package:librarian_app/core/data/borrowers_repository.dart'; import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; @@ -14,3 +16,40 @@ final borrowerDetailsProvider = Provider>((ref) async { final borrowers = ref.read(borrowersRepositoryProvider.notifier); return await borrowers.getBorrowerDetails(selectedBorrower.id); }); + +final memberDetailsProvider = FutureProvider((ref) async { + final selectedMemberId = ref.watch(selectedBorrowerProvider)?.id; + if (selectedMemberId == null) { + return null; + } + + final details = + await BorrowersRepository().getBorrowerDetails(selectedMemberId); + if (details == null) { + return null; + } + + return MemberDetailsViewModel( + id: selectedMemberId, + name: details.name, + email: details.email, + phone: details.phone, + issues: details.issues, + ); +}); + +class MemberDetailsViewModel { + const MemberDetailsViewModel({ + required this.id, + required this.name, + required this.issues, + this.email, + this.phone, + }); + + final String id; + final String name; + final String? email; + final String? phone; + final List issues; +} diff --git a/apps/librarian/lib/widgets/skeleton.dart b/apps/librarian/lib/widgets/skeleton.dart index 1e5d2e6..f4408c7 100644 --- a/apps/librarian/lib/widgets/skeleton.dart +++ b/apps/librarian/lib/widgets/skeleton.dart @@ -14,6 +14,7 @@ class Skeleton extends StatelessWidget { @override Widget build(BuildContext context) { return Skeletonizer( + enabled: enabled, child: child, ); } From 59ea208a0df2cd0f14736ce66c753933cd7d7614 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 21:11:41 -0500 Subject: [PATCH 05/15] connect loans to new providers --- .../lib/core/data/loans_repository.dart | 4 - .../loans/details/loan_details_header.dart | 56 ++++--- .../loans/details/loan_details_page.dart | 85 ++++------ .../loans/details/loan_details_pane.dart | 152 +++++++++++------- .../modules/loans/edit/edit_loan_dialog.dart | 7 +- .../modules/loans/list/loans_list_view.dart | 58 +++++-- .../providers/loan_details_provider.dart | 54 +++++-- .../loans/providers/loans_provider.dart | 20 ++- apps/librarian/lib/providers/loans.dart | 6 + 9 files changed, 265 insertions(+), 177 deletions(-) create mode 100644 apps/librarian/lib/providers/loans.dart diff --git a/apps/librarian/lib/core/data/loans_repository.dart b/apps/librarian/lib/core/data/loans_repository.dart index 4eadef1..a15f2b7 100644 --- a/apps/librarian/lib/core/data/loans_repository.dart +++ b/apps/librarian/lib/core/data/loans_repository.dart @@ -58,8 +58,6 @@ class LoansRepository extends Notifier>> { thingId: thingId, checkedInDate: dateFormat.format(DateTime.now()), )); - - ref.invalidateSelf(); } Future updateLoan({ @@ -76,7 +74,5 @@ class LoansRepository extends Notifier>> { dueBackDate: dateFormat.format(dueBackDate), notes: notes, )); - - ref.invalidateSelf(); } } diff --git a/apps/librarian/lib/modules/loans/details/loan_details_header.dart b/apps/librarian/lib/modules/loans/details/loan_details_header.dart index e598270..e441d4b 100644 --- a/apps/librarian/lib/modules/loans/details/loan_details_header.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_header.dart @@ -12,14 +12,16 @@ import 'thing_number.dart'; class LoanDetailsHeader extends ConsumerWidget { const LoanDetailsHeader({ super.key, + required this.loading, required this.loan, required this.onSave, required this.onCheckIn, }); + final bool loading; final LoanDetailsModel loan; - final void Function(DateTime dueDate, String? notes) onSave; - final void Function() onCheckIn; + final void Function(DateTime dueDate, String? notes)? onSave; + final void Function()? onCheckIn; @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,14 +32,16 @@ class LoanDetailsHeader extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( - children: [ - ThingNumber(number: loan.thing.number), - const SizedBox(width: 16), - Text( - loan.thing.name, - style: const TextStyle(fontSize: 24), - ), - ], + children: loading + ? [] + : [ + ThingNumber(number: loan.thing.number), + const SizedBox(width: 16), + Text( + loan.thing.name, + style: const TextStyle(fontSize: 24), + ), + ], ), Row( children: [ @@ -49,9 +53,7 @@ class LoanDetailsHeader extends ConsumerWidget { return EditLoanDialog( dueDate: loan.dueDate, notes: loan.notes, - onSavePressed: (newDueDate, notes) { - onSave(newDueDate, notes); - }, + onSavePressed: onSave, ); }, ); @@ -103,19 +105,21 @@ class LoanDetailsHeader extends ConsumerWidget { ), const SizedBox(width: 4), IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return CheckinDialog( - thingNumber: loan.thing.number, - onCheckin: () async { - await Future(onCheckIn); - }, - ); - }, - ); - }, + onPressed: onCheckIn != null + ? () { + showDialog( + context: context, + builder: (context) { + return CheckinDialog( + thingNumber: loan.thing.number, + onCheckin: () async { + await Future(onCheckIn!); + }, + ); + }, + ); + } + : null, tooltip: 'Check in', icon: const Icon(Icons.library_add_check), ), diff --git a/apps/librarian/lib/modules/loans/details/loan_details_page.dart b/apps/librarian/lib/modules/loans/details/loan_details_page.dart index 4c1db5f..6cd4262 100644 --- a/apps/librarian/lib/modules/loans/details/loan_details_page.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_page.dart @@ -1,9 +1,7 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/things/details/inventory_details_page.dart'; import 'package:librarian_app/modules/loans/providers/loan_details_provider.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; import 'package:librarian_app/modules/loans/checkin/checkin_dialog.dart'; import 'package:librarian_app/modules/loans/email/send_email_dialog.dart'; import 'package:librarian_app/modules/loans/details/loan_details.dart'; @@ -16,57 +14,11 @@ class LoanDetailsPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final loanDetailsFuture = ref.watch(loanDetailsProvider); - - return FutureBuilder( - future: loanDetailsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return loadingScaffold; - } - - if (snapshot.hasError) { - return errorScaffold(snapshot.error.toString()); - } - - final loan = snapshot.data!; - - Future updateLoan(String loanId, String thingId, - DateTime newDueDate, String? notes) async { - final loans = ref.read(loansRepositoryProvider.notifier); - try { - await loans.updateLoan( - loanId: loanId, - thingId: thingId, - dueBackDate: newDueDate, - notes: notes); - } catch (error) { - if (kDebugMode) { - print(error); - } - } - } - - void checkIn() async { - showDialog( - context: context, - builder: (context) { - return CheckinDialog( - thingNumber: loan.thing.number, - onCheckin: () async { - final loans = ref.read(loansRepositoryProvider.notifier); - await loans.closeLoan( - loanId: loan.id, thingId: loan.thing.id); - }, - ); - }, - ).then((result) { - if (result ?? false) { - Navigator.of(context).pop(); - } - }); - } + final loanDetailsAsync = ref.watch(loanDetailsProvider); + return loanDetailsAsync.when( + data: (model) { + final loan = model.loan!; return Scaffold( appBar: AppBar( title: Text('#${loan.thing.number}'), @@ -80,9 +32,8 @@ class LoanDetailsPage extends ConsumerWidget { return EditLoanDialog( dueDate: loan.dueDate, notes: loan.notes, - onSavePressed: (newDueDate, notes) async { - await updateLoan( - loan.id, loan.thing.id, newDueDate, notes); + onSavePressed: (newDueDate, notes) { + model.onSave?.call(newDueDate, notes); }, ); }, @@ -134,12 +85,34 @@ class LoanDetailsPage extends ConsumerWidget { ), ), floatingActionButton: FloatingActionButton( - onPressed: checkIn, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return CheckinDialog( + thingNumber: loan.thing.number, + onCheckin: () async { + model.onCheckIn?.call(); + }, + ); + }, + ).then((result) { + if (result ?? false) { + Navigator.of(context).pop(); + } + }); + }, tooltip: 'Check in', child: const Icon(Icons.check_rounded), ), ); }, + loading: () { + return loadingScaffold; + }, + error: (error, stackTrace) { + return errorScaffold(stackTrace.toString()); + }, ); } } diff --git a/apps/librarian/lib/modules/loans/details/loan_details_pane.dart b/apps/librarian/lib/modules/loans/details/loan_details_pane.dart index d200e88..8ae9496 100644 --- a/apps/librarian/lib/modules/loans/details/loan_details_pane.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_pane.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; +import 'package:librarian_app/core/api/models/loan_details_model.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; import 'package:librarian_app/modules/loans/providers/loan_details_provider.dart'; -import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; import 'package:librarian_app/modules/loans/details/loan_details_header.dart'; +import 'package:librarian_app/widgets/skeleton.dart'; -import '../providers/loans_repository_provider.dart'; import 'loan_details.dart'; class LoanDetailsPane extends ConsumerWidget { @@ -12,69 +14,105 @@ class LoanDetailsPane extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final selectedLoan = ref.read(selectedLoanProvider); - final loanDetailsFuture = ref.watch(loanDetailsProvider); + final loanDetailsAsync = ref.watch(loanDetailsProvider); return Card( color: Theme.of(context).colorScheme.surfaceContainerHigh, clipBehavior: Clip.antiAlias, - child: selectedLoan == null - ? const Center(child: Text('Loan Details')) - : FutureBuilder( - future: loanDetailsFuture, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center(child: Text(snapshot.error.toString())); - } + child: loanDetailsAsync.when( + data: (model) { + if (model.loan == null) { + return const Center(child: Text('Loan Details')); + } - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } + final loan = model.loan!; - final loanDetails = snapshot.data!; + return _Details( + loading: false, + details: loan, + onSave: (dueDate, notes) { + model.onSave?.call(dueDate, notes); + }, + onCheckIn: model.onCheckIn ?? () {}, + ); + }, + loading: () { + return _Details( + loading: true, + details: dummyDetails, + onSave: (_, __) {}, + onCheckIn: () {}, + ); + }, + error: (error, stackTrace) { + return Center(child: Text(stackTrace.toString())); + }, + ), + ); + } +} + +class _Details extends StatelessWidget { + const _Details({ + required this.details, + required this.onSave, + required this.onCheckIn, + this.loading = false, + }); - return Column( - children: [ - LoanDetailsHeader( - loan: loanDetails, - onSave: (dueDate, notes) { - ref.read(loansRepositoryProvider.notifier).updateLoan( - loanId: selectedLoan.id, - thingId: selectedLoan.thing.id, - dueBackDate: dueDate, - notes: notes); - }, - onCheckIn: () { - ref - .read(loansRepositoryProvider.notifier) - .closeLoan( - loanId: selectedLoan.id, - thingId: selectedLoan.thing.id, - ) - .then((_) { - ref.read(selectedLoanProvider.notifier).state = null; - }); - }, - ), - Expanded( - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16), - child: LoanDetails( - borrower: loanDetails.borrower, - things: [loanDetails.thing], - notes: loanDetails.notes, - checkedOutDate: loanDetails.checkedOutDate, - dueDate: loanDetails.dueDate, - isOverdue: loanDetails.isOverdue, - ), - ), - ), - ), - ], - ); - }, + final bool loading; + final LoanDetailsModel details; + final void Function(DateTime, String?)? onSave; + final void Function()? onCheckIn; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + LoanDetailsHeader( + loading: loading, + loan: details, + onSave: onSave, + onCheckIn: onCheckIn, + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Skeleton( + enabled: loading, + child: LoanDetails( + borrower: details.borrower, + things: [details.thing], + notes: details.notes, + checkedOutDate: details.checkedOutDate, + dueDate: details.dueDate, + isOverdue: details.isOverdue, + ), + ), ), + ), + ), + ], ); } } + +final dummyDetails = LoanDetailsModel( + id: '', + number: 1000, + thing: const ThingSummaryModel( + id: '', + name: 'Something', + number: 0, + images: [], + ), + borrower: const BorrowerModel( + id: '', + name: 'Jane Smith', + issues: [], + ), + checkedOutDate: DateTime.now(), + dueDate: DateTime.now(), + remindersSent: 0, +); diff --git a/apps/librarian/lib/modules/loans/edit/edit_loan_dialog.dart b/apps/librarian/lib/modules/loans/edit/edit_loan_dialog.dart index 3757565..6a801ef 100644 --- a/apps/librarian/lib/modules/loans/edit/edit_loan_dialog.dart +++ b/apps/librarian/lib/modules/loans/edit/edit_loan_dialog.dart @@ -11,7 +11,7 @@ class EditLoanDialog extends StatefulWidget { final DateTime dueDate; final String? notes; - final void Function(DateTime newDueDate, String? notes) onSavePressed; + final void Function(DateTime newDueDate, String? notes)? onSavePressed; @override State createState() => _EditLoanDialogState(); @@ -38,9 +38,10 @@ class _EditLoanDialogState extends State { listenable: _notesController, builder: (context, child) { return FilledProgressButton( - onPressed: _hasUnsavedChanges() + onPressed: _hasUnsavedChanges() && widget.onSavePressed != null ? () { - widget.onSavePressed(dueDate, _notesController.text); + widget.onSavePressed + ?.call(dueDate, _notesController.text); Navigator.of(context).pop(true); } : null, diff --git a/apps/librarian/lib/modules/loans/list/loans_list_view.dart b/apps/librarian/lib/modules/loans/list/loans_list_view.dart index fb3ea58..b831303 100644 --- a/apps/librarian/lib/modules/loans/list/loans_list_view.dart +++ b/apps/librarian/lib/modules/loans/list/loans_list_view.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; +import 'package:librarian_app/core/api/models/loan_model.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; import 'package:librarian_app/modules/loans/providers/loans_provider.dart'; import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; +import 'package:librarian_app/widgets/skeleton.dart'; -import '../../../core/api/models/loan_model.dart'; import 'loans_list.dart'; class LoansListView extends ConsumerWidget { @@ -16,25 +19,15 @@ class LoansListView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final filteredLoans = ref.watch(loansProvider); - - return FutureBuilder( - future: filteredLoans, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center(child: Text(snapshot.error.toString())); - } - - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } - - if (snapshot.hasData && snapshot.data!.isEmpty) { + final filteredLoans = ref.watch(loansListProvider); + return filteredLoans.when( + data: (model) { + if (model.loans.isEmpty) { return const Center(child: Text('No results found')); } return LoansList( - loans: snapshot.data!, + loans: model.loans, selected: ref.watch(selectedLoanProvider), onTap: (loan) { ref.read(selectedLoanProvider.notifier).state = loan; @@ -42,6 +35,39 @@ class LoansListView extends ConsumerWidget { }, ); }, + loading: () { + return Skeleton( + enabled: true, + child: LoansList( + loans: [ + dummyLoan, + dummyLoan, + dummyLoan, + ], + ), + ); + }, + error: (error, stackTrace) { + return Center(child: Text(stackTrace.toString())); + }, ); } } + +final dummyLoan = LoanModel( + id: '', + number: 0, + thing: const ThingSummaryModel( + id: '', + name: 'Thing', + number: 0, + images: [], + ), + borrower: const BorrowerModel( + id: '', + name: 'Borrower Borrowersson', + issues: [], + ), + checkedOutDate: DateTime.now(), + dueDate: DateTime.now(), +); diff --git a/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart b/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart index ec9205b..c8ccd21 100644 --- a/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart @@ -1,17 +1,53 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; +import 'package:librarian_app/core/api/models/loan_details_model.dart'; +import 'package:librarian_app/core/data/loans_repository.dart'; import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; +import 'package:librarian_app/providers/loans.dart'; +import 'package:librarian_app/providers/members.dart'; -import '../../../core/api/models/loan_details_model.dart'; - -final loanDetailsProvider = Provider>((ref) async { - ref.watch(loansRepositoryProvider); +final loanDetailsProvider = FutureProvider((ref) async { + ref.watch(loansProvider); final selectedLoan = ref.watch(selectedLoanProvider); if (selectedLoan == null) { - return null; + return LoanDetailsViewModel(); } - return await ref - .read(loansRepositoryProvider.notifier) - .getLoan(id: selectedLoan.id, thingId: selectedLoan.thing.id); + final details = await LoansRepository().getLoan( + id: selectedLoan.id, + thingId: selectedLoan.thing.id, + ); + + return LoanDetailsViewModel( + loan: details, + onSave: (dueDate, notes) { + LoansRepository() + .updateLoan( + loanId: details!.id, + thingId: details.thing.id, + dueBackDate: dueDate, + notes: notes) + .then((_) => ref.invalidate(loansProvider)); + }, + onCheckIn: () { + LoansRepository() + .closeLoan(loanId: details!.id, thingId: details.thing.id) + .then((_) { + ref.invalidate(loansProvider); + ref.invalidate(selectedLoanProvider); + ref.invalidate(membersProvider); + }); + }, + ); }); + +class LoanDetailsViewModel { + LoanDetailsViewModel({ + this.loan, + this.onSave, + this.onCheckIn, + }); + + final LoanDetailsModel? loan; + final void Function(DateTime, String?)? onSave; + final void Function()? onCheckIn; +} diff --git a/apps/librarian/lib/modules/loans/providers/loans_provider.dart b/apps/librarian/lib/modules/loans/providers/loans_provider.dart index d0e8252..4a6a5a0 100644 --- a/apps/librarian/lib/modules/loans/providers/loans_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loans_provider.dart @@ -1,20 +1,28 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/loan_model.dart'; import 'package:librarian_app/modules/loans/providers/loans_filter_provider.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; +import 'package:librarian_app/providers/loans.dart'; -final loansProvider = Provider>>((ref) async { +final loansListProvider = FutureProvider((ref) async { final searchFilter = ref.watch(loansFilterProvider); - final loans = await ref.watch(loansRepositoryProvider); + final loans = await ref.watch(loansProvider); - if (searchFilter == null) { - return loans; + if (searchFilter == null || searchFilter.isEmpty) { + return LoansListViewModel(loans: loans); } - return loans + final filteredLoans = loans .where((l) => l.borrower.name.toLowerCase().contains(searchFilter.toLowerCase()) || l.thing.name.toLowerCase().contains(searchFilter.toLowerCase()) || l.thing.number.toString() == searchFilter) .toList(); + + return LoansListViewModel(loans: filteredLoans); }); + +class LoansListViewModel { + LoansListViewModel({required this.loans}); + + final List loans; +} diff --git a/apps/librarian/lib/providers/loans.dart b/apps/librarian/lib/providers/loans.dart new file mode 100644 index 0000000..269f7bc --- /dev/null +++ b/apps/librarian/lib/providers/loans.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/data/loans_repository.dart'; + +final loansProvider = Provider((ref) async { + return await LoansRepository().getLoans(); +}); From 70cf6c9e9e6cf786bc478c4c4bd9c7b82b53da23 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 21:12:03 -0500 Subject: [PATCH 06/15] remove unneeded invalidation --- .../lib/modules/loans/providers/loan_details_provider.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart b/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart index c8ccd21..66def1e 100644 --- a/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart @@ -3,7 +3,6 @@ import 'package:librarian_app/core/api/models/loan_details_model.dart'; import 'package:librarian_app/core/data/loans_repository.dart'; import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; import 'package:librarian_app/providers/loans.dart'; -import 'package:librarian_app/providers/members.dart'; final loanDetailsProvider = FutureProvider((ref) async { ref.watch(loansProvider); @@ -34,7 +33,6 @@ final loanDetailsProvider = FutureProvider((ref) async { .then((_) { ref.invalidate(loansProvider); ref.invalidate(selectedLoanProvider); - ref.invalidate(membersProvider); }); }, ); From c2d0707080bd6957bb3993ec3c61d9181d7a851d Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 21:44:32 -0500 Subject: [PATCH 07/15] reload root providers when selecting navigation items --- .../lib/dashboard/pages/dashboard_page.dart | 3 +++ .../providers/invalidate_module.dart | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 apps/librarian/lib/dashboard/providers/invalidate_module.dart diff --git a/apps/librarian/lib/dashboard/pages/dashboard_page.dart b/apps/librarian/lib/dashboard/pages/dashboard_page.dart index b5118a2..00a589f 100644 --- a/apps/librarian/lib/dashboard/pages/dashboard_page.dart +++ b/apps/librarian/lib/dashboard/pages/dashboard_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/dashboard/providers/create_loan_controller.dart'; +import 'package:librarian_app/dashboard/providers/invalidate_module.dart'; import 'package:librarian_app/dashboard/providers/workspace.dart'; import 'package:librarian_app/modules/authentication/providers/auth_service_provider.dart'; import 'package:librarian_app/modules/authentication/providers/user_tray.dart'; @@ -200,6 +201,7 @@ class _DashboardPageState extends ConsumerState { selectedIndex: _moduleIndex, onDestinationSelected: (index) { setState(() => _moduleIndex = index); + invalidateModule(ref, index); }, leading: menuAnchor, child: module.desktopLayout, @@ -209,6 +211,7 @@ class _DashboardPageState extends ConsumerState { selectedIndex: _moduleIndex, onDestinationSelected: (index) { setState(() => _moduleIndex = index); + invalidateModule(ref, index); }, destinations: const [ NavigationDestination( diff --git a/apps/librarian/lib/dashboard/providers/invalidate_module.dart b/apps/librarian/lib/dashboard/providers/invalidate_module.dart new file mode 100644 index 0000000..a042fd3 --- /dev/null +++ b/apps/librarian/lib/dashboard/providers/invalidate_module.dart @@ -0,0 +1,26 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; +import 'package:librarian_app/providers/loans.dart'; +import 'package:librarian_app/providers/members.dart'; + +void invalidateModule(WidgetRef ref, int index) { + switch (index) { + case loansIndex: + ref.invalidate(loansProvider); + return; + case membersIndex: + ref.invalidate(membersProvider); + case thingsIndex: + case repairIndex: + ref.invalidate(thingsRepositoryProvider); + return; + case actionsIndex: + return; + } +} + +const loansIndex = 0; +const membersIndex = 1; +const thingsIndex = 2; +const repairIndex = 3; +const actionsIndex = 4; From 196eb51fa44d2c0a5415d29a826dcfb12404fba5 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 21:49:56 -0500 Subject: [PATCH 08/15] display skeleton when loading things list --- .../inventory_list/inventory_list_view.dart | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/apps/librarian/lib/modules/things/details/inventory/inventory_list/inventory_list_view.dart b/apps/librarian/lib/modules/things/details/inventory/inventory_list/inventory_list_view.dart index a1c7e0a..62e8552 100644 --- a/apps/librarian/lib/modules/things/details/inventory/inventory_list/inventory_list_view.dart +++ b/apps/librarian/lib/modules/things/details/inventory/inventory_list/inventory_list_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/things/providers/edited_thing_details_providers.dart'; import 'package:librarian_app/modules/things/providers/selected_thing_provider.dart'; import 'package:librarian_app/modules/things/providers/things_provider.dart'; +import 'package:librarian_app/widgets/skeleton.dart'; import '../../../../../core/api/models/thing_model.dart'; import 'inventory_list.dart'; @@ -20,24 +21,31 @@ class InventoryListView extends ConsumerWidget { return FutureBuilder( future: things, builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } - if (snapshot.hasData && snapshot.data!.isEmpty) { return const Center(child: Text('Nothing to see here')); } - return InventoryList( - things: snapshot.data!, - selected: selectedThing, - onTap: (thing) { - ref.read(thingDetailsEditorProvider).discardChanges(); - ref.read(selectedThingProvider.notifier).state = thing; - onTap?.call(thing); - }, + return Skeleton( + enabled: snapshot.connectionState == ConnectionState.waiting, + child: InventoryList( + things: snapshot.data ?? [dummyThing, dummyThing, dummyThing], + selected: selectedThing, + onTap: (thing) { + ref.read(thingDetailsEditorProvider).discardChanges(); + ref.read(selectedThingProvider.notifier).state = thing; + onTap?.call(thing); + }, + ), ); }, ); } } + +final dummyThing = ThingModel( + id: '', + name: 'Something', + hidden: false, + stock: 1, + available: 1, +); From 0d5ba69f11c67f047011fb44a292674c3df06066 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 22:13:37 -0500 Subject: [PATCH 09/15] fix member list onTap (mobile) --- .../lib/modules/members/list/members_list_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/librarian/lib/modules/members/list/members_list_view.dart b/apps/librarian/lib/modules/members/list/members_list_view.dart index 47d51be..e88d442 100644 --- a/apps/librarian/lib/modules/members/list/members_list_view.dart +++ b/apps/librarian/lib/modules/members/list/members_list_view.dart @@ -35,7 +35,10 @@ class MembersListView extends ConsumerWidget { return MembersList( borrowers: list.members, selected: list.selected, - onTap: list.onTap, + onTap: (model) { + list.onTap(model); + onTap?.call(model); + }, ); }, error: (error, stackTrace) { From 39b70a24aecd1a8b6d8204a8cbe13b800ee2697c Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 22:23:37 -0500 Subject: [PATCH 10/15] smoothen member details loading state --- .../modules/members/details/member_details_pane.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/librarian/lib/modules/members/details/member_details_pane.dart b/apps/librarian/lib/modules/members/details/member_details_pane.dart index 7d41429..6055827 100644 --- a/apps/librarian/lib/modules/members/details/member_details_pane.dart +++ b/apps/librarian/lib/modules/members/details/member_details_pane.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/members/providers/edited_borrower_details_providers.dart'; import 'package:librarian_app/modules/members/details/member_details.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import 'package:librarian_app/widgets/dialogs/save_dialog.dart'; import 'package:librarian_app/widgets/panes/pane_header.dart'; @@ -27,13 +28,10 @@ class MemberDetailsPane extends ConsumerWidget { return Center(child: Text(snapshot.error!.toString())); } - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - + final loading = snapshot.connectionState == ConnectionState.waiting; final borrower = snapshot.data; - return borrower == null + return ref.watch(selectedBorrowerProvider) == null ? const Center(child: Text('Borrower Details')) : Column( children: [ @@ -42,7 +40,7 @@ class MemberDetailsPane extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - borrower.name, + loading ? '' : borrower!.name, style: const TextStyle( fontSize: 24, ), From a9f6444f6ccc50d30c2bdb1718278a600acd34ec Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 22:34:24 -0500 Subject: [PATCH 11/15] remove extra loading indicator from payments card --- .../modules/members/details/payments_card.dart | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/apps/librarian/lib/modules/members/details/payments_card.dart b/apps/librarian/lib/modules/members/details/payments_card.dart index 0d2e0bf..fe5c748 100644 --- a/apps/librarian/lib/modules/members/details/payments_card.dart +++ b/apps/librarian/lib/modules/members/details/payments_card.dart @@ -18,10 +18,6 @@ class PaymentsCard extends ConsumerWidget { return FutureBuilder( future: repository.getPayments(ref.watch(selectedBorrowerProvider)!.id), builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return _LoadingCardPlaceholder(); - } - final payments = snapshot.data ?? []; return DetailsCard( @@ -62,16 +58,3 @@ class _PaymentListTile extends StatelessWidget { ); } } - -class _LoadingCardPlaceholder extends StatelessWidget { - @override - Widget build(BuildContext context) { - return const DetailsCard( - body: CardBody( - child: Center( - child: CircularProgressIndicator(), - ), - ), - ); - } -} From bec7dcb2ea7387756b1ff2e284ab5f9503620106 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 22:59:16 -0500 Subject: [PATCH 12/15] load new loan from new root provider --- apps/librarian/lib/core/data/loans_repository.dart | 1 - .../modules/loans/providers/loans_controller_provider.dart | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/librarian/lib/core/data/loans_repository.dart b/apps/librarian/lib/core/data/loans_repository.dart index a15f2b7..4a653e6 100644 --- a/apps/librarian/lib/core/data/loans_repository.dart +++ b/apps/librarian/lib/core/data/loans_repository.dart @@ -40,7 +40,6 @@ class LoansRepository extends Notifier>> { dueBackDate: dateFormat.format(dueBackDate), )); - ref.invalidateSelf(); return (response.data as Map)['id'] as String; } catch (error) { return null; diff --git a/apps/librarian/lib/modules/loans/providers/loans_controller_provider.dart b/apps/librarian/lib/modules/loans/providers/loans_controller_provider.dart index 3030a2f..465f0b9 100644 --- a/apps/librarian/lib/modules/loans/providers/loans_controller_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loans_controller_provider.dart @@ -1,7 +1,8 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/data/loans_repository.dart'; +import 'package:librarian_app/providers/loans.dart'; -import 'loans_repository_provider.dart'; import 'selected_loan_provider.dart'; class LoansController { @@ -14,10 +15,10 @@ class LoansController { required List thingIds, required DateTime dueDate, }) async { - final loanId = await ref.read(loansRepositoryProvider.notifier).openLoan( + final loanId = await LoansRepository().openLoan( borrowerId: borrowerId, thingIds: thingIds, dueBackDate: dueDate); - final loan = (await ref.read(loansRepositoryProvider)) + final loan = (await ref.refresh(loansProvider)) .firstWhereOrNull((l) => l.id == loanId); ref.read(selectedLoanProvider.notifier).state = loan; From 4fb84cb3b3acf01012d5de46693cca1ebb198bd6 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 23:10:17 -0500 Subject: [PATCH 13/15] remove loans repository provider --- apps/librarian/lib/core/data/loans_repository.dart | 6 +----- .../actions/providers/actions_service_provider.dart | 4 ++-- .../loans/details/loan_details_controller.dart | 4 ++-- .../modules/loans/details/previous_loan_details.dart | 6 ++---- .../loans/providers/loans_repository_provider.dart | 8 -------- .../providers/edited_borrower_details_providers.dart | 12 +++++++----- 6 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart diff --git a/apps/librarian/lib/core/data/loans_repository.dart b/apps/librarian/lib/core/data/loans_repository.dart index 4a653e6..ae33d3c 100644 --- a/apps/librarian/lib/core/data/loans_repository.dart +++ b/apps/librarian/lib/core/data/loans_repository.dart @@ -1,14 +1,10 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:librarian_app/core/api/api.dart' as API; import '../api/models/loan_details_model.dart'; import '../api/models/loan_model.dart'; -class LoansRepository extends Notifier>> { - @override - Future> build() async => await getLoans(); - +class LoansRepository { Future getLoan({ required String id, required String thingId, diff --git a/apps/librarian/lib/modules/actions/providers/actions_service_provider.dart b/apps/librarian/lib/modules/actions/providers/actions_service_provider.dart index a324f43..a1ad13c 100644 --- a/apps/librarian/lib/modules/actions/providers/actions_service_provider.dart +++ b/apps/librarian/lib/modules/actions/providers/actions_service_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/api.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; +import 'package:librarian_app/providers/loans.dart'; import 'package:librarian_app/utils/format.dart'; class ActionsService { @@ -26,7 +26,7 @@ class ActionsService { if (result) { Future.delayed(const Duration(seconds: 2), () { - ref.invalidate(loansRepositoryProvider); + ref.invalidate(loansProvider); }); } diff --git a/apps/librarian/lib/modules/loans/details/loan_details_controller.dart b/apps/librarian/lib/modules/loans/details/loan_details_controller.dart index 983d662..be745d1 100644 --- a/apps/librarian/lib/modules/loans/details/loan_details_controller.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_controller.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/api.dart' as api; import 'package:librarian_app/modules/loans/details/thing_number.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; +import 'package:librarian_app/providers/loans.dart'; import 'package:librarian_app/widgets/dialogs/general_dialog.dart'; import 'previous_loan_details.dart'; @@ -44,7 +44,7 @@ class LoanDetailsController { Future sendReminderEmail({required int loanNumber}) async { api.sendReminderEmail(loanNumber: loanNumber).then((value) { - ref.invalidate(loansRepositoryProvider); + ref.invalidate(loansProvider); ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('Email was sent'))); diff --git a/apps/librarian/lib/modules/loans/details/previous_loan_details.dart b/apps/librarian/lib/modules/loans/details/previous_loan_details.dart index 07534f6..3bd5bc0 100644 --- a/apps/librarian/lib/modules/loans/details/previous_loan_details.dart +++ b/apps/librarian/lib/modules/loans/details/previous_loan_details.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/data/loans_repository.dart'; -import '../providers/loans_repository_provider.dart'; import 'loan_details.dart'; class PreviousLoanDetails extends ConsumerWidget { @@ -17,9 +17,7 @@ class PreviousLoanDetails extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return FutureBuilder( - future: ref - .read(loansRepositoryProvider.notifier) - .getLoan(id: loanId, thingId: itemId), + future: LoansRepository().getLoan(id: loanId, thingId: itemId), builder: (context, snapshot) { if (snapshot.hasData) { final previousLoan = snapshot.data!; diff --git a/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart b/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart deleted file mode 100644 index 77aa9e6..0000000 --- a/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/core/data/loans_repository.dart'; - -import '../../../core/api/models/loan_model.dart'; - -final loansRepositoryProvider = - NotifierProvider>>( - LoansRepository.new); diff --git a/apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart b/apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart index c9dfd59..251cbf9 100644 --- a/apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart +++ b/apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart @@ -1,8 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/data/borrowers_repository.dart'; import 'package:librarian_app/modules/members/providers/borrower_details_provider.dart'; -import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; +import 'package:librarian_app/providers/loans.dart'; +import 'package:librarian_app/providers/members.dart'; final phoneProvider = StateProvider((ref) => null); @@ -18,14 +19,15 @@ class BorrowerDetailsEditor { final ProviderRef ref; Future save() async { - await ref.read(borrowersRepositoryProvider.notifier).updateBorrower( + await BorrowersRepository().updateBorrower( ref.read(selectedBorrowerProvider)!.id, email: ref.read(emailProvider), phone: ref.read(phoneProvider)); discardChanges(); - ref.invalidate(loansRepositoryProvider); + ref.invalidate(loansProvider); + ref.invalidate(membersProvider); } void discardChanges() { @@ -33,7 +35,7 @@ class BorrowerDetailsEditor { ref.read(emailProvider.notifier).state = null; // Causes borrower details to refresh from the API - ref.invalidate(borrowerDetailsProvider); + ref.invalidate(memberDetailsProvider); } } From e1d658f6c2b4cea0a87adb18a55d54fcd17b1846 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 23:23:00 -0500 Subject: [PATCH 14/15] use skeleton for thing details loading state --- .../things/details/inventory_details.dart | 154 ++++++++++-------- .../details/inventory_details_pane.dart | 8 +- 2 files changed, 87 insertions(+), 75 deletions(-) diff --git a/apps/librarian/lib/modules/things/details/inventory_details.dart b/apps/librarian/lib/modules/things/details/inventory_details.dart index 0941413..657b88c 100644 --- a/apps/librarian/lib/modules/things/details/inventory_details.dart +++ b/apps/librarian/lib/modules/things/details/inventory_details.dart @@ -2,6 +2,7 @@ import 'package:file_picker/_internal/file_picker_web.dart'; import 'package:file_picker/file_picker.dart'; 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/modules/things/details/inventory/create_items/create_items_dialog.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'; @@ -13,6 +14,7 @@ import 'package:librarian_app/modules/things/providers/things_repository_provide import 'package:librarian_app/modules/things/details/categories/categories_card.dart'; import 'package:librarian_app/modules/things/details/inventory/items_card.dart'; import 'package:librarian_app/modules/things/details/image/thing_image_card.dart'; +import 'package:librarian_app/widgets/skeleton.dart'; import 'linked_things/card.dart'; @@ -26,78 +28,92 @@ class InventoryDetails extends ConsumerWidget { return FutureBuilder( future: detailsFuture, builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } + final details = snapshot.data; - final details = snapshot.data!; - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Wrap( - spacing: 16, - runSpacing: 32, - children: [ - ThingImageCard( - width: 240, - height: 240, - imageUrl: ref.watch(imageUploadProvider) != null - ? null - : details.images.firstOrNull?.url, - imageBytes: ref.watch(imageUploadProvider)?.bytes, - onRemove: () { - ref.read(imageUploadProvider.notifier).state = - const UpdatedImageModel(type: null, bytes: null); - }, - onReplace: () async { - FilePickerResult? result = await FilePickerWeb.platform - .pickFiles(type: FileType.image); - if (result != null) { + return Skeleton( + enabled: snapshot.connectionState == ConnectionState.waiting, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Wrap( + spacing: 16, + runSpacing: 32, + children: [ + ThingImageCard( + width: 240, + height: 240, + imageUrl: ref.watch(imageUploadProvider) != null + ? null + : details?.images.firstOrNull?.url, + imageBytes: ref.watch(imageUploadProvider)?.bytes, + onRemove: () { ref.read(imageUploadProvider.notifier).state = - UpdatedImageModel( - type: result.files.single.extension, - bytes: result.files.single.bytes, - ); - } - }, - ), - ThingDetailsCard(details: details), - ], - ), - const SizedBox(height: 32), - const Wrap( - spacing: 16, - runSpacing: 32, - children: [ - CategoriesCard(), - LinkedThingsCard(), - ], - ), - const SizedBox(height: 32), - ItemsCard( - items: details.items, - availableItemsCount: details.available, - onTap: (item) => ref - .read(itemDetailsOrchestrator) - .openItem(context, item: item, hiddenLocked: details.hidden), - onAddItemsPressed: () { - showDialog( - context: context, - builder: (context) => CreateItemsDialog( - thing: ref.read(selectedThingProvider)!, - ), - ); - }, - onToggleHidden: details.hidden - ? null - : (id, value) async { - await ref - .read(thingsRepositoryProvider.notifier) - .updateItem(id, hidden: value); + const UpdatedImageModel(type: null, bytes: null); + }, + onReplace: () async { + FilePickerResult? result = await FilePickerWeb.platform + .pickFiles(type: FileType.image); + if (result != null) { + ref.read(imageUploadProvider.notifier).state = + UpdatedImageModel( + type: result.files.single.extension, + bytes: result.files.single.bytes, + ); + } }, - ), - ], + ), + ThingDetailsCard( + details: details ?? + DetailedThingModel( + id: '', + name: 'Something', + categories: ['Category', 'Category'], + linkedThings: [], + images: [], + items: [], + hidden: false, + eyeProtection: false, + stock: 1, + available: 1, + ), + ), + ], + ), + const SizedBox(height: 32), + const Wrap( + spacing: 16, + runSpacing: 32, + children: [ + CategoriesCard(), + LinkedThingsCard(), + ], + ), + const SizedBox(height: 32), + ItemsCard( + items: details?.items ?? [], + availableItemsCount: details?.available ?? 0, + onTap: (item) => ref.read(itemDetailsOrchestrator).openItem( + context, + item: item, + hiddenLocked: details?.hidden ?? false), + onAddItemsPressed: () { + showDialog( + context: context, + builder: (context) => CreateItemsDialog( + thing: ref.read(selectedThingProvider)!, + ), + ); + }, + onToggleHidden: details?.hidden == true + ? null + : (id, value) async { + await ref + .read(thingsRepositoryProvider.notifier) + .updateItem(id, hidden: value); + }, + ), + ], + ), ); }, ); 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 1d87447..aaff3b4 100644 --- a/apps/librarian/lib/modules/things/details/inventory_details_pane.dart +++ b/apps/librarian/lib/modules/things/details/inventory_details_pane.dart @@ -44,11 +44,7 @@ class InventoryDetailsPane extends ConsumerWidget { return Center(child: Text(snapshot.error.toString())); } - if (!snapshot.hasData) { - return const Center(child: CircularProgressIndicator()); - } - - final thingDetails = snapshot.data!; + final thingDetails = snapshot.data; final hasUnsavedChanges = ref.watch(unsavedChangesProvider); return Column( @@ -60,7 +56,7 @@ class InventoryDetailsPane extends ConsumerWidget { child: Container( margin: const EdgeInsets.only(right: 16.0), child: Text( - thingDetails.name, + thingDetails?.name ?? '', overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 24), ), From e439920d5829e585673e90d37909520b5584b617 Mon Sep 17 00:00:00 2001 From: Dillon Fagan Date: Tue, 12 Nov 2024 23:53:40 -0500 Subject: [PATCH 15/15] remove need for borrower repository provider --- .../lib/core/data/borrowers_repository.dart | 16 +--------------- .../checkout/stepper/borrower/borrower_step.dart | 9 ++++----- .../lib/modules/members/details/issues.dart | 9 +++++---- .../modules/members/details/payments_card.dart | 7 +++---- .../providers/borrower_details_provider.dart | 10 ++++------ .../members/providers/borrowers_provider.dart | 3 +-- .../providers/borrowers_repository_provider.dart | 7 ------- 7 files changed, 18 insertions(+), 43 deletions(-) delete mode 100644 apps/librarian/lib/modules/members/providers/borrowers_repository_provider.dart diff --git a/apps/librarian/lib/core/data/borrowers_repository.dart b/apps/librarian/lib/core/data/borrowers_repository.dart index f24200f..bb3320f 100644 --- a/apps/librarian/lib/core/data/borrowers_repository.dart +++ b/apps/librarian/lib/core/data/borrowers_repository.dart @@ -1,14 +1,9 @@ -import 'package:collection/collection.dart'; -import 'package:flutter_riverpod/flutter_riverpod.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'; -class BorrowersRepository extends Notifier>> { - @override - Future> build() async => await getBorrowers(); - +class BorrowersRepository { Future> getBorrowers() async { final response = await api.fetchBorrowers(); return (response.data as List) @@ -16,11 +11,6 @@ class BorrowersRepository extends Notifier>> { .toList(); } - Future getBorrower(String id) async { - final borrowers = await state; - return borrowers.firstWhereOrNull((b) => b.id == id); - } - Future getBorrowerDetails(String id) async { final response = await api.fetchBorrower(id); return BorrowerModel.fromJson(response.data as Map); @@ -29,8 +19,6 @@ class BorrowersRepository extends Notifier>> { Future updateBorrower(String id, {String? email, String? phone}) async { try { await api.updateBorrower(id, email: email, phone: phone); - - ref.invalidateSelf(); return true; } catch (error) { return false; @@ -56,8 +44,6 @@ class BorrowersRepository extends Notifier>> { } catch (error) { return false; } - - ref.invalidateSelf(); return true; } } diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart index 4ad809d..c81089e 100644 --- a/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart +++ b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart @@ -1,8 +1,8 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/modules/members/details/issues.dart'; -import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/loans/checkout/stepper/borrower/borrower_search_delegate.dart'; import 'package:librarian_app/providers/members.dart'; @@ -36,10 +36,9 @@ Step buildBorrowerStep({ ); if (success) { - ref - .read(borrowersRepositoryProvider.notifier) - .getBorrower(borrower.id) - .then(onBorrowerSelected); + ref.read(membersProvider).then((list) { + return list.firstWhereOrNull((b) => b.id == borrower.id); + }).then(onBorrowerSelected); } }, ), diff --git a/apps/librarian/lib/modules/members/details/issues.dart b/apps/librarian/lib/modules/members/details/issues.dart index 2b45828..dc16bd0 100644 --- a/apps/librarian/lib/modules/members/details/issues.dart +++ b/apps/librarian/lib/modules/members/details/issues.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/core/api/models/issue_model.dart'; +import 'package:librarian_app/core/data/borrowers_repository.dart'; +import 'package:librarian_app/providers/members.dart'; -import '../../../core/api/models/issue_model.dart'; import '../payments/dues_dialog.dart'; class MemberIssues extends ConsumerWidget { @@ -105,10 +106,10 @@ class _PayDuesButton extends ConsumerWidget { instructions: issue.instructions!, imageUrl: issue.graphicUrl, onConfirmPayment: (cash) async { - final result = await ref - .read(borrowersRepositoryProvider.notifier) + final result = await BorrowersRepository() .recordPayment(borrowerId: memberId, cash: cash); + ref.invalidate(membersProvider); onRecordCashPayment(result); }, ); diff --git a/apps/librarian/lib/modules/members/details/payments_card.dart b/apps/librarian/lib/modules/members/details/payments_card.dart index fe5c748..73f52cf 100644 --- a/apps/librarian/lib/modules/members/details/payments_card.dart +++ b/apps/librarian/lib/modules/members/details/payments_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/core/data/borrowers_repository.dart'; import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import 'package:librarian_app/widgets/details_card/card_body.dart'; import 'package:librarian_app/widgets/details_card/card_header.dart'; @@ -13,10 +13,9 @@ class PaymentsCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final repository = ref.read(borrowersRepositoryProvider.notifier); - return FutureBuilder( - future: repository.getPayments(ref.watch(selectedBorrowerProvider)!.id), + future: BorrowersRepository() + .getPayments(ref.watch(selectedBorrowerProvider)!.id), builder: (context, snapshot) { final payments = snapshot.data ?? []; diff --git a/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart b/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart index 5155f94..6a2d925 100644 --- a/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart +++ b/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart @@ -1,20 +1,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/core/api/models/issue_model.dart'; import 'package:librarian_app/core/data/borrowers_repository.dart'; -import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; - -import '../../../core/api/models/borrower_model.dart'; +import 'package:librarian_app/providers/members.dart'; final borrowerDetailsProvider = Provider>((ref) async { - ref.watch(borrowersRepositoryProvider); + ref.watch(membersProvider); final selectedBorrower = ref.watch(selectedBorrowerProvider); if (selectedBorrower == null) { return null; } - final borrowers = ref.read(borrowersRepositoryProvider.notifier); - return await borrowers.getBorrowerDetails(selectedBorrower.id); + return await BorrowersRepository().getBorrowerDetails(selectedBorrower.id); }); final memberDetailsProvider = FutureProvider((ref) async { diff --git a/apps/librarian/lib/modules/members/providers/borrowers_provider.dart b/apps/librarian/lib/modules/members/providers/borrowers_provider.dart index 58f718d..bf4b6f9 100644 --- a/apps/librarian/lib/modules/members/providers/borrowers_provider.dart +++ b/apps/librarian/lib/modules/members/providers/borrowers_provider.dart @@ -1,7 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'package:librarian_app/modules/members/providers/borrowers_filter_provider.dart'; -import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import 'package:librarian_app/providers/members.dart'; @@ -9,7 +8,7 @@ import 'edited_borrower_details_providers.dart'; final borrowersProvider = Provider>>((ref) async { final searchFilter = ref.watch(borrowersFilterProvider); - final borrowers = await ref.watch(borrowersRepositoryProvider); + final borrowers = await ref.watch(membersProvider); if (searchFilter == null) { return borrowers; diff --git a/apps/librarian/lib/modules/members/providers/borrowers_repository_provider.dart b/apps/librarian/lib/modules/members/providers/borrowers_repository_provider.dart deleted file mode 100644 index c449220..0000000 --- a/apps/librarian/lib/modules/members/providers/borrowers_repository_provider.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/core/api/models/borrower_model.dart'; -import 'package:librarian_app/core/data/borrowers_repository.dart'; - -final borrowersRepositoryProvider = - NotifierProvider>>( - BorrowersRepository.new);