Initial and done prolly
This commit is contained in:
commit
6f88b9966f
175 changed files with 15445 additions and 0 deletions
105
lib/pages/main/widgets/product_widget.dart
Normal file
105
lib/pages/main/widgets/product_widget.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:groceries_manager/common/icons.dart';
|
||||
import 'package:groceries_manager/db/database.dart';
|
||||
import 'package:groceries_manager/utils/format_datetime_extension.dart';
|
||||
import 'package:groceries_manager/utils/pluralize_int_extension.dart';
|
||||
|
||||
class ProductWidget extends GetWidget {
|
||||
final ProductData prod;
|
||||
final ProductCategoryData cat;
|
||||
final StorageLocationData store;
|
||||
|
||||
final void Function(ProductData) onEditClicked;
|
||||
final void Function(ProductData) onDeleteClicked;
|
||||
final void Function(ProductData) onAddToCartClicked;
|
||||
|
||||
const ProductWidget({
|
||||
super.key,
|
||||
required this.prod,
|
||||
required this.cat,
|
||||
required this.store,
|
||||
required this.onEditClicked,
|
||||
required this.onDeleteClicked,
|
||||
required this.onAddToCartClicked,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print(prod);
|
||||
return Card.outlined(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
top: 4,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
key: ValueKey("se-item-${prod.id}"),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(prod.name),
|
||||
subtitle: Text(
|
||||
"Куплено: ${prod.purchaseDate?.simpleDateFormat}\n"
|
||||
"Испортится через ${() {
|
||||
final diff =
|
||||
prod.expiryDate!.difference(DateTime.now());
|
||||
if (diff.inDays == 0) {
|
||||
return diff.inHours.pluralize(
|
||||
name: "час",
|
||||
absent: "часа",
|
||||
absentMul: "часов",
|
||||
);
|
||||
}
|
||||
return (diff.inDays + (diff.inHours > 24 ? 1 : 0))
|
||||
.pluralize(
|
||||
name: "день",
|
||||
absent: "дня",
|
||||
absentMul: "дней",
|
||||
);
|
||||
}()}",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Chip(
|
||||
avatar: Icon(
|
||||
ProductCategoryIcons.fromName(cat.icon).icon,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
label: Text(cat.name),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => onAddToCartClicked(prod),
|
||||
icon: const Icon(Icons.add_shopping_cart),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => onEditClicked(prod),
|
||||
icon: const Icon(Icons.edit),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => onDeleteClicked(prod),
|
||||
icon: const Icon(Icons.delete_forever),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
162
lib/pages/main/widgets/shopping_list_card.dart
Normal file
162
lib/pages/main/widgets/shopping_list_card.dart
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:groceries_manager/pages/main/home_controller.dart';
|
||||
|
||||
class ShoppingListCard extends GetView<HomeController> {
|
||||
const ShoppingListCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Card(
|
||||
child: Obx(
|
||||
() => controller.shoppingList.isEmpty
|
||||
? const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.check_rounded, size: 64),
|
||||
SizedBox(height: 16),
|
||||
Text("Всё в наличии!"),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Obx(
|
||||
() => ListView.builder(
|
||||
itemCount: controller.shoppingList.length + 3,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const ListTile(
|
||||
title: Text("Купить"),
|
||||
);
|
||||
}
|
||||
index -= 1;
|
||||
final toBuyItems = controller.groupedShoppingList[false]!;
|
||||
if (index < toBuyItems.length) {
|
||||
final (item, category, location) = toBuyItems[index];
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
key: ValueKey("slc-cbi-${item.id}-true"),
|
||||
title: Text(item.name),
|
||||
subtitle: Text("${item.quantity} ${item.unit}"),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: false,
|
||||
onChanged: (en) {
|
||||
if (en == true) {
|
||||
controller.switchShoppingItem(
|
||||
item,
|
||||
true,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
controller.editShoppingItem(
|
||||
item,
|
||||
category,
|
||||
location,
|
||||
);
|
||||
},
|
||||
child: const ListTile(
|
||||
title: Text("Редактировать"),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
controller.deleteShoppingItem(item);
|
||||
},
|
||||
child: const ListTile(
|
||||
title: Text("Удалить"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
index -= toBuyItems.length;
|
||||
if (index == 0) {
|
||||
return const Divider();
|
||||
}
|
||||
index -= 1;
|
||||
if (index == 0) {
|
||||
return const ListTile(
|
||||
title: Text("Куплено"),
|
||||
);
|
||||
}
|
||||
index -= 1;
|
||||
final boughtItems = controller.groupedShoppingList[true]!;
|
||||
if (index < boughtItems.length) {
|
||||
final (item, category, location) = boughtItems[index];
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
key: ValueKey("slc-cbi-${item.id}-true"),
|
||||
title: Text(item.name),
|
||||
subtitle: Text("${item.quantity} ${item.unit}"),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Checkbox(
|
||||
value: true,
|
||||
onChanged: (en) {
|
||||
if (en == false) {
|
||||
controller.switchShoppingItem(
|
||||
item,
|
||||
false,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
onTap: () {
|
||||
controller.saveShoppingItem(
|
||||
item,
|
||||
category,
|
||||
location,
|
||||
);
|
||||
},
|
||||
child: const ListTile(
|
||||
title: Text("Применить"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return const ListTile(title: Text("UNREACHABLE"));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
47
lib/pages/main/widgets/soon_expiries_card.dart
Normal file
47
lib/pages/main/widgets/soon_expiries_card.dart
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:groceries_manager/pages/main/home_controller.dart';
|
||||
import 'package:groceries_manager/pages/main/widgets/product_widget.dart';
|
||||
|
||||
class SoonExpiriesCard extends GetView<HomeController> {
|
||||
const SoonExpiriesCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Card(
|
||||
child: Obx(
|
||||
() => controller.soonExpiries.isEmpty
|
||||
? const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.sunny,
|
||||
size: 64,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text("Ура, всем продуктам хорошо!"),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView(
|
||||
children: [
|
||||
for (final (prod, cat, store) in controller.soonExpiries)
|
||||
ProductWidget(
|
||||
key: ValueKey("se-holder-${prod.id}"),
|
||||
prod: prod,
|
||||
cat: cat,
|
||||
store: store,
|
||||
onEditClicked: (p) => controller.editProduct(p),
|
||||
onDeleteClicked: (p) => controller.deleteProduct(p),
|
||||
onAddToCartClicked: (p) =>
|
||||
controller.addToShoppingList(p),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
118
lib/pages/main/widgets/stock_products_card.dart
Normal file
118
lib/pages/main/widgets/stock_products_card.dart
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../common/icons.dart';
|
||||
import '../../../utils/pluralize_int_extension.dart';
|
||||
import '../home_controller.dart';
|
||||
|
||||
import 'storages_list_item.dart';
|
||||
|
||||
class StockProductsCard extends GetView<HomeController> {
|
||||
const StockProductsCard({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Obx(
|
||||
() => controller.stockProducts.isEmpty
|
||||
? const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.question_mark_rounded,
|
||||
size: 64,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text("Пустовато, что-то упустили?"),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SingleChildScrollView(child: ItemsList()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemsList extends GetView<HomeController> {
|
||||
const ItemsList({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(
|
||||
() => ExpansionPanelList(
|
||||
expansionCallback: (int index, bool isExpanded) {
|
||||
controller.expandedStorages[index] = isExpanded;
|
||||
},
|
||||
children: [
|
||||
for (final (idx, (storage, items))
|
||||
in controller.stockProducts.indexed)
|
||||
ExpansionPanel(
|
||||
isExpanded: controller.expandedStorages[idx],
|
||||
headerBuilder: (context, isExpanded) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
StorageLocationIcon.fromName(storage.icon).icon,
|
||||
),
|
||||
title: Text(storage.name),
|
||||
);
|
||||
},
|
||||
body: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (storage.description.isNotEmpty)
|
||||
ListTile(
|
||||
dense: true,
|
||||
trailing: const Icon(Icons.description),
|
||||
title: Text(storage.description),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
trailing: const Icon(Icons.numbers),
|
||||
title: Text(items.length.pluralize(
|
||||
name: "предмет",
|
||||
absent: "предмета",
|
||||
absentMul: "предметов",
|
||||
)),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () => controller.editStorageLocation(storage),
|
||||
trailing: const Icon(Icons.edit),
|
||||
title: const Text("Редактировать"),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () => controller.deleteStorageLocation(storage),
|
||||
trailing: const Icon(Icons.delete_forever),
|
||||
title: const Text("Удалить"),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
onTap: () => controller.addNewProduct(storage),
|
||||
trailing: const Icon(Icons.add),
|
||||
title: const Text("Добавить продукт"),
|
||||
),
|
||||
const Divider(),
|
||||
if (items.isEmpty)
|
||||
const ListTile(
|
||||
title: Text("Пусто :("),
|
||||
),
|
||||
for (final (prod, cat) in items)
|
||||
StoragesListItem(
|
||||
key: ValueKey(prod.hashCode + cat.hashCode),
|
||||
prod: prod,
|
||||
cat: cat,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
111
lib/pages/main/widgets/storages_list_item.dart
Normal file
111
lib/pages/main/widgets/storages_list_item.dart
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../common/icons.dart';
|
||||
import '../../../db/database.dart';
|
||||
import '../../../utils/format_datetime_extension.dart';
|
||||
import '../../../utils/pluralize_int_extension.dart';
|
||||
import '../home_controller.dart';
|
||||
|
||||
class StoragesListItem extends GetWidget<HomeController> {
|
||||
final ProductData prod;
|
||||
final ProductCategoryData cat;
|
||||
|
||||
const StoragesListItem({
|
||||
super.key,
|
||||
required this.prod,
|
||||
required this.cat,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card.outlined(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
top: 4,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
key: ValueKey("se-item-${prod.id}"),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(prod.name),
|
||||
subtitle: Text(
|
||||
"Куплено: ${prod.purchaseDate?.simpleDateFormat}\n"
|
||||
"Испортится через ${() {
|
||||
final diff =
|
||||
prod.expiryDate!.difference(DateTime.now());
|
||||
if (diff.inDays == 0) {
|
||||
return diff.inHours.pluralize(
|
||||
name: "час",
|
||||
absent: "часа",
|
||||
absentMul: "часов",
|
||||
);
|
||||
}
|
||||
return (diff.inDays + (diff.inHours > 24 ? 1 : 0))
|
||||
.pluralize(
|
||||
name: "день",
|
||||
absent: "дня",
|
||||
absentMul: "дней",
|
||||
);
|
||||
}()}",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
runSpacing: 4,
|
||||
children: [
|
||||
Chip(
|
||||
avatar: Icon(
|
||||
ProductCategoryIcons.fromName(cat.icon).icon,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
label: Text(cat.name),
|
||||
),
|
||||
Chip(
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -4,
|
||||
vertical: -4,
|
||||
),
|
||||
label: Text("${prod.quantity} ${prod.unit}"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => controller.addToShoppingList(prod),
|
||||
icon: const Icon(Icons.add_shopping_cart),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => controller.editProduct(prod),
|
||||
icon: const Icon(Icons.edit),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => controller.deleteProduct(prod),
|
||||
icon: const Icon(Icons.delete_forever),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue