Skip to content

Commit

Permalink
Merge pull request #11 from pvdthings/safeguard-duplicate-thing
Browse files Browse the repository at this point in the history
Librarian: Safeguard duplicate thing
  • Loading branch information
dillonfagan committed May 21, 2024
2 parents 2280457 + fc8ca96 commit 1b2d765
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import 'package:librarian_app/src/features/borrowers/widgets/borrowers_list/sear
import 'package:librarian_app/src/features/borrowers/widgets/needs_attention_view.dart';
import 'package:librarian_app/src/features/dashboard/providers/end_drawer_provider.dart';
import 'package:librarian_app/src/features/dashboard/widgets/create_menu_item.dart';
import 'package:librarian_app/src/features/inventory/providers/things_repository_provider.dart';
import 'package:librarian_app/src/features/inventory/widgets/layouts/inventory_desktop_layout.dart';
import 'package:librarian_app/src/features/inventory/pages/inventory_details_page.dart';
import 'package:librarian_app/src/features/inventory/widgets/inventory_list/searchable_inventory_list.dart';
Expand Down Expand Up @@ -136,23 +135,7 @@ class _DashboardPageState extends ConsumerState<DashboardPage> {
await Future.delayed(const Duration(milliseconds: 150), () {
showDialog(
context: context,
builder: (context) {
return CreateThingDialog(
onCreate: (name, spanishName) {
ref
.read(thingsRepositoryProvider.notifier)
.createThing(name: name, spanishName: spanishName)
.then((value) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${value.name} created'),
),
);
});
},
);
},
builder: (context) => const CreateThingDialog(),
);
});
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/src/features/inventory/providers/things_repository_provider.dart';

import '../models/thing_model.dart';

Provider<Future<List<ThingModel>>> findThingsByName(String name) {
return Provider((ref) async => await ref
.read(thingsRepositoryProvider.notifier)
.getThings(filter: name));
}
Original file line number Diff line number Diff line change
@@ -1,94 +1,167 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/src/features/inventory/providers/find_things_by_name.dart';
import 'package:librarian_app/src/widgets/input_decoration.dart';

class CreateThingDialog extends StatelessWidget {
CreateThingDialog({super.key, this.onCreate});
import '../../models/thing_model.dart';
import '../../providers/things_repository_provider.dart';

class CreateThingDialog extends ConsumerStatefulWidget {
const CreateThingDialog({super.key});

@override
ConsumerState<ConsumerStatefulWidget> createState() {
return _CreateThingDialogState();
}
}

class _CreateThingDialogState extends ConsumerState<CreateThingDialog> {
final _formKey = GlobalKey<FormState>();
final void Function(String name, String? spanishName)? onCreate;

final _name = TextEditingController();
final _spanishName = TextEditingController();

Future<List<ThingModel>>? existingMatches;

createThing() {
ref
.read(thingsRepositoryProvider.notifier)
.createThing(name: _name.text, spanishName: _spanishName.text)
.then((value) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('${value.name} created'),
),
);
});
}

@override
Widget build(BuildContext context) {
return Dialog(
child: Container(
padding: const EdgeInsets.all(16),
child: IntrinsicWidth(
child: Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'New Thing',
style: Theme.of(context).textTheme.headlineMedium,
),
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.close),
),
],
),
const SizedBox(height: 16),
TextFormField(
controller: _name,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
return AlertDialog(
icon: const Icon(Icons.build),
title: const Text('Create New Thing'),
actions: [
OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
ListenableBuilder(
listenable: _name,
builder: (context, child) => FilledButton(
onPressed: _name.text.isNotEmpty
? () {
if (_formKey.currentState!.validate()) {
createThing();
}
}
: null,
child: const Text('Create'),
),
),
],
contentPadding: const EdgeInsets.all(16),
content: SizedBox(
width: 500,
child: Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _name,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
}

return null;
},
decoration: inputDecoration.copyWith(
labelText: 'Name',
icon: const Icon(Icons.build),
constraints: const BoxConstraints(minWidth: 500),
),
),
const SizedBox(height: 16),
TextFormField(
controller: _spanishName,
decoration: inputDecoration.copyWith(
labelText: 'Name (Spanish)',
icon: const Icon(Icons.build),
constraints: const BoxConstraints(minWidth: 500),
),
return null;
},
decoration: inputDecoration.copyWith(
labelText: 'Name',
constraints: const BoxConstraints(minWidth: 500),
),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
OutlinedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
const SizedBox(width: 16),
FilledButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
onCreate?.call(_name.text, _spanishName.text);
}
},
child: const Text('Create'),
),
],
onChanged: (value) {
if (value.isEmpty || value.length < 4) {
return;
}

setState(() {
existingMatches = ref.read(findThingsByName(value));
});
},
),
const SizedBox(height: 16),
TextFormField(
controller: _spanishName,
decoration: inputDecoration.copyWith(
labelText: 'Name (Spanish)',
constraints: const BoxConstraints(minWidth: 500),
),
],
),
),
const SizedBox(height: 16),
FutureBuilder(
future: existingMatches,
builder: (context, snapshot) {
final existingThingName = snapshot.data?.firstOrNull?.name;
if (existingThingName != null) {
return _ExistingThingWarning(
thingName: _name.text,
existingThingName: existingThingName,
);
}

return const SizedBox.shrink();
},
),
],
),
),
),
);
}
}

class _ExistingThingWarning extends StatelessWidget {
const _ExistingThingWarning({
required this.thingName,
required this.existingThingName,
});

final String thingName;
final String existingThingName;

@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.zero,
child: ListTile(
leading: const Icon(
Icons.warning_rounded,
color: Colors.amber,
),
title: const Text('Thing Already Exists'),
subtitle: Text.rich(
TextSpan(
children: [
const TextSpan(text: 'A thing named '),
TextSpan(text: thingName, style: boldTextStyle),
const TextSpan(text: ' might already exist.\n'),
const TextSpan(text: 'Is '),
TextSpan(text: existingThingName, style: boldTextStyle),
const TextSpan(text: ' the same thing?'),
],
),
),
),
);
}
}

const boldTextStyle = TextStyle(fontWeight: FontWeight.bold);

0 comments on commit 1b2d765

Please sign in to comment.