From 9c55ef61d9c0880a3a4fdb9c8a4ae7198d4de8f1 Mon Sep 17 00:00:00 2001 From: RedCommander735 <40245965+RedCommander735@users.noreply.github.com> Date: Thu, 23 May 2024 22:25:42 +0200 Subject: [PATCH 1/5] feat: items highlightable --- lib/src/listview/item_list_model.dart | 21 ++- lib/src/listview/item_list_view.dart | 5 +- lib/src/listview/list_item.dart | 212 +++++++++++++------------- lib/src/utils/sqlite_service.dart | 4 +- 4 files changed, 126 insertions(+), 116 deletions(-) diff --git a/lib/src/listview/item_list_model.dart b/lib/src/listview/item_list_model.dart index 8dae44a..13fa00b 100644 --- a/lib/src/listview/item_list_model.dart +++ b/lib/src/listview/item_list_model.dart @@ -7,7 +7,8 @@ class ItemListModel extends ChangeNotifier { List _items = []; int _hiddenEntries = 0; - + bool multiselect = false; + int selected = 0; UnmodifiableListView get items => UnmodifiableListView(_items); int get hiddenEntries => _hiddenEntries; @@ -30,7 +31,6 @@ class ItemListModel extends ChangeNotifier { notifyListeners(); } - void add(ListItem item) { final sqlitesevice = SqliteService(); sqlitesevice.createItem(item); @@ -39,7 +39,6 @@ class ItemListModel extends ChangeNotifier { notifyListeners(); } - void remove(ListItem item) { final sqlitesevice = SqliteService(); sqlitesevice.deleteItem(item.id); @@ -47,4 +46,20 @@ class ItemListModel extends ChangeNotifier { notifyListeners(); } + + void select(ListItem item) { + item.selected = !item.selected; + + if (item.selected) { + selected += 1; + } else { + selected -= 1; + } + + if (selected < 1) { + multiselect = false; + } + + notifyListeners(); + } } diff --git a/lib/src/listview/item_list_view.dart b/lib/src/listview/item_list_view.dart index ac2319f..733752c 100644 --- a/lib/src/listview/item_list_view.dart +++ b/lib/src/listview/item_list_view.dart @@ -20,10 +20,7 @@ class ItemListViewState extends State { itemBuilder: (context, index) { return ListEntry(item: items[index]); }, - separatorBuilder: (context, build) => const Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Divider(height: 1), - ), + separatorBuilder: (context, build) => const Divider(height: 1), ); }); } diff --git a/lib/src/listview/list_item.dart b/lib/src/listview/list_item.dart index 5177c4f..fff9e53 100644 --- a/lib/src/listview/list_item.dart +++ b/lib/src/listview/list_item.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import 'package:spritverbrauch/src/components/sp_compound_icon.dart'; import 'package:spritverbrauch/src/components/sp_price_text.dart'; import 'package:spritverbrauch/src/listview/item_list_model.dart'; +import 'package:spritverbrauch/src/settings/filter_model.dart'; import 'package:spritverbrauch/src/utils/sqlite_service.dart'; double roundDouble(double value, int places) { @@ -24,6 +25,7 @@ class ListEntry extends StatefulWidget { class _ListEntryState extends State { late ListItem item; + @override void initState() { super.initState(); @@ -34,145 +36,139 @@ class _ListEntryState extends State { @override Widget build(BuildContext context) { - var date = DateTime.fromMillisecondsSinceEpoch(widget.item.date); - var day = date.day.toString().padLeft(2, '0'); - var month = date.month.toString().padLeft(2, '0'); - var year = date.year.toString(); + DateTime date = DateTime.fromMillisecondsSinceEpoch(item.date); + String day = date.day.toString().padLeft(2, '0'); + String month = date.month.toString().padLeft(2, '0'); + String year = date.year.toString(); String locale = Intl.systemLocale; - var formatter = NumberFormat.decimalPatternDigits(decimalDigits: 2, locale: locale); + NumberFormat formatter = NumberFormat.decimalPatternDigits(decimalDigits: 2, locale: locale); + + String litersPerKilometer = formatter.format(item.litersPerKilometer); - var litersPerKilometer = formatter.format(widget.item.litersPerKilometer); + double price = item.priceTotal; - var price = widget.item.priceTotal; + String distance = formatter.format(item.distance); - var distance = formatter.format(widget.item.distance); + String fuel = formatter.format(item.fuelInLiters); - var fuel = formatter.format(widget.item.fuelInLiters); + double pricePerLiter = item.pricePerLiter; - var pricePerLiter = widget.item.pricePerLiter; + bool highlighted = item.selected; return DefaultTextStyle( style: TextStyle(fontSize: 16, color: Theme.of(context).colorScheme.onBackground), child: InkWell( onLongPress: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Eintrag löschen?'), - content: const Text('Diesen Eintrag wirklich löschen?'), - actions: [ - TextButton( - child: const Text('Abbrechen'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Bestätigen'), - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Eintrag entfernt"), - showCloseIcon: true, - )); - - Provider.of(context, listen: false).remove(item); - - Navigator.of(context).pop(); - }, - ), - ], - ), - ); + bool multiselect = Provider.of(context, listen: false).multiselect; + if (!multiselect) { + Provider.of(context, listen: false).multiselect = true; + Provider.of(context, listen: false).select(item); + } else { + Provider.of(context, listen: false).select(item); + } }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 15), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 7), - child: Row( - children: [ - Expanded( + onTap: () { + bool multiselect = Provider.of(context, listen: false).multiselect; + if (multiselect) { + Provider.of(context, listen: false).select(item); + } + }, + splashColor: (highlighted) ? Colors.transparent : null, + child: Container( + color: (highlighted) ? Color.lerp(Colors.white, Colors.transparent, 0.55) : Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 15), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 7), + child: Row( + children: [ + Expanded( child: Row( - children: [ - const Icon(Icons.date_range_rounded), - const SizedBox(width: 2), - Text("$day.$month.$year"), - ], - )), - Expanded( + children: [ + const Icon(Icons.date_range_rounded), + const SizedBox(width: 2), + Text("$day.$month.$year"), + ], + ), + ), + Expanded( child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - CompoundIcon( - firstIcon: Icons.local_gas_station_rounded, - secondIcon: Icons.route_rounded, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CompoundIcon( + firstIcon: Icons.local_gas_station_rounded, + secondIcon: Icons.route_rounded, + ), + const SizedBox(width: 2), + Text("$litersPerKilometer l/km"), + ], ), - const SizedBox(width: 2), - Text("$litersPerKilometer l/km"), - ], + ), ), - )), - Expanded( + Expanded( child: Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.euro_rounded), - const SizedBox(width: 2), - SPPriceText(value: price, unit: '€'), - ], + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.euro_rounded), + const SizedBox(width: 2), + SPPriceText(value: price, unit: '€'), + ], + ), + ), ), - )), - ], + ], + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 7), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - const Icon(Icons.route_rounded), - const SizedBox(width: 2), - Text("$distance km"), - ], - )), - Expanded( - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, + Padding( + padding: const EdgeInsets.symmetric(vertical: 7), + child: Row( + children: [ + Expanded( + child: Row( children: [ - const Icon(Icons.local_gas_station_rounded), + const Icon(Icons.route_rounded), const SizedBox(width: 2), - Text("$fuel l"), + Text("$distance km"), ], - ), - )), - Expanded( - child: Align( - alignment: Alignment.centerRight, + )), + Expanded( + child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ - CompoundIcon( - firstIcon: Icons.euro_rounded, - secondIcon: Icons.local_gas_station_rounded, - ), + const Icon(Icons.local_gas_station_rounded), const SizedBox(width: 2), - SPPriceText(value: pricePerLiter, unit: '€'), + Text("$fuel l"), ], ), + )), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CompoundIcon( + firstIcon: Icons.euro_rounded, + secondIcon: Icons.local_gas_station_rounded, + ), + const SizedBox(width: 2), + SPPriceText(value: pricePerLiter, unit: '€'), + ], + ), + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/src/utils/sqlite_service.dart b/lib/src/utils/sqlite_service.dart index 375cd4c..c09e084 100644 --- a/lib/src/utils/sqlite_service.dart +++ b/lib/src/utils/sqlite_service.dart @@ -84,7 +84,9 @@ class ListItem { final double pricePerLiter; final double litersPerKilometer; - const ListItem({ + bool selected = false; + + ListItem({ required this.id, required this.date, required this.distance, From 3785f9dbb75286322edb2e7ef54eec30d7ca0ef3 Mon Sep 17 00:00:00 2001 From: RedCommander735 <40245965+RedCommander735@users.noreply.github.com> Date: Thu, 23 May 2024 22:25:42 +0200 Subject: [PATCH 2/5] feat: items highlightable --- lib/src/listview/item_list_model.dart | 20 ++- lib/src/listview/item_list_view.dart | 5 +- lib/src/listview/list_item.dart | 212 +++++++++++++------------- lib/src/utils/sqlite_service.dart | 4 +- 4 files changed, 126 insertions(+), 115 deletions(-) diff --git a/lib/src/listview/item_list_model.dart b/lib/src/listview/item_list_model.dart index f4d5e41..6483155 100644 --- a/lib/src/listview/item_list_model.dart +++ b/lib/src/listview/item_list_model.dart @@ -10,6 +10,8 @@ class ItemListModel extends ChangeNotifier { DateTime? _start; DateTime? _end; + bool multiselect = false; + int selected = 0; UnmodifiableListView get items => UnmodifiableListView(_items); int get hiddenEntries => _hiddenEntries; @@ -34,7 +36,6 @@ class ItemListModel extends ChangeNotifier { notifyListeners(); } - void add(ListItem item) { final sqlitesevice = SqliteService(); sqlitesevice.createItem(item); @@ -43,10 +44,25 @@ class ItemListModel extends ChangeNotifier { notifyListeners(); } - void remove(ListItem item) { final sqlitesevice = SqliteService(); sqlitesevice.deleteItem(item.id).then((value) => { if (value > 0) {loadFiltered(start: _start, end: _end)}}); } + + void select(ListItem item) { + item.selected = !item.selected; + + if (item.selected) { + selected += 1; + } else { + selected -= 1; + } + + if (selected < 1) { + multiselect = false; + } + + notifyListeners(); + } } diff --git a/lib/src/listview/item_list_view.dart b/lib/src/listview/item_list_view.dart index eca2fca..37d7c8c 100644 --- a/lib/src/listview/item_list_view.dart +++ b/lib/src/listview/item_list_view.dart @@ -16,10 +16,7 @@ class ItemListView extends StatelessWidget { itemBuilder: (context, index) { return ListEntry(item: items[index]); }, - separatorBuilder: (context, build) => const Padding( - padding: EdgeInsets.symmetric(horizontal: 8), - child: Divider(height: 1), - ), + separatorBuilder: (context, build) => const Divider(height: 1), ); }); } diff --git a/lib/src/listview/list_item.dart b/lib/src/listview/list_item.dart index 5177c4f..fff9e53 100644 --- a/lib/src/listview/list_item.dart +++ b/lib/src/listview/list_item.dart @@ -6,6 +6,7 @@ import 'package:provider/provider.dart'; import 'package:spritverbrauch/src/components/sp_compound_icon.dart'; import 'package:spritverbrauch/src/components/sp_price_text.dart'; import 'package:spritverbrauch/src/listview/item_list_model.dart'; +import 'package:spritverbrauch/src/settings/filter_model.dart'; import 'package:spritverbrauch/src/utils/sqlite_service.dart'; double roundDouble(double value, int places) { @@ -24,6 +25,7 @@ class ListEntry extends StatefulWidget { class _ListEntryState extends State { late ListItem item; + @override void initState() { super.initState(); @@ -34,145 +36,139 @@ class _ListEntryState extends State { @override Widget build(BuildContext context) { - var date = DateTime.fromMillisecondsSinceEpoch(widget.item.date); - var day = date.day.toString().padLeft(2, '0'); - var month = date.month.toString().padLeft(2, '0'); - var year = date.year.toString(); + DateTime date = DateTime.fromMillisecondsSinceEpoch(item.date); + String day = date.day.toString().padLeft(2, '0'); + String month = date.month.toString().padLeft(2, '0'); + String year = date.year.toString(); String locale = Intl.systemLocale; - var formatter = NumberFormat.decimalPatternDigits(decimalDigits: 2, locale: locale); + NumberFormat formatter = NumberFormat.decimalPatternDigits(decimalDigits: 2, locale: locale); + + String litersPerKilometer = formatter.format(item.litersPerKilometer); - var litersPerKilometer = formatter.format(widget.item.litersPerKilometer); + double price = item.priceTotal; - var price = widget.item.priceTotal; + String distance = formatter.format(item.distance); - var distance = formatter.format(widget.item.distance); + String fuel = formatter.format(item.fuelInLiters); - var fuel = formatter.format(widget.item.fuelInLiters); + double pricePerLiter = item.pricePerLiter; - var pricePerLiter = widget.item.pricePerLiter; + bool highlighted = item.selected; return DefaultTextStyle( style: TextStyle(fontSize: 16, color: Theme.of(context).colorScheme.onBackground), child: InkWell( onLongPress: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Eintrag löschen?'), - content: const Text('Diesen Eintrag wirklich löschen?'), - actions: [ - TextButton( - child: const Text('Abbrechen'), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - TextButton( - child: const Text('Bestätigen'), - onPressed: () { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Eintrag entfernt"), - showCloseIcon: true, - )); - - Provider.of(context, listen: false).remove(item); - - Navigator.of(context).pop(); - }, - ), - ], - ), - ); + bool multiselect = Provider.of(context, listen: false).multiselect; + if (!multiselect) { + Provider.of(context, listen: false).multiselect = true; + Provider.of(context, listen: false).select(item); + } else { + Provider.of(context, listen: false).select(item); + } }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 15), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 7), - child: Row( - children: [ - Expanded( + onTap: () { + bool multiselect = Provider.of(context, listen: false).multiselect; + if (multiselect) { + Provider.of(context, listen: false).select(item); + } + }, + splashColor: (highlighted) ? Colors.transparent : null, + child: Container( + color: (highlighted) ? Color.lerp(Colors.white, Colors.transparent, 0.55) : Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 15), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 7), + child: Row( + children: [ + Expanded( child: Row( - children: [ - const Icon(Icons.date_range_rounded), - const SizedBox(width: 2), - Text("$day.$month.$year"), - ], - )), - Expanded( + children: [ + const Icon(Icons.date_range_rounded), + const SizedBox(width: 2), + Text("$day.$month.$year"), + ], + ), + ), + Expanded( child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - CompoundIcon( - firstIcon: Icons.local_gas_station_rounded, - secondIcon: Icons.route_rounded, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CompoundIcon( + firstIcon: Icons.local_gas_station_rounded, + secondIcon: Icons.route_rounded, + ), + const SizedBox(width: 2), + Text("$litersPerKilometer l/km"), + ], ), - const SizedBox(width: 2), - Text("$litersPerKilometer l/km"), - ], + ), ), - )), - Expanded( + Expanded( child: Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.euro_rounded), - const SizedBox(width: 2), - SPPriceText(value: price, unit: '€'), - ], + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.euro_rounded), + const SizedBox(width: 2), + SPPriceText(value: price, unit: '€'), + ], + ), + ), ), - )), - ], + ], + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 7), - child: Row( - children: [ - Expanded( - child: Row( - children: [ - const Icon(Icons.route_rounded), - const SizedBox(width: 2), - Text("$distance km"), - ], - )), - Expanded( - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, + Padding( + padding: const EdgeInsets.symmetric(vertical: 7), + child: Row( + children: [ + Expanded( + child: Row( children: [ - const Icon(Icons.local_gas_station_rounded), + const Icon(Icons.route_rounded), const SizedBox(width: 2), - Text("$fuel l"), + Text("$distance km"), ], - ), - )), - Expanded( - child: Align( - alignment: Alignment.centerRight, + )), + Expanded( + child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ - CompoundIcon( - firstIcon: Icons.euro_rounded, - secondIcon: Icons.local_gas_station_rounded, - ), + const Icon(Icons.local_gas_station_rounded), const SizedBox(width: 2), - SPPriceText(value: pricePerLiter, unit: '€'), + Text("$fuel l"), ], ), + )), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CompoundIcon( + firstIcon: Icons.euro_rounded, + secondIcon: Icons.local_gas_station_rounded, + ), + const SizedBox(width: 2), + SPPriceText(value: pricePerLiter, unit: '€'), + ], + ), + ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/src/utils/sqlite_service.dart b/lib/src/utils/sqlite_service.dart index 9476089..fc84708 100644 --- a/lib/src/utils/sqlite_service.dart +++ b/lib/src/utils/sqlite_service.dart @@ -86,7 +86,9 @@ class ListItem { final double pricePerLiter; final double litersPerKilometer; - const ListItem({ + bool selected = false; + + ListItem({ required this.id, required this.date, required this.distance, From edbded18ea8205f31b513101f89b1c981cdb6f35 Mon Sep 17 00:00:00 2001 From: RedCommander735 <40245965+RedCommander735@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:18:05 +0200 Subject: [PATCH 3/5] feat: multidelete via speeddial --- lib/main.dart | 71 +++++++++++++++++----- lib/src/components/alert_dialog.dart | 43 ++++++++++++++ lib/src/listview/item_list_model.dart | 86 ++++++++++++++++++--------- lib/src/listview/item_list_view.dart | 3 +- lib/src/listview/list_item.dart | 23 +++---- pubspec.lock | 8 +++ pubspec.yaml | 1 + 7 files changed, 175 insertions(+), 60 deletions(-) create mode 100644 lib/src/components/alert_dialog.dart diff --git a/lib/main.dart b/lib/main.dart index 5752ce0..ec76938 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,12 +2,14 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_speed_dial/flutter_speed_dial.dart'; +import 'package:spritverbrauch/src/add_item.dart'; +import 'package:spritverbrauch/src/components/alert_dialog.dart'; import 'package:spritverbrauch/src/settings/filter_model.dart'; import 'package:spritverbrauch/src/listview/item_list_model.dart'; import 'package:spritverbrauch/src/listview/item_list_view.dart'; import 'package:spritverbrauch/src/overview.dart'; -import 'package:spritverbrauch/src/add_item.dart'; import 'package:spritverbrauch/src/settings/filter.dart'; import 'package:provider/provider.dart'; @@ -76,28 +78,69 @@ class _SpritpreiseState extends State { useMaterial3: true, ), themeMode: ThemeMode.system, - home: const Main(), + home: Main(), ); }); } } -class Main extends StatelessWidget { - const Main({super.key}); +class Main extends StatefulWidget { + Main({super.key}); + + @override + State
createState() => _MainState(); +} +class _MainState extends State
{ @override Widget build(BuildContext context) { + ItemListModel itemListModel = Provider.of(context, listen: false); return Scaffold( - floatingActionButton: Builder( - builder: (context) => FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const AddItem()), - ); - }, - )), + floatingActionButton: SpeedDial( + icon: Icons.add, + activeIcon: Icons.close, + openCloseDial: itemListModel.openCloseDial, + onPress: () { + if (!itemListModel.multiselect) { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const AddItem()), + ); + } + }, + onClose: () => itemListModel.multiselect = false, + renderOverlay: false, + spaceBetweenChildren: 4, + childPadding: const EdgeInsets.all(10), + spacing: 3, + children: [ + SpeedDialChild( + child: const Icon(Icons.delete_forever), + backgroundColor: Colors.red, + foregroundColor: Colors.white, + onTap: () { + String title = + itemListModel.selected > 1 ? '${itemListModel.selected} Eintrage löschen?' : 'Eintrag löschen?'; + + String content = itemListModel.selected > 1 + ? 'Willst du die ausgewählten Einträge wirklich löschen?' + : 'Willst du den ausgewählten Einträge wirklich löschen?'; + + Popup.showAlert( + context: context, + title: title, + content: content, + onConfirm: () { + itemListModel.remove(itemListModel.getSelected); + itemListModel.deselectAll(); + }, + onDeny: () { + } + ); + }, + shape: const CircleBorder()) + ], + ), body: SafeArea( child: DefaultTabController( length: 2, diff --git a/lib/src/components/alert_dialog.dart b/lib/src/components/alert_dialog.dart new file mode 100644 index 0000000..8c775f9 --- /dev/null +++ b/lib/src/components/alert_dialog.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class Popup { + static showAlert( + {required BuildContext context, + required String title, + required String content, + String? snackBarText, + required void Function() onConfirm, + void Function()? onDeny}) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + TextButton( + child: const Text('Abbrechen'), + onPressed: () { + if (onDeny != null) onDeny(); + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('Bestätigen'), + onPressed: () { + if (snackBarText != null) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(snackBarText), + showCloseIcon: true, + )); + } + + onConfirm(); + + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + } +} diff --git a/lib/src/listview/item_list_model.dart b/lib/src/listview/item_list_model.dart index 6483155..3214a93 100644 --- a/lib/src/listview/item_list_model.dart +++ b/lib/src/listview/item_list_model.dart @@ -1,68 +1,96 @@ import 'dart:collection'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:spritverbrauch/src/utils/sqlite_service.dart'; class ItemListModel extends ChangeNotifier { - + late SqliteService _sqliteService; + bool _sqliteInitialized = false; + List _items = []; int _hiddenEntries = 0; - DateTime? _start; - DateTime? _end; - bool multiselect = false; + bool _multiselect = false; + ValueNotifier openCloseDial = ValueNotifier(false); int selected = 0; UnmodifiableListView get items => UnmodifiableListView(_items); int get hiddenEntries => _hiddenEntries; + bool get multiselect => _multiselect; + + set multiselect(bool enabled) { + if (!enabled) { + deselectAll(); + } else { + openCloseDial.value = true; + } + _multiselect = enabled; + } + void load() async { - final sqlitesevice = SqliteService(); - final list = await sqlitesevice.getItems(); + if (!_sqliteInitialized) { + _sqliteService = SqliteService(); + _sqliteInitialized = true; + } + final list = await _sqliteService.getItems(); _items = list; - + notifyListeners(); } void loadFiltered({DateTime? start, DateTime? end}) async { - _start = start; - _end = end; - final sqlitesevice = SqliteService(); - final list = await sqlitesevice.getItems(); - final listFiltered = await sqlitesevice.getItemsFiltered(start ?? DateTime(1970), end ?? DateTime.now()); + final list = await _sqliteService.getItems(); + final listFiltered = await _sqliteService.getItemsFiltered(start ?? DateTime(1970), end ?? DateTime.now()); _items = listFiltered; _hiddenEntries = list.length - listFiltered.length; - + notifyListeners(); } void add(ListItem item) { - final sqlitesevice = SqliteService(); - sqlitesevice.createItem(item); + _sqliteService.createItem(item); _items.add(item); - + notifyListeners(); } - void remove(ListItem item) { - final sqlitesevice = SqliteService(); - sqlitesevice.deleteItem(item.id).then((value) => { - if (value > 0) {loadFiltered(start: _start, end: _end)}}); + void remove(List passedItems) { + for (var item in passedItems) { + _sqliteService.deleteItem(item.id); + _items.remove(item); + } } - void select(ListItem item) { - item.selected = !item.selected; + List get getSelected { + return UnmodifiableListView(_items.where((element) => element.selected)); + } - if (item.selected) { - selected += 1; - } else { - selected -= 1; - } + void select(List passedItems) { + for (var item in passedItems) { + item.selected = !item.selected; + + if (item.selected) { + selected += 1; + } else { + selected -= 1; + } - if (selected < 1) { - multiselect = false; + if (selected < 1) { + _multiselect = false; + openCloseDial.value = false; + } } notifyListeners(); } + + void deselectAll() { + for (ListItem item in _items) { + item.selected = false; + } + selected = 0; + notifyListeners(); + } } diff --git a/lib/src/listview/item_list_view.dart b/lib/src/listview/item_list_view.dart index 37d7c8c..4aa95c8 100644 --- a/lib/src/listview/item_list_view.dart +++ b/lib/src/listview/item_list_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:spritverbrauch/src/listview/item_list_model.dart'; import 'package:spritverbrauch/src/listview/list_item.dart'; -import 'package:spritverbrauch/src/utils/sqlite_service.dart'; class ItemListView extends StatelessWidget { const ItemListView({super.key}); @@ -14,7 +13,7 @@ class ItemListView extends StatelessWidget { return ListView.separated( itemCount: items.length, itemBuilder: (context, index) { - return ListEntry(item: items[index]); + return ListEntry(items: items, item: items[index]); }, separatorBuilder: (context, build) => const Divider(height: 1), ); diff --git a/lib/src/listview/list_item.dart b/lib/src/listview/list_item.dart index fff9e53..99f7618 100644 --- a/lib/src/listview/list_item.dart +++ b/lib/src/listview/list_item.dart @@ -6,7 +6,6 @@ import 'package:provider/provider.dart'; import 'package:spritverbrauch/src/components/sp_compound_icon.dart'; import 'package:spritverbrauch/src/components/sp_price_text.dart'; import 'package:spritverbrauch/src/listview/item_list_model.dart'; -import 'package:spritverbrauch/src/settings/filter_model.dart'; import 'package:spritverbrauch/src/utils/sqlite_service.dart'; double roundDouble(double value, int places) { @@ -16,26 +15,20 @@ double roundDouble(double value, int places) { class ListEntry extends StatefulWidget { final ListItem item; - const ListEntry({super.key, required this.item}); + final List items; + const ListEntry({super.key, required this.items, required this.item}); @override State createState() => _ListEntryState(); } class _ListEntryState extends State { - late ListItem item; - - - @override - void initState() { - super.initState(); - setState(() { - item = widget.item; - }); - } @override Widget build(BuildContext context) { + ListItem item = widget.item; + + DateTime date = DateTime.fromMillisecondsSinceEpoch(item.date); String day = date.day.toString().padLeft(2, '0'); String month = date.month.toString().padLeft(2, '0'); @@ -63,15 +56,15 @@ class _ListEntryState extends State { bool multiselect = Provider.of(context, listen: false).multiselect; if (!multiselect) { Provider.of(context, listen: false).multiselect = true; - Provider.of(context, listen: false).select(item); + Provider.of(context, listen: false).select([item]); } else { - Provider.of(context, listen: false).select(item); + Provider.of(context, listen: false).select([item]); } }, onTap: () { bool multiselect = Provider.of(context, listen: false).multiselect; if (multiselect) { - Provider.of(context, listen: false).select(item); + Provider.of(context, listen: false).select([item]); } }, splashColor: (highlighted) ? Colors.transparent : null, diff --git a/pubspec.lock b/pubspec.lock index 0c72c99..f5260c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -102,6 +102,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_speed_dial: + dependency: "direct main" + description: + name: flutter_speed_dial + sha256: "698a037274a66dbae8697c265440e6acb6ab6cae9ac5f95c749e7944d8f28d41" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index a2bf5a3..6bc2e09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: path_provider: ^2.1.3 csv: ^6.0.0 permission_handler: ^11.3.1 + flutter_speed_dial: ^7.0.0 dev_dependencies: flutter_test: From 2ba9321365d4987f1caf29c84b8ffb7114c8c0d5 Mon Sep 17 00:00:00 2001 From: RedCommander735 <40245965+RedCommander735@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:41:29 +0200 Subject: [PATCH 4/5] style: change text --- lib/main.dart | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ec76938..5603082 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -115,30 +115,24 @@ class _MainState extends State
{ spacing: 3, children: [ SpeedDialChild( - child: const Icon(Icons.delete_forever), - backgroundColor: Colors.red, - foregroundColor: Colors.white, - onTap: () { - String title = - itemListModel.selected > 1 ? '${itemListModel.selected} Eintrage löschen?' : 'Eintrag löschen?'; - - String content = itemListModel.selected > 1 - ? 'Willst du die ausgewählten Einträge wirklich löschen?' - : 'Willst du den ausgewählten Einträge wirklich löschen?'; - - Popup.showAlert( - context: context, - title: title, - content: content, - onConfirm: () { - itemListModel.remove(itemListModel.getSelected); - itemListModel.deselectAll(); - }, - onDeny: () { - } - ); - }, - shape: const CircleBorder()) + child: const Icon(Icons.delete_forever), + backgroundColor: Colors.red, + foregroundColor: Colors.white, + onTap: () { + Popup.showAlert( + context: context, + title: itemListModel.selected > 1 ? '${itemListModel.selected} Eintrage löschen?' : 'Eintrag löschen?', + content: itemListModel.selected > 1 + ? 'Bist du dir sicher, dass du die ausgewählten Einträge löschen willst?' + : 'Bist du dir sicher, dass du den ausgewählten Einträg löschen willst?', + onConfirm: () { + itemListModel.remove(List.from(itemListModel.getSelected)); + itemListModel.deselectAll(); + }, + ); + }, + shape: const CircleBorder(), + ) ], ), body: SafeArea( From e33dc790ff9df892d7f14397b894c3676d4dd975 Mon Sep 17 00:00:00 2001 From: RedCommander735 <40245965+RedCommander735@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:42:06 +0200 Subject: [PATCH 5/5] refactor: move code --- lib/main.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5603082..49a18ad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -100,6 +100,11 @@ class _MainState extends State
{ icon: Icons.add, activeIcon: Icons.close, openCloseDial: itemListModel.openCloseDial, + closeManually: true, + renderOverlay: false, + spaceBetweenChildren: 4, + childPadding: const EdgeInsets.all(10), + spacing: 3, onPress: () { if (!itemListModel.multiselect) { Navigator.push( @@ -109,10 +114,6 @@ class _MainState extends State
{ } }, onClose: () => itemListModel.multiselect = false, - renderOverlay: false, - spaceBetweenChildren: 4, - childPadding: const EdgeInsets.all(10), - spacing: 3, children: [ SpeedDialChild( child: const Icon(Icons.delete_forever),