diff --git a/apps/librarian/lib/modules/loans/models/loan_details_model.dart b/apps/librarian/lib/core/api/models/loan_details_model.dart similarity index 100% rename from apps/librarian/lib/modules/loans/models/loan_details_model.dart rename to apps/librarian/lib/core/api/models/loan_details_model.dart diff --git a/apps/librarian/lib/modules/loans/models/loan_model.dart b/apps/librarian/lib/core/api/models/loan_model.dart similarity index 95% rename from apps/librarian/lib/modules/loans/models/loan_model.dart rename to apps/librarian/lib/core/api/models/loan_model.dart index 708000d..c2572fd 100644 --- a/apps/librarian/lib/modules/loans/models/loan_model.dart +++ b/apps/librarian/lib/core/api/models/loan_model.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:librarian_app/modules/borrowers/models/borrower_model.dart'; -import 'package:librarian_app/modules/loans/models/thing_summary_model.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; class LoanModel { final String id; diff --git a/apps/librarian/lib/modules/loans/models/thing_summary_model.dart b/apps/librarian/lib/core/api/models/thing_summary_model.dart similarity index 100% rename from apps/librarian/lib/modules/loans/models/thing_summary_model.dart rename to apps/librarian/lib/core/api/models/thing_summary_model.dart diff --git a/apps/librarian/lib/modules/loans/data/loans_repository.dart b/apps/librarian/lib/core/data/loans_repository.dart similarity index 95% rename from apps/librarian/lib/modules/loans/data/loans_repository.dart rename to apps/librarian/lib/core/data/loans_repository.dart index f699011..4eadef1 100644 --- a/apps/librarian/lib/modules/loans/data/loans_repository.dart +++ b/apps/librarian/lib/core/data/loans_repository.dart @@ -2,8 +2,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:librarian_app/core/api/api.dart' as API; -import '../models/loan_details_model.dart'; -import '../models/loan_model.dart'; +import '../api/models/loan_details_model.dart'; +import '../api/models/loan_model.dart'; class LoansRepository extends Notifier>> { @override diff --git a/apps/librarian/lib/modules/loans/widgets/layouts/loans_desktop_layout.dart b/apps/librarian/lib/dashboard/layouts/loans_desktop_layout.dart similarity index 88% rename from apps/librarian/lib/modules/loans/widgets/layouts/loans_desktop_layout.dart rename to apps/librarian/lib/dashboard/layouts/loans_desktop_layout.dart index 32e4982..a67e75f 100644 --- a/apps/librarian/lib/modules/loans/widgets/layouts/loans_desktop_layout.dart +++ b/apps/librarian/lib/dashboard/layouts/loans_desktop_layout.dart @@ -5,8 +5,8 @@ import 'package:librarian_app/widgets/panes/list_pane.dart'; import 'package:librarian_app/widgets/panes/pane_header.dart'; import 'package:librarian_app/modules/loans/providers/loans_filter_provider.dart'; import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; -import 'package:librarian_app/modules/loans/widgets/loan_details/loan_details_pane.dart'; -import 'package:librarian_app/modules/loans/widgets/loans_list/loans_list_view.dart'; +import 'package:librarian_app/modules/loans/details/loan_details_pane.dart'; +import 'package:librarian_app/modules/loans/list/loans_list_view.dart'; class LoansDesktopLayout extends ConsumerWidget { const LoansDesktopLayout({super.key}); diff --git a/apps/librarian/lib/dashboard/pages/dashboard_page.dart b/apps/librarian/lib/dashboard/pages/dashboard_page.dart index 0623fd2..2047bf4 100644 --- a/apps/librarian/lib/dashboard/pages/dashboard_page.dart +++ b/apps/librarian/lib/dashboard/pages/dashboard_page.dart @@ -1,6 +1,8 @@ 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/workspace.dart'; import 'package:librarian_app/modules/authentication/providers/auth_service_provider.dart'; import 'package:librarian_app/modules/authentication/providers/user_tray.dart'; import 'package:librarian_app/modules/borrowers/widgets/layouts/borrowers_desktop_layout.dart'; @@ -12,10 +14,9 @@ import 'package:librarian_app/dashboard/layouts/inventory_desktop_layout.dart'; import 'package:librarian_app/modules/things/details/inventory_details_page.dart'; import 'package:librarian_app/modules/things/details/inventory/inventory_list/searchable_inventory_list.dart'; import 'package:librarian_app/modules/things/create/create_thing_dialog.dart'; -import 'package:librarian_app/modules/loans/pages/checkout_page.dart'; -import 'package:librarian_app/modules/loans/pages/loan_details_page.dart'; -import 'package:librarian_app/modules/loans/widgets/loans_list/searchable_loans_list.dart'; -import 'package:librarian_app/modules/loans/widgets/layouts/loans_desktop_layout.dart'; +import 'package:librarian_app/modules/loans/details/loan_details_page.dart'; +import 'package:librarian_app/modules/loans/list/searchable_loans_list.dart'; +import 'package:librarian_app/dashboard/layouts/loans_desktop_layout.dart'; import 'package:librarian_app/modules/updates/widgets/update_dialog_controller.dart'; import 'package:librarian_app/modules/updates/notifiers/update_notifier.dart'; import 'package:librarian_app/utils/media_query.dart'; @@ -107,6 +108,7 @@ class _DashboardPageState extends ConsumerState { @override Widget build(BuildContext context) { + final ws = ref.watch(workspace); final mobile = isMobile(context); final module = _modules[_moduleIndex]; @@ -120,18 +122,18 @@ class _DashboardPageState extends ConsumerState { context: context, leadingIcon: const Icon(Icons.handshake_rounded), text: 'Create Loan', - onTap: () async { - _menuController.close(); - setState(() => _moduleIndex = 0); - await Future.delayed(const Duration(milliseconds: 150), () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const CheckoutPage(), - ), - ); - }); - }, + tooltip: ws.hasItem + ? 'Another "Create Loan" window is already in use.' + : null, + onTap: ws.hasItem + ? null + : () async { + _menuController.close(); + setState(() => _moduleIndex = 0); + await Future.delayed(const Duration(milliseconds: 150), () { + ref.read(createLoan).createLoan(context); + }); + }, ), createMenuItem( context: context, diff --git a/apps/librarian/lib/dashboard/providers/create_loan_controller.dart b/apps/librarian/lib/dashboard/providers/create_loan_controller.dart new file mode 100644 index 0000000..a228f34 --- /dev/null +++ b/apps/librarian/lib/dashboard/providers/create_loan_controller.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/dashboard/providers/workspace.dart'; +import 'package:librarian_app/dashboard/widgets/workspace_window.dart'; +import 'package:librarian_app/modules/loans/checkout/checkout_page.dart'; +import 'package:librarian_app/modules/loans/checkout/checkout_stepper.dart'; +import 'package:librarian_app/utils/media_query.dart'; + +class CreateLoanController { + CreateLoanController(this.ref); + + final Ref ref; + + void createLoan(BuildContext context) { + if (isMobile(context)) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CheckoutPage(), + ), + ); + return; + } + + final ws = ref.read(workspace); + final window = WorkspaceWindow( + title: 'Create Loan', + content: CheckoutStepper( + onFinish: () => ws.closeWindow(), + ), + ); + + ws.open(window); + } +} + +final createLoan = Provider((ref) => CreateLoanController(ref)); diff --git a/apps/librarian/lib/dashboard/providers/workspace.dart b/apps/librarian/lib/dashboard/providers/workspace.dart new file mode 100644 index 0000000..064fd0f --- /dev/null +++ b/apps/librarian/lib/dashboard/providers/workspace.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/dashboard/widgets/workspace_window.dart'; + +class WorkspaceController { + WorkspaceController(this.ref); + + final Ref ref; + final List _items = []; + final Map _minimized = {}; + + bool get hasItem => _items.isNotEmpty; + + ActiveWorkspaceItem? get activeItem { + if (_items.isEmpty) { + return null; + } + + final item = _items.last; + + return ActiveWorkspaceItem( + isMinimized: isMinimized(item.id), + widget: item.widget, + ); + } + + List> get minimizedItems { + return _minimized.entries.toList(); + } + + void open(WorkspaceWindow window) { + _items.add(WorkspaceItem( + id: window.id, + title: window.title, + widget: window, + )); + + ref.notifyListeners(); + } + + void closeWindow() { + _items.removeLast(); + ref.notifyListeners(); + } + + void close(String id) { + _items.removeWhere((i) => i.id == id); + _minimized.remove(id); + + ref.notifyListeners(); + } + + void maximize(String id) { + // minimize current active item + final activeItem = _items.last; + _minimized[activeItem.id] = activeItem; + + // make active + final item = _items.firstWhere((i) => i.id == id); + _items.remove(item); + _items.add(item); + + // maximize + _minimized.remove(id); + + ref.notifyListeners(); + } + + void minimize( + String id, { + String? title, + }) { + _minimized[id] = _items.last; + ref.notifyListeners(); + } + + bool isMinimized(String id) { + return _minimized.containsKey(id); + } +} + +class WorkspaceItem { + WorkspaceItem({ + required this.id, + required this.widget, + this.title, + }); + + final String id; + final String? title; + final Widget widget; +} + +class ActiveWorkspaceItem { + const ActiveWorkspaceItem({ + required this.isMinimized, + required this.widget, + }); + + final bool isMinimized; + final Widget widget; +} + +final workspace = Provider((ref) => WorkspaceController(ref)); diff --git a/apps/librarian/lib/dashboard/widgets/create_menu_item.dart b/apps/librarian/lib/dashboard/widgets/create_menu_item.dart index b69bc3c..39f2a59 100644 --- a/apps/librarian/lib/dashboard/widgets/create_menu_item.dart +++ b/apps/librarian/lib/dashboard/widgets/create_menu_item.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; Widget createMenuItem({ - required void Function() onTap, + required void Function()? onTap, required String text, required BuildContext context, Widget? leadingIcon, + String? tooltip, }) { - return MenuItemButton( + final button = MenuItemButton( onPressed: onTap, leadingIcon: leadingIcon, trailingIcon: const Icon(Icons.add), @@ -15,4 +16,13 @@ Widget createMenuItem({ style: Theme.of(context).textTheme.titleMedium, ), ); + + if (tooltip != null) { + return Tooltip( + message: tooltip, + child: button, + ); + } + + return button; } diff --git a/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart b/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart index ff3b702..5b3a85d 100644 --- a/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart +++ b/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:librarian_app/dashboard/widgets/workspace.dart'; class DesktopDashboard extends StatelessWidget { const DesktopDashboard({ @@ -16,49 +17,48 @@ class DesktopDashboard extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - NavigationRail( - labelType: NavigationRailLabelType.selected, - leading: leading, - destinations: const [ - NavigationRailDestination( - selectedIcon: Icon(Icons.handshake), - icon: Icon(Icons.handshake_outlined), - label: Text('Loans'), - padding: EdgeInsets.symmetric(vertical: 8), - ), - NavigationRailDestination( - selectedIcon: Icon(Icons.people), - icon: Icon(Icons.people_outlined), - label: Text('Borrowers'), - padding: EdgeInsets.symmetric(vertical: 8), - ), - NavigationRailDestination( - selectedIcon: Icon(Icons.build), - icon: Icon(Icons.build_outlined), - label: Text('Things'), - padding: EdgeInsets.symmetric(vertical: 8), - ), - NavigationRailDestination( - selectedIcon: Icon(Icons.electric_bolt), - icon: Icon(Icons.electric_bolt_outlined), - label: Text('Actions'), - padding: EdgeInsets.symmetric(vertical: 8), - ), - ], - selectedIndex: selectedIndex, - onDestinationSelected: (index) { - onDestinationSelected?.call(index); - }, - ), - Expanded( - child: Container( - margin: const EdgeInsets.only(right: 8, bottom: 8), + return Workspace( + child: Row( + children: [ + NavigationRail( + labelType: NavigationRailLabelType.selected, + leading: leading, + destinations: const [ + NavigationRailDestination( + selectedIcon: Icon(Icons.handshake), + icon: Icon(Icons.handshake_outlined), + label: Text('Loans'), + padding: EdgeInsets.symmetric(vertical: 8), + ), + NavigationRailDestination( + selectedIcon: Icon(Icons.people), + icon: Icon(Icons.people_outlined), + label: Text('Borrowers'), + padding: EdgeInsets.symmetric(vertical: 8), + ), + NavigationRailDestination( + selectedIcon: Icon(Icons.build), + icon: Icon(Icons.build_outlined), + label: Text('Things'), + padding: EdgeInsets.symmetric(vertical: 8), + ), + NavigationRailDestination( + selectedIcon: Icon(Icons.electric_bolt), + icon: Icon(Icons.electric_bolt_outlined), + label: Text('Actions'), + padding: EdgeInsets.symmetric(vertical: 8), + ), + ], + selectedIndex: selectedIndex, + onDestinationSelected: (index) { + onDestinationSelected?.call(index); + }, + ), + Expanded( child: child, ), - ), - ], + ], + ), ); } } diff --git a/apps/librarian/lib/dashboard/widgets/workspace.dart b/apps/librarian/lib/dashboard/widgets/workspace.dart new file mode 100644 index 0000000..3d0d8a2 --- /dev/null +++ b/apps/librarian/lib/dashboard/widgets/workspace.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/dashboard/providers/workspace.dart'; + +class Workspace extends ConsumerWidget { + const Workspace({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final ws = ref.watch(workspace); + + return Stack( + children: [ + Container( + margin: const EdgeInsets.only(right: 8, bottom: 8), + child: child, + ), + Align( + alignment: Alignment.center, + child: Container( + margin: const EdgeInsets.only( + right: 8, + bottom: 8, + left: 8, + ), + child: AnimatedOpacity( + opacity: ws.activeItem != null ? 1 : 0, + duration: const Duration(milliseconds: 200), + child: ws.activeItem?.widget, + ), + ), + ), + const Align( + alignment: Alignment.bottomRight, + child: MinimizedItems(), + ), + ], + ); + } +} + +class MinimizedItems extends ConsumerWidget { + const MinimizedItems({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final ws = ref.watch(workspace); + + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + ...ws.minimizedItems.map( + (entry) => MinimizedItem( + title: entry.value.title ?? '', + onMaximize: () { + ws.maximize(entry.value.id); + }, + ), + ), + const SizedBox(width: 16), + ], + ); + } +} + +class MinimizedItem extends StatelessWidget { + const MinimizedItem({ + super.key, + required this.title, + required this.onMaximize, + }); + + final String title; + final void Function() onMaximize; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onMaximize, + child: Container( + constraints: const BoxConstraints(minWidth: 160, maxWidth: 400), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + boxShadow: [ + BoxShadow( + color: Theme.of(context).shadowColor.withOpacity(0.2), + blurRadius: 4, + spreadRadius: 2, + ) + ], + color: Colors.amber, + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 16), + const Icon( + Icons.circle, + size: 8, + color: Colors.black, + ), + ], + ), + ), + ); + } +} diff --git a/apps/librarian/lib/dashboard/widgets/workspace_window.dart b/apps/librarian/lib/dashboard/widgets/workspace_window.dart new file mode 100644 index 0000000..69a9d14 --- /dev/null +++ b/apps/librarian/lib/dashboard/widgets/workspace_window.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/dashboard/providers/workspace.dart'; +import 'package:uuid/uuid.dart'; + +class WorkspaceWindow extends ConsumerWidget { + WorkspaceWindow({ + super.key, + required this.title, + required this.content, + }); + + late final id = const Uuid().v4(); + + final String title; + final Widget content; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final ws = ref.watch(workspace); + + return Offstage( + offstage: ws.isMinimized(id), + child: Card( + clipBehavior: Clip.antiAlias, + color: Theme.of(context).colorScheme.surfaceContainerHigh, + elevation: 1, + child: Column( + children: [ + WorkspaceWindowHeader( + title: title, + onMinimize: () => ws.minimize(id, title: title), + onClose: () => ws.close(id), + ), + Expanded( + child: SingleChildScrollView( + child: content, + ), + ), + ], + ), + ), + ); + } +} + +class WorkspaceWindowHeader extends StatelessWidget { + const WorkspaceWindowHeader({ + super.key, + required this.title, + required this.onMinimize, + required this.onClose, + }); + + final String title; + final void Function() onMinimize; + final void Function() onClose; + + @override + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).colorScheme.primaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Row( + children: [ + Expanded( + child: Text( + title, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + IconButton( + onPressed: onMinimize, + icon: const Icon(Icons.arrow_downward), + ), + const SizedBox(width: 4), + IconButton( + onPressed: onClose, + icon: const Icon(Icons.close), + ), + ], + ), + ); + } +} diff --git a/apps/librarian/lib/modules/loans/widgets/checkin/checkin_dialog.dart b/apps/librarian/lib/modules/loans/checkin/checkin_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/checkin/checkin_dialog.dart rename to apps/librarian/lib/modules/loans/checkin/checkin_dialog.dart diff --git a/apps/librarian/lib/modules/loans/widgets/checkout/checkout_details.dart b/apps/librarian/lib/modules/loans/checkout/checkout_details.dart similarity index 96% rename from apps/librarian/lib/modules/loans/widgets/checkout/checkout_details.dart rename to apps/librarian/lib/modules/loans/checkout/checkout_details.dart index c66fc17..68850f3 100644 --- a/apps/librarian/lib/modules/loans/widgets/checkout/checkout_details.dart +++ b/apps/librarian/lib/modules/loans/checkout/checkout_details.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:librarian_app/modules/borrowers/models/borrower_model.dart'; import 'package:librarian_app/widgets/detail.dart'; -import 'package:librarian_app/modules/loans/models/thing_summary_model.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; class CheckoutDetails extends StatelessWidget { const CheckoutDetails({ diff --git a/apps/librarian/lib/modules/loans/pages/checkout_page.dart b/apps/librarian/lib/modules/loans/checkout/checkout_page.dart similarity index 66% rename from apps/librarian/lib/modules/loans/pages/checkout_page.dart rename to apps/librarian/lib/modules/loans/checkout/checkout_page.dart index 103672f..30a0d51 100644 --- a/apps/librarian/lib/modules/loans/pages/checkout_page.dart +++ b/apps/librarian/lib/modules/loans/checkout/checkout_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:librarian_app/modules/loans/widgets/checkout/checkout_stepper.dart'; +import 'package:librarian_app/modules/loans/checkout/checkout_stepper.dart'; class CheckoutPage extends StatelessWidget { const CheckoutPage({super.key}); @@ -13,7 +13,11 @@ class CheckoutPage extends StatelessWidget { ), body: Container( constraints: const BoxConstraints(maxWidth: 600), - child: const CheckoutStepper(), + child: CheckoutStepper( + onFinish: () { + Navigator.of(context).pop(); + }, + ), ), ); } diff --git a/apps/librarian/lib/modules/loans/widgets/checkout/checkout_stepper.dart b/apps/librarian/lib/modules/loans/checkout/checkout_stepper.dart similarity index 95% rename from apps/librarian/lib/modules/loans/widgets/checkout/checkout_stepper.dart rename to apps/librarian/lib/modules/loans/checkout/checkout_stepper.dart index 05c11da..869ad46 100644 --- a/apps/librarian/lib/modules/loans/widgets/checkout/checkout_stepper.dart +++ b/apps/librarian/lib/modules/loans/checkout/checkout_stepper.dart @@ -4,20 +4,22 @@ import 'package:librarian_app/modules/borrowers/models/borrower_model.dart'; import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; import 'package:librarian_app/modules/borrowers/widgets/borrower_details/borrower_issues.dart'; import 'package:librarian_app/modules/borrowers/widgets/borrower_search_delegate.dart'; -import 'package:librarian_app/modules/loans/pages/loan_details_page.dart'; +import 'package:librarian_app/modules/loans/details/loan_details_page.dart'; import 'package:librarian_app/modules/loans/providers/loans_controller_provider.dart'; -import 'package:librarian_app/modules/loans/widgets/checkout/eye_protection_dialog.dart'; +import 'package:librarian_app/modules/loans/checkout/eye_protection_dialog.dart'; import 'package:librarian_app/utils/media_query.dart'; import 'package:librarian_app/widgets/filled_progress_button.dart'; import 'package:librarian_app/core/api/models/item_model.dart'; import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; -import 'package:librarian_app/modules/loans/models/thing_summary_model.dart'; -import 'package:librarian_app/modules/loans/widgets/checkout/connected_thing_search_field.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; +import 'package:librarian_app/modules/loans/checkout/connected_thing_search_field.dart'; import 'checkout_details.dart'; class CheckoutStepper extends ConsumerStatefulWidget { - const CheckoutStepper({super.key}); + const CheckoutStepper({super.key, this.onFinish}); + + final void Function()? onFinish; @override ConsumerState createState() => _CheckoutStepperState(); @@ -62,7 +64,7 @@ class _CheckoutStepperState extends ConsumerState { dueDate: _dueDate); Future.delayed(Duration.zero, () { - Navigator.of(context).pop(); + widget.onFinish?.call(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(success ? 'Success!' : 'Failed to create loan records'), diff --git a/apps/librarian/lib/modules/loans/widgets/checkout/connected_thing_search_field.dart b/apps/librarian/lib/modules/loans/checkout/connected_thing_search_field.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/checkout/connected_thing_search_field.dart rename to apps/librarian/lib/modules/loans/checkout/connected_thing_search_field.dart diff --git a/apps/librarian/lib/modules/loans/widgets/checkout/eye_protection_dialog.dart b/apps/librarian/lib/modules/loans/checkout/eye_protection_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/checkout/eye_protection_dialog.dart rename to apps/librarian/lib/modules/loans/checkout/eye_protection_dialog.dart diff --git a/apps/librarian/lib/modules/loans/widgets/checkout/pick_things.dart b/apps/librarian/lib/modules/loans/checkout/pick_things.dart similarity index 98% rename from apps/librarian/lib/modules/loans/widgets/checkout/pick_things.dart rename to apps/librarian/lib/modules/loans/checkout/pick_things.dart index f4ac991..4c37a55 100644 --- a/apps/librarian/lib/modules/loans/widgets/checkout/pick_things.dart +++ b/apps/librarian/lib/modules/loans/checkout/pick_things.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/item_model.dart'; import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; -import '../../../../widgets/fields/submit_text_field.dart'; +import '../../../widgets/fields/submit_text_field.dart'; import 'thing_list_tile.dart'; class PickThingsView extends ConsumerStatefulWidget { diff --git a/apps/librarian/lib/modules/loans/widgets/checkout/thing_list_tile.dart b/apps/librarian/lib/modules/loans/checkout/thing_list_tile.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/checkout/thing_list_tile.dart rename to apps/librarian/lib/modules/loans/checkout/thing_list_tile.dart diff --git a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details.dart b/apps/librarian/lib/modules/loans/details/loan_details.dart similarity index 98% rename from apps/librarian/lib/modules/loans/widgets/loan_details/loan_details.dart rename to apps/librarian/lib/modules/loans/details/loan_details.dart index f439c67..69c7704 100644 --- a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:librarian_app/modules/borrowers/models/borrower_model.dart'; import 'package:librarian_app/widgets/detail.dart'; -import 'package:librarian_app/modules/loans/models/thing_summary_model.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; import 'package:librarian_app/utils/media_query.dart'; import 'package:librarian_app/widgets/no_image.dart'; diff --git a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_controller.dart b/apps/librarian/lib/modules/loans/details/loan_details_controller.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_controller.dart rename to apps/librarian/lib/modules/loans/details/loan_details_controller.dart diff --git a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_header.dart b/apps/librarian/lib/modules/loans/details/loan_details_header.dart similarity index 94% rename from apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_header.dart rename to apps/librarian/lib/modules/loans/details/loan_details_header.dart index 5be9ef1..d528dd8 100644 --- a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_header.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_header.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/widgets/panes/pane_header.dart'; -import 'package:librarian_app/modules/loans/widgets/email/send_email_dialog.dart'; -import 'package:librarian_app/modules/loans/widgets/loan_details/loan_details_controller.dart'; +import 'package:librarian_app/modules/loans/email/send_email_dialog.dart'; +import 'package:librarian_app/modules/loans/details/loan_details_controller.dart'; -import '../../models/loan_details_model.dart'; +import '../../../core/api/models/loan_details_model.dart'; import '../checkin/checkin_dialog.dart'; import '../edit/edit_loan_dialog.dart'; import 'thing_number.dart'; diff --git a/apps/librarian/lib/modules/loans/pages/loan_details_page.dart b/apps/librarian/lib/modules/loans/details/loan_details_page.dart similarity index 92% rename from apps/librarian/lib/modules/loans/pages/loan_details_page.dart rename to apps/librarian/lib/modules/loans/details/loan_details_page.dart index 08eec41..4c1db5f 100644 --- a/apps/librarian/lib/modules/loans/pages/loan_details_page.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_page.dart @@ -4,12 +4,12 @@ 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/widgets/checkin/checkin_dialog.dart'; -import 'package:librarian_app/modules/loans/widgets/email/send_email_dialog.dart'; -import 'package:librarian_app/modules/loans/widgets/loan_details/loan_details.dart'; -import 'package:librarian_app/modules/loans/widgets/loan_details/loan_details_controller.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'; +import 'package:librarian_app/modules/loans/details/loan_details_controller.dart'; -import '../widgets/edit/edit_loan_dialog.dart'; +import '../edit/edit_loan_dialog.dart'; class LoanDetailsPage extends ConsumerWidget { const LoanDetailsPage({super.key}); diff --git a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_pane.dart b/apps/librarian/lib/modules/loans/details/loan_details_pane.dart similarity index 95% rename from apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_pane.dart rename to apps/librarian/lib/modules/loans/details/loan_details_pane.dart index 49fa897..d200e88 100644 --- a/apps/librarian/lib/modules/loans/widgets/loan_details/loan_details_pane.dart +++ b/apps/librarian/lib/modules/loans/details/loan_details_pane.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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/widgets/loan_details/loan_details_header.dart'; +import 'package:librarian_app/modules/loans/details/loan_details_header.dart'; -import '../../providers/loans_repository_provider.dart'; +import '../providers/loans_repository_provider.dart'; import 'loan_details.dart'; class LoanDetailsPane extends ConsumerWidget { diff --git a/apps/librarian/lib/modules/loans/widgets/loan_details/thing_number.dart b/apps/librarian/lib/modules/loans/details/thing_number.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/loan_details/thing_number.dart rename to apps/librarian/lib/modules/loans/details/thing_number.dart diff --git a/apps/librarian/lib/modules/loans/widgets/edit/edit_loan_dialog.dart b/apps/librarian/lib/modules/loans/edit/edit_loan_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/edit/edit_loan_dialog.dart rename to apps/librarian/lib/modules/loans/edit/edit_loan_dialog.dart diff --git a/apps/librarian/lib/modules/loans/widgets/email/send_email_dialog.dart b/apps/librarian/lib/modules/loans/email/send_email_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/loans/widgets/email/send_email_dialog.dart rename to apps/librarian/lib/modules/loans/email/send_email_dialog.dart diff --git a/apps/librarian/lib/modules/loans/widgets/loans_list/loans_list.dart b/apps/librarian/lib/modules/loans/list/loans_list.dart similarity index 97% rename from apps/librarian/lib/modules/loans/widgets/loans_list/loans_list.dart rename to apps/librarian/lib/modules/loans/list/loans_list.dart index 022d005..39b726c 100644 --- a/apps/librarian/lib/modules/loans/widgets/loans_list/loans_list.dart +++ b/apps/librarian/lib/modules/loans/list/loans_list.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:librarian_app/utils/media_query.dart'; -import '../../models/loan_model.dart'; +import '../../../core/api/models/loan_model.dart'; class LoansList extends StatelessWidget { final List loans; diff --git a/apps/librarian/lib/modules/loans/widgets/loans_list/loans_list_view.dart b/apps/librarian/lib/modules/loans/list/loans_list_view.dart similarity index 96% rename from apps/librarian/lib/modules/loans/widgets/loans_list/loans_list_view.dart rename to apps/librarian/lib/modules/loans/list/loans_list_view.dart index 8159545..fb3ea58 100644 --- a/apps/librarian/lib/modules/loans/widgets/loans_list/loans_list_view.dart +++ b/apps/librarian/lib/modules/loans/list/loans_list_view.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/loans/providers/loans_provider.dart'; import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; -import '../../models/loan_model.dart'; +import '../../../core/api/models/loan_model.dart'; import 'loans_list.dart'; class LoansListView extends ConsumerWidget { diff --git a/apps/librarian/lib/modules/loans/widgets/loans_list/searchable_loans_list.dart b/apps/librarian/lib/modules/loans/list/searchable_loans_list.dart similarity index 94% rename from apps/librarian/lib/modules/loans/widgets/loans_list/searchable_loans_list.dart rename to apps/librarian/lib/modules/loans/list/searchable_loans_list.dart index 6d487d5..8ebb1db 100644 --- a/apps/librarian/lib/modules/loans/widgets/loans_list/searchable_loans_list.dart +++ b/apps/librarian/lib/modules/loans/list/searchable_loans_list.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/widgets/fields/submit_text_field.dart'; -import 'package:librarian_app/modules/loans/models/loan_model.dart'; +import 'package:librarian_app/core/api/models/loan_model.dart'; import 'package:librarian_app/modules/loans/providers/loans_filter_provider.dart'; import 'loans_list_view.dart'; diff --git a/apps/librarian/lib/modules/loans/pages/open_loan_page.dart b/apps/librarian/lib/modules/loans/pages/open_loan_page.dart deleted file mode 100644 index a3048c9..0000000 --- a/apps/librarian/lib/modules/loans/pages/open_loan_page.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/models/borrower_model.dart'; -import 'package:librarian_app/core/api/models/item_model.dart'; -import 'package:librarian_app/modules/loans/models/thing_summary_model.dart'; -import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; -import 'package:librarian_app/modules/loans/widgets/checkout/pick_things.dart'; -import 'package:librarian_app/modules/borrowers/widgets/needs_attention_view.dart'; -import 'package:librarian_app/modules/loans/widgets/loan_details/loan_details.dart'; -import 'package:librarian_app/modules/borrowers/widgets/borrowers_list/searchable_borrowers_list.dart'; - -class OpenLoanPage extends ConsumerStatefulWidget { - const OpenLoanPage({super.key}); - - @override - ConsumerState createState() => _OpenLoanPageState(); -} - -class _OpenLoanPageState extends ConsumerState { - OpenLoanView _currentView = OpenLoanView.selectBorrower; - - late String _viewTitle; - late Widget _body; - Widget? _floatingActionButton; - - BorrowerModel _borrower = BorrowerModel(id: '', name: "Borrower", issues: []); - final List _things = []; - final DateTime _dueDate = DateTime.now().add(const Duration(days: 7)); - - void _configureView() { - switch (_currentView) { - case OpenLoanView.selectBorrower: - _viewTitle = "Select Borrower"; - _body = SearchableBorrowersList(onTapBorrower: _onTapBorrower); - break; - case OpenLoanView.addThings: - _viewTitle = "Add Things"; - _body = PickThingsView( - pickedThings: _things, - onThingPicked: _onTapThing, - ); - _floatingActionButton = _things.isNotEmpty - ? FloatingActionButton.extended( - onPressed: () => - setState(() => _currentView = OpenLoanView.confirmLoan), - icon: const Icon(Icons.navigate_next_rounded), - label: const Text('NEXT'), - ) - : null; - break; - case OpenLoanView.confirmLoan: - _viewTitle = "Confirm Loan"; - _body = Padding( - padding: const EdgeInsets.all(16), - child: LoanDetails( - borrower: _borrower, - things: _things - .map((t) => ThingSummaryModel( - id: t.id, - name: t.name, - number: t.number, - images: [], - )) - .toList(), - checkedOutDate: DateTime.now(), - dueDate: _dueDate, - ), - ); - _floatingActionButton = FloatingActionButton.extended( - onPressed: _onTapCreate, - icon: const Icon(Icons.check_rounded), - label: const Text('CONFIRM'), - backgroundColor: Colors.green, - ); - break; - case OpenLoanView.borrowerNeedsAttention: - _viewTitle = "Ineligible"; - _body = NeedsAttentionView(borrower: _borrower); - _floatingActionButton = FloatingActionButton( - onPressed: () => Navigator.pop(context), - backgroundColor: Colors.green, - child: const Icon(Icons.close_rounded), - ); - break; - } - } - - void _onTapBorrower(BorrowerModel borrower) { - _borrower = borrower; - if (borrower.active) { - setState(() => _currentView = OpenLoanView.addThings); - return; - } - - setState(() => _currentView = OpenLoanView.borrowerNeedsAttention); - } - - void _onTapThing(ItemModel thing) { - setState(() { - if (_things.contains(thing)) { - _things.remove(thing); - } else { - _things.add(thing); - } - }); - } - - Future _onTapCreate() async { - final loans = ref.read(loansRepositoryProvider.notifier); - - await loans.openLoan( - borrowerId: _borrower.id, - thingIds: _things.map((e) => e.id).toList(), - dueBackDate: _dueDate, - ); - - Future.delayed( - Duration.zero, - () => Navigator.of(context).pop(), - ); - } - - @override - Widget build(BuildContext context) { - _configureView(); - - return Scaffold( - appBar: AppBar(title: Text(_viewTitle)), - body: _body, - floatingActionButton: _floatingActionButton, - ); - } -} - -enum OpenLoanView { - selectBorrower, - addThings, - confirmLoan, - borrowerNeedsAttention -} 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 5924858..ec9205b 100644 --- a/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loan_details_provider.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; import 'package:librarian_app/modules/loans/providers/selected_loan_provider.dart'; -import '../models/loan_details_model.dart'; +import '../../../core/api/models/loan_details_model.dart'; final loanDetailsProvider = Provider>((ref) async { ref.watch(loansRepositoryProvider); diff --git a/apps/librarian/lib/modules/loans/providers/loans_provider.dart b/apps/librarian/lib/modules/loans/providers/loans_provider.dart index 5922e12..d0e8252 100644 --- a/apps/librarian/lib/modules/loans/providers/loans_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loans_provider.dart @@ -1,5 +1,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/loans/models/loan_model.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'; diff --git a/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart b/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart index 7ff67e9..77aa9e6 100644 --- a/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/loans_repository_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/loans/data/loans_repository.dart'; +import 'package:librarian_app/core/data/loans_repository.dart'; -import '../models/loan_model.dart'; +import '../../../core/api/models/loan_model.dart'; final loansRepositoryProvider = NotifierProvider>>( diff --git a/apps/librarian/lib/modules/loans/providers/selected_loan_provider.dart b/apps/librarian/lib/modules/loans/providers/selected_loan_provider.dart index a76d374..981c749 100644 --- a/apps/librarian/lib/modules/loans/providers/selected_loan_provider.dart +++ b/apps/librarian/lib/modules/loans/providers/selected_loan_provider.dart @@ -1,4 +1,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/loans/models/loan_model.dart'; +import 'package:librarian_app/core/api/models/loan_model.dart'; final selectedLoanProvider = StateProvider((ref) => null); diff --git a/apps/librarian/pubspec.yaml b/apps/librarian/pubspec.yaml index b2dd99b..0822cce 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+8 +version: 1.0.0+9 environment: sdk: '>=3.0.0' diff --git a/apps/librarian/test/loans/initials_test.dart b/apps/librarian/test/loans/initials_test.dart index 86a4fef..f920eda 100644 --- a/apps/librarian/test/loans/initials_test.dart +++ b/apps/librarian/test/loans/initials_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:librarian_app/modules/loans/widgets/loans_list/loans_list.dart'; +import 'package:librarian_app/modules/loans/list/loans_list.dart'; void main() { group('Initials', () {