Initial and done prolly

This commit is contained in:
Andrew 2025-01-05 16:01:21 +07:00
commit 6f88b9966f
175 changed files with 15445 additions and 0 deletions

View 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),
),
],
),
],
),
),
);
}
}

View 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"));
},
),
),
),
),
);
}
}

View 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),
),
],
),
),
),
);
}
}

View 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,
),
],
),
)
],
),
);
}
}

View 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),
),
],
),
],
),
),
);
}
}