Skip to content
This repository has been archived by the owner on May 28, 2024. It is now read-only.

Commit

Permalink
Merge pull request #80 from pvdthings/dev
Browse files Browse the repository at this point in the history
Item Details
  • Loading branch information
dillonfagan authored Nov 6, 2023
2 parents 8447c41 + 801c056 commit df65408
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 7 deletions.
8 changes: 8 additions & 0 deletions lib/src/features/common/data/lending_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,17 @@ class LendingApi {

static Future<Response> updateInventoryItem(
String id, {
String? brand,
String? condition,
String? description,
double? estimatedValue,
bool? hidden,
}) async {
return await _client.patch('/inventory/$id', data: {
'brand': brand,
'condition': condition,
'description': description,
'estimatedValue': estimatedValue,
'hidden': hidden,
});
}
Expand Down
5 changes: 3 additions & 2 deletions lib/src/features/common/widgets/checkbox_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class CheckboxField extends StatelessWidget {
final String title;
final bool value;
final void Function(bool? value) onChanged;
final void Function(bool? value)? onChanged;

const CheckboxField({
super.key,
Expand All @@ -15,11 +15,12 @@ class CheckboxField extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Checkbox(value: value, onChanged: onChanged),
const SizedBox(width: 8),
TapRegion(
onTapInside: (_) => onChanged(!value),
onTapInside: onChanged != null ? (_) => onChanged!(!value) : null,
child: Text(
title,
style: Theme.of(context).textTheme.bodyLarge,
Expand Down
10 changes: 6 additions & 4 deletions lib/src/features/common/widgets/filled_progress_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ class _FilledProgressButtonState extends State<FilledProgressButton> {
}

return FilledButton(
onPressed: () {
setState(() => _isLoading = true);
widget.onPressed?.call();
},
onPressed: widget.onPressed == null
? null
: () {
setState(() => _isLoading = true);
widget.onPressed?.call();
},
child: widget.child,
);
}
Expand Down
13 changes: 12 additions & 1 deletion lib/src/features/inventory/data/inventory_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,20 @@ class InventoryRepository extends Notifier<Future<List<ThingModel>>> {

Future<void> updateItem(
String id, {
String? brand,
String? description,
String? condition,
double? estimatedValue,
bool? hidden,
}) async {
await LendingApi.updateInventoryItem(id, hidden: hidden);
await LendingApi.updateInventoryItem(
id,
brand: brand,
condition: condition,
description: description,
estimatedValue: estimatedValue,
hidden: hidden,
);
ref.invalidateSelf();
}
}
9 changes: 9 additions & 0 deletions lib/src/features/inventory/models/item_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ class ItemModel {
required this.hidden,
required this.totalLoans,
this.brand,
this.condition,
this.description,
this.estimatedValue,
});

final String id;
final int number;
final String name;
final String? description;
final String? brand;
final String? condition;
final double? estimatedValue;
final bool available;
final bool hidden;
final int totalLoans;
Expand All @@ -22,10 +28,13 @@ class ItemModel {
id: json['id'] as String,
number: json['number'] as int,
name: json['name'] as String? ?? 'Unknown Thing',
description: json['description'] as String?,
available: json['available'] as bool,
hidden: json['hidden'] as bool,
totalLoans: json['totalLoans'] as int,
brand: json['brand'] as String?,
condition: json['condition'] as String?,
estimatedValue: json['estimatedValue'] as double?,
);
}
}
61 changes: 61 additions & 0 deletions lib/src/features/inventory/pages/item_details_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/src/features/inventory/providers/edited_item_details_providers.dart';
import 'package:librarian_app/src/features/inventory/widgets/inventory_details/items_card/item_details.dart';

import '../models/item_model.dart';

class ItemDetailsPage extends ConsumerWidget {
const ItemDetailsPage({
super.key,
required this.item,
required this.hiddenLocked,
});

final ItemModel item;
final bool hiddenLocked;

@override
Widget build(BuildContext context, WidgetRef ref) {
return WillPopScope(
onWillPop: () async {
ref.read(itemDetailsEditorProvider).discardChanges();
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text('#${item.number} ${item.name}'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ItemDetails(item: item, hiddenLocked: hiddenLocked),
),
floatingActionButton: _FloatingActionSaveButton(itemId: item.id),
),
);
}
}

class _FloatingActionSaveButton extends ConsumerWidget {
const _FloatingActionSaveButton({required this.itemId});

final String itemId;

@override
Widget build(BuildContext context, WidgetRef ref) {
return Visibility(
visible: ref.watch(unsavedChangesProvider),
child: FloatingActionButton(
onPressed: ref.watch(unsavedChangesProvider)
? () {
ref
.read(itemDetailsEditorProvider)
.save(itemId)
.then((_) => Navigator.of(context).pop());
}
: null,
child: const Icon(Icons.save),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/src/features/inventory/providers/things_repository_provider.dart';

final brandProvider = StateProvider<String?>((ref) => null);

final descriptionProvider = StateProvider<String?>((ref) => null);

final hiddenProvider = StateProvider<bool?>((ref) => null);

final estimatedValueProvider = StateProvider<double?>((ref) => null);

final conditionProvider = StateProvider<String?>((ref) => null);

final unsavedChangesProvider = Provider<bool>((ref) {
return ref.watch(brandProvider) != null ||
ref.watch(descriptionProvider) != null ||
ref.watch(hiddenProvider) != null ||
ref.watch(estimatedValueProvider) != null ||
ref.watch(conditionProvider) != null;
});

class ItemDetailsEditor {
ItemDetailsEditor(this.ref);

final ProviderRef ref;

Future<void> save(String id) async {
await ref.read(thingsRepositoryProvider.notifier).updateItem(id,
brand: ref.read(brandProvider),
condition: ref.read(conditionProvider),
description: ref.read(descriptionProvider),
estimatedValue: ref.read(estimatedValueProvider),
hidden: ref.read(hiddenProvider));
discardChanges();
}

void discardChanges() {
ref.read(brandProvider.notifier).state = null;
ref.read(conditionProvider.notifier).state = null;
ref.read(descriptionProvider.notifier).state = null;
ref.read(estimatedValueProvider.notifier).state = null;
ref.read(hiddenProvider.notifier).state = null;
}
}

final itemDetailsEditorProvider = Provider((ref) => ItemDetailsEditor(ref));
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/src/features/common/widgets/checkbox_field.dart';
import 'package:librarian_app/src/features/common/widgets/input_decoration.widget.dart';
import 'package:librarian_app/src/features/inventory/models/updated_image_model.dart';
import 'package:librarian_app/src/features/inventory/pages/item_details_page.dart';
import 'package:librarian_app/src/features/inventory/providers/edited_thing_details_providers.dart';
import 'package:librarian_app/src/features/inventory/providers/selected_thing_provider.dart';
import 'package:librarian_app/src/features/inventory/providers/thing_details_provider.dart';
import 'package:librarian_app/src/features/inventory/providers/things_repository_provider.dart';
import 'package:librarian_app/src/features/inventory/widgets/dialogs/add_inventory_dialog.dart';
import 'package:librarian_app/src/features/inventory/widgets/inventory_details/categories_card.dart';
import 'package:librarian_app/src/features/inventory/widgets/inventory_details/items_card/item_details_dialog.dart';
import 'package:librarian_app/src/features/inventory/widgets/inventory_details/items_card/items_card.dart';
import 'package:librarian_app/src/features/inventory/widgets/inventory_details/thing_image_card/thing_image_card.dart';
import 'package:librarian_app/src/utils/media_query.dart';

class InventoryDetails extends ConsumerWidget {
const InventoryDetails({super.key});
Expand Down Expand Up @@ -92,6 +95,28 @@ class InventoryDetails extends ConsumerWidget {
ItemsCard(
items: details.items,
availableItemsCount: details.available,
onTap: (item) {
if (isMobile(context)) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return ItemDetailsPage(
item: item,
hiddenLocked: details.hidden,
);
}));
return;
}

showDialog(
context: context,
builder: (context) {
return ItemDetailsDialog(
item: item,
hiddenLocked: details.hidden,
);
},
);
},
onAddItemsPressed: () {
showDialog(
context: context,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/src/features/common/widgets/checkbox_field.dart';
import 'package:librarian_app/src/features/common/widgets/input_decoration.widget.dart';
import 'package:librarian_app/src/features/inventory/models/item_model.dart';
import 'package:librarian_app/src/features/inventory/providers/edited_item_details_providers.dart';
import 'package:librarian_app/src/utils/format.dart';

class ItemDetails extends ConsumerWidget {
const ItemDetails({
super.key,
required this.item,
required this.hiddenLocked,
});

final ItemModel item;
final bool hiddenLocked;

@override
Widget build(BuildContext context, WidgetRef ref) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Builder(
builder: (context) {
final checkbox = CheckboxField(
title: 'Hidden',
value: ref.watch(hiddenProvider) ?? item.hidden,
onChanged: hiddenLocked
? null
: (value) {
ref.read(hiddenProvider.notifier).state = value;
},
);

if (!hiddenLocked) {
return checkbox;
}

return Tooltip(
message: 'Unable to unhide because the thing is hidden.',
child: checkbox,
);
},
),
const SizedBox(height: 24),
TextFormField(
controller: TextEditingController(text: item.brand),
decoration: inputDecoration.copyWith(
labelText: 'Brand',
hintText: 'Generic',
),
onChanged: (value) {
ref.read(brandProvider.notifier).state = value;
},
),
const SizedBox(height: 16),
TextFormField(
controller: TextEditingController(text: item.description),
decoration: inputDecoration.copyWith(labelText: 'Description'),
onChanged: (value) {
ref.read(descriptionProvider.notifier).state = value;
},
),
const SizedBox(height: 16),
TextFormField(
controller:
TextEditingController(text: formatNumber(item.estimatedValue)),
decoration: inputDecoration.copyWith(
labelText: 'Estimated Value (\$)',
prefixText: '\$ ',
),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
onChanged: (value) {
ref.read(estimatedValueProvider.notifier).state =
double.tryParse(value);
},
),
const SizedBox(height: 16),
DropdownButtonFormField(
decoration: inputDecoration.copyWith(labelText: 'Condition'),
items: const [
DropdownMenuItem(
value: 'Like New',
child: Text('Like New'),
),
DropdownMenuItem(
value: 'Lightly Used',
child: Text('Lightly Used'),
),
DropdownMenuItem(
value: 'Heavily Used',
child: Text('Heavily Used'),
),
DropdownMenuItem(
value: 'Damaged',
child: Text('Damaged'),
),
],
onChanged: (value) {
ref.read(conditionProvider.notifier).state = value;
},
value: item.condition,
),
],
);
}
}
Loading

1 comment on commit df65408

@vercel
Copy link

@vercel vercel bot commented on df65408 Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

librarian-app – ./

librarian-app-pvdthings.vercel.app
librarian-app.vercel.app
librarian-app-git-main-pvdthings.vercel.app

Please sign in to comment.