diff --git a/lib/api/apis/basket_api.dart b/lib/api/apis/basket_api.dart index 3a6d6ab..3b73f79 100644 --- a/lib/api/apis/basket_api.dart +++ b/lib/api/apis/basket_api.dart @@ -12,17 +12,20 @@ class BasketApi { .get(); } - Future createBasket({required final String name}) async { - return _db - .into(_db.basketTable) - .insert(BasketTableCompanion.insert(name: name)); + Future createBasket( + {required final String name, required final String? description}) async { + return _db.into(_db.basketTable).insert(BasketTableCompanion.insert( + name: name, description: Value(description))); } - Future updateName( - {required final int id, required final String name}) async { + Future updateBasket( + {required final int id, + required final String name, + required final String? description}) async { return (_db.update(_db.basketTable)..where((t) => t.id.equals(id))) .write(BasketTableCompanion( name: Value(name), + description: Value(description), )); } diff --git a/lib/presentation/baskets_presenter.dart b/lib/presentation/baskets_presenter.dart index 4009d3e..7745f8b 100644 --- a/lib/presentation/baskets_presenter.dart +++ b/lib/presentation/baskets_presenter.dart @@ -22,14 +22,6 @@ class BasketsPresenter extends Presenter { })); } - void createBasket({required final String name}) { - _basketApi.createBasket(name: name).then((_) => fetchBaskets()); - } - - void updateBasketName({required final int id, required final String name}) { - _basketApi.updateName(id: id, name: name).then((_) => fetchBaskets()); - } - void deleteBasket({required final int id}) { _basketApi.deleteBasket(id: id).then((_) => fetchBaskets()); } diff --git a/lib/presentation/create_basket_presenter.dart b/lib/presentation/create_basket_presenter.dart new file mode 100644 index 0000000..7634a0f --- /dev/null +++ b/lib/presentation/create_basket_presenter.dart @@ -0,0 +1,59 @@ +import 'package:wealth_wave/api/apis/basket_api.dart'; +import 'package:wealth_wave/core/presenter.dart'; +import 'package:wealth_wave/core/single_event.dart'; +import 'package:wealth_wave/domain/models/basket.dart'; + +class CreateBasketPresenter extends Presenter { + final BasketApi _basketApi; + + CreateBasketPresenter({final BasketApi? basketApi}) + : _basketApi = basketApi ?? BasketApi(), + super(CreateBasketViewState()); + + void createBasket({final int? basketId}) { + var viewState = getViewState(); + + if (!viewState.isValid()) { + return; + } + + final name = viewState.name; + final description = viewState.description; + + if (basketId != null) { + _basketApi + .updateBasket(id: basketId, name: name, description: description) + .then((_) => updateViewState( + (viewState) => viewState.onBasketCreated = SingleEvent(null))); + } else { + _basketApi.createBasket(name: name, description: description).then((_) => + updateViewState( + (viewState) => viewState.onBasketCreated = SingleEvent(null))); + } + } + + void onNameChanged(String text) { + updateViewState((viewState) => viewState.name = text); + } + + void onDescriptionChanged(String text) { + updateViewState((viewState) => viewState.description = text); + } + + void setBasket(Basket basketToUpdate) { + updateViewState((viewState) { + viewState.name = basketToUpdate.name; + viewState.description = basketToUpdate.description; + }); + } +} + +class CreateBasketViewState { + String name = ''; + String? description; + SingleEvent? onBasketCreated; + + bool isValid() { + return name.isNotEmpty; + } +} diff --git a/lib/ui/pages/baskets_page.dart b/lib/ui/pages/baskets_page.dart index cc18e68..49b96bf 100644 --- a/lib/ui/pages/baskets_page.dart +++ b/lib/ui/pages/baskets_page.dart @@ -3,6 +3,7 @@ import 'package:wealth_wave/core/page_state.dart'; import 'package:wealth_wave/domain/models/basket.dart'; import 'package:wealth_wave/presentation/baskets_presenter.dart'; import 'package:wealth_wave/ui/app_dimen.dart'; +import 'package:wealth_wave/ui/widgets/create_basket_dialog.dart'; import 'package:wealth_wave/utils/ui_utils.dart'; class BasketsPage extends StatefulWidget { @@ -35,10 +36,8 @@ class _BasketsPage )), floatingActionButton: FloatingActionButton( onPressed: () { - _showBasketNameDialog(context: context).then((value) { - if (value != null) { - presenter.createBasket(name: value); - } + showCreateBasketDialog(context: context).then((value) { + presenter.fetchBaskets(); }); }, tooltip: 'Add', @@ -52,49 +51,6 @@ class _BasketsPage return BasketsPresenter(); } - final _textFieldController = TextEditingController(); - - Future _showBasketNameDialog( - {required final BuildContext context, final String? name}) async { - if (name != null) { - _textFieldController.text = name; - } - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Add Basket', - style: Theme.of(context).textTheme.titleMedium), - content: Padding( - padding: const EdgeInsets.all(AppDimen.minPadding), - child: TextFormField( - controller: _textFieldController, - decoration: const InputDecoration( - labelText: "Basket Name", border: OutlineInputBorder()), - )), - actions: [ - ElevatedButton( - child: const Text("Cancel"), - onPressed: () { - _textFieldController.clear(); - Navigator.pop(context); - }), - ElevatedButton( - onPressed: () { - var basketName = _textFieldController.text; - _textFieldController.clear(); - if (basketName.isEmpty) { - return; - } - Navigator.pop(context, basketName); - }, - child: - name == null ? const Text('Add') : const Text('Update')), - ], - ); - }); - } - Widget _basketWidget( {required final BuildContext context, required final Basket basket}) { return Card( @@ -114,8 +70,8 @@ class _BasketsPage PopupMenuButton( onSelected: (value) { if (value == 1) { - _showBasketNameDialog( - name: basket.name, context: context) + showCreateBasketDialog( + basketToUpdate: basket, context: context) .then((value) => presenter.fetchBaskets()); } else if (value == 2) { presenter.deleteBasket(id: basket.id); @@ -136,6 +92,15 @@ class _BasketsPage ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + basket.description != null + ? Text(basket.description!, + style: Theme.of(context).textTheme.bodyMedium) + : Container(), + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/ui/widgets/create_basket_dialog.dart b/lib/ui/widgets/create_basket_dialog.dart new file mode 100644 index 0000000..5630b2e --- /dev/null +++ b/lib/ui/widgets/create_basket_dialog.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:wealth_wave/core/page_state.dart'; +import 'package:wealth_wave/domain/models/basket.dart'; +import 'package:wealth_wave/presentation/create_basket_presenter.dart'; +import 'package:wealth_wave/ui/app_dimen.dart'; + +Future showCreateBasketDialog( + {required final BuildContext context, final Basket? basketToUpdate}) { + return showDialog( + context: context, + builder: (context) => + _CreateBasketDialog(basketToUpdate: basketToUpdate)); +} + +class _CreateBasketDialog extends StatefulWidget { + final Basket? basketToUpdate; + + const _CreateBasketDialog({this.basketToUpdate}); + + @override + State<_CreateBasketDialog> createState() => _CreateBasketPage(); +} + +class _CreateBasketPage extends PageState { + final _nameController = TextEditingController(); + final _descriptionController = TextEditingController(); + + @override + void initState() { + super.initState(); + + Basket? basketToUpdate = widget.basketToUpdate; + if (basketToUpdate != null) { + _nameController.text = basketToUpdate.name; + _descriptionController.text = basketToUpdate.description ?? ''; + presenter.setBasket(basketToUpdate); + } + + _nameController.addListener(() { + presenter.onNameChanged(_nameController.text); + }); + + _descriptionController.addListener(() { + presenter.onDescriptionChanged(_descriptionController.text); + }); + } + + @override + Widget buildWidget(BuildContext context, CreateBasketViewState snapshot) { + WidgetsBinding.instance.addPostFrameCallback((_) { + snapshot.onBasketCreated?.consume((_) { + Navigator.of(context).pop(); + }); + }); + + return AlertDialog( + title: Text('Create Basket', + style: Theme.of(context).textTheme.titleMedium), + actions: [ + ElevatedButton( + child: const Text("Cancel"), + onPressed: () { + Navigator.pop(context); + }), + ElevatedButton( + onPressed: snapshot.isValid() + ? () { + presenter.createBasket(basketId: widget.basketToUpdate?.id); + } + : null, + child: widget.basketToUpdate != null + ? const Text('Update') + : const Text('Create'), + ), + ], + content: SingleChildScrollView( + child: Column(children: [ + TextFormField( + textInputAction: TextInputAction.next, + controller: _nameController, + decoration: const InputDecoration( + labelText: 'Name', border: OutlineInputBorder()), + ), + const SizedBox(height: AppDimen.minPadding), + TextFormField( + textInputAction: TextInputAction.next, + controller: _descriptionController, + decoration: const InputDecoration( + labelText: 'Description', border: OutlineInputBorder()), + ), + ]), + )); + } + + @override + CreateBasketPresenter initializePresenter() { + return CreateBasketPresenter(); + } +}