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,138 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:groceries_manager/common/icons.dart';
import 'package:groceries_manager/services/db_service.dart';
import 'package:groceries_manager/services/toaster_service.dart';
import '../../../db/database.dart';
class CategoryEditorController extends GetxController {
final editedCategory = Rxn<ProductCategoryData>();
final categoryIcon = ProductCategoryIcons.fork.obs;
final nameController = TextEditingController();
void setEditedCategory(ProductCategoryData ecd) {
editedCategory.value = ecd;
nameController.text = ecd.name;
categoryIcon.value = ProductCategoryIcons.fromName(ecd.icon);
}
Future<void> save() async {
final text = nameController.text.trim();
if (text.isEmpty) {
ToasterService.to.error(
title: "Ошибка",
message: "Имя категории не может быть пустым",
);
return;
}
if (editedCategory.value != null) {
await DBService.to.db.updateProductCategory(
id: editedCategory.value!.id,
icon: categoryIcon.value.name,
name: text,
);
ToasterService.to.success(
title: "Успех",
message: "Категория '$text' изменена",
);
} else {
await DBService.to.db.addProductCategory(
icon: categoryIcon.value.name,
name: text,
);
ToasterService.to.success(
title: "Успех",
message: "Категория '$text' создана",
);
}
cancel();
}
void cancel() {
Get.backLegacy();
}
}
class CategoryEditorDialog extends GetView<CategoryEditorController> {
const CategoryEditorDialog({super.key});
@override
Widget build(BuildContext context) {
final size = Get.size;
return AlertDialog(
title: controller.editedCategory.value != null
? const Text("Редактируем категорию")
: const Text("Новая категория"),
content: SizedBox(
width: size.width * 0.5,
height: size.height * 0.9,
child: ListView(
children: [
TextFormField(
controller: controller.nameController,
decoration: const InputDecoration(label: Text("Название")),
),
Obx(
() => DropdownButtonFormField<ProductCategoryIcons>(
items: [
for (final item in ProductCategoryIcons.values)
DropdownMenuItem(
value: item,
child: Row(
children: [
Icon(
ProductCategoryIcons.fromName(item.name).icon,
),
const SizedBox(width: 8),
Text(item.betterName),
],
),
),
],
value: controller.categoryIcon.value,
isExpanded: true,
hint: const Text("Иконка"),
onChanged: (catIcon) {
if (catIcon != null) {
controller.categoryIcon.value = catIcon;
}
},
),
),
],
),
),
actions: [
TextButton(
onPressed: () => controller.cancel(),
child: const Text("Отмена"),
),
TextButton(
onPressed: () => controller.save(),
child: const Text("Сохранить"),
),
],
);
}
static Future<void> show({
ProductCategoryData? ecd,
}) async {
final controller = Get.put(CategoryEditorController());
if (ecd != null) {
controller.setEditedCategory(ecd);
}
await Get.dialog(
const CategoryEditorDialog(),
id: "CategoryEditorDialog",
name: "CategoryEditorDialog",
);
Get.delete<CategoryEditorController>();
}
}

View file

@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:groceries_manager/common/icons.dart';
import 'package:groceries_manager/services/db_service.dart';
import 'package:groceries_manager/services/toaster_service.dart';
import 'package:groceries_manager/utils/get_interface_extension.dart';
import '../../../db/database.dart';
import 'category_editor_dialog.dart';
class CategoryManagerController extends GetxController {
final categories = <(ProductCategoryData, int)>[].obs;
@override
void onInit() {
super.onInit();
refreshData();
}
Future<void> refreshData() async {
final categories = await DBService.to.db.getProductCategoriesWithCounts();
this.categories.clear();
this.categories.addAll(categories);
}
void cancel() {
Get.backLegacy();
}
Future<void> deleteCategory(ProductCategoryData item) async {
final count = categories.firstWhere((e) => e.$1.id == item.id).$2;
if (count > 0) {
ToasterService.to.error(
title: "Ошибка",
message: "Нельзя удалить категорию, к которой привязаны продукты!",
);
return;
}
final confirmed = await Get.confirm(
title: "Внимание!",
content: "Удаление категории необратимо!\n"
"Точно хотите удалить '${item.name}'?",
);
if (!confirmed) return;
await DBService.to.db.deleteProductCategory(item);
refreshData();
}
Future<void> newCategory() async {
await CategoryEditorDialog.show();
refreshData();
}
Future<void> editCategory(ProductCategoryData item) async {
await CategoryEditorDialog.show(ecd: item);
refreshData();
}
}
class CategoryManagerDialog extends GetView<CategoryManagerController> {
const CategoryManagerDialog({super.key});
@override
Widget build(BuildContext context) {
final size = Get.size;
return AlertDialog(
title: const Text("Управление категориями"),
content: SizedBox(
width: size.width * 0.9,
height: size.height * 0.9,
child: SingleChildScrollView(
child: Obx(
() => Table(
border: TableBorder.all(color: Colors.blueGrey.withAlpha(50)),
children: [
const TableRow(
children: [
TableCell(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Иконка"),
),
),
TableCell(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Название"),
),
),
TableCell(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Продукты в категории"),
),
),
TableCell(
child: SizedBox(),
),
],
),
for (final item in controller.categories)
TableRow(
key: ValueKey(item),
children: [
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
ProductCategoryIcons.fromName(item.$1.icon).icon,
),
),
),
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.$1.name),
),
),
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("${item.$2}"),
),
),
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ElevatedButton.icon(
onPressed: () =>
controller.editCategory(item.$1),
icon: const Icon(Icons.edit_rounded),
label: const Text("Редактировать"),
),
const SizedBox(height: 4),
ElevatedButton.icon(
onPressed: () =>
controller.deleteCategory(item.$1),
icon: const Icon(Icons.delete_forever),
label: const Text("Удалить"),
),
],
),
),
),
],
),
],
),
),
),
),
actions: [
ElevatedButton.icon(
onPressed: () => controller.newCategory(),
icon: const Icon(Icons.add),
label: const Text("Новая категория"),
),
TextButton(
onPressed: () => controller.cancel(),
child: const Text("Закрыть"),
),
],
);
}
static Future<void> show({
ProductCategoryData? ecd,
}) async {
Get.put(CategoryManagerController());
await Get.dialog(
const CategoryManagerDialog(),
id: "CategoryManagerDialog",
name: "CategoryManagerDialog",
);
Get.delete<CategoryManagerController>();
}
}

View file

@ -0,0 +1,325 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:groceries_manager/common/icons.dart';
import 'package:groceries_manager/utils/format_datetime_extension.dart';
import 'package:groceries_manager/pages/main/dialogs/category_editor_dialog.dart';
import '../../../db/database.dart';
import '../../../services/db_service.dart';
import '../../../services/toaster_service.dart';
import 'storage_location_editor_dialog.dart';
class ProductEditorController extends GetxController {
ProductData? editedProduct;
late final RxList<ProductCategoryData> categories;
late final RxList<StorageLocationData> storages;
final nameController = TextEditingController();
final category = Rxn<ProductCategoryData>();
final storage = Rxn<StorageLocationData>();
final quantityController = TextEditingController();
final unitController = TextEditingController();
final expiryDate = Rxn<DateTime>();
final barcodeController = TextEditingController();
void setEditedProduct(ProductData epd) {
editedProduct = epd;
nameController.text = epd.name;
category.value =
categories.firstWhere((e) => e.id == editedProduct!.category);
storage.value = storages.firstWhere((e) => e.id == editedProduct!.storage);
quantityController.text = epd.quantity.toString();
unitController.text = epd.unit;
expiryDate.value = epd.expiryDate;
barcodeController.text = epd.barcode;
}
Future<void> save() async {
final name = nameController.text.trim();
if (name.isEmpty) {
ToasterService.to.error(
title: "Ошибка",
message: "Имя категории не может быть пустым",
);
return;
}
final category = this.category.value;
if (category == null) {
ToasterService.to.error(
title: "Ошибка",
message: "Необходимо выбрать категорию продукта",
);
return;
}
final storage = this.storage.value;
if (storage == null) {
ToasterService.to.error(
title: "Ошибка",
message: "Необходимо выбрать место хранения продукта",
);
return;
}
final quantity = quantityController.text;
if (!quantity.isNum) {
ToasterService.to.error(
title: "Ошибка",
message: "Кол-во продукта должно быть числом",
);
return;
}
final unit = unitController.text;
if (unit.isEmpty) {
ToasterService.to.error(
title: "Ошибка",
message: "Единица измерения продукта должна быть указана",
);
return;
}
final expiryDate = this.expiryDate.value;
if (expiryDate == null) {
ToasterService.to.error(
title: "Ошибка",
message: "Дата истечения срока годности должна быть указана",
);
return;
}
final barcode = barcodeController.text;
if (editedProduct != null) {
await DBService.to.db.updateProduct(
id: editedProduct!.id,
name: name,
category: category,
storage: storage,
quantity: double.parse(quantity),
unit: unit,
expiryDate: expiryDate,
barcode: barcode,
);
ToasterService.to.success(
title: "Успех",
message: "Продукт '$name' изменён",
);
} else {
await DBService.to.db.addProduct(
name: name,
category: category,
storage: storage,
quantity: double.parse(quantity),
unit: unit,
expiryDate: expiryDate,
barcode: barcode,
);
ToasterService.to.success(
title: "Успех",
message: "Продукт '$name' создан",
);
}
cancel();
}
void cancel() {
Get.backLegacy();
}
void setCategories(RxList<ProductCategoryData> categories) {
this.categories = categories;
}
void setStorages(RxList<StorageLocationData> storages) {
this.storages = storages;
}
void setStorage(StorageLocationData storage) {
this.storage.value = storage;
}
Future<void> newCategory() async {
await CategoryEditorDialog.show();
}
Future<void> newStorage() async {
await StorageLocationEditorDialog.show();
}
}
class ProductEditorDialog extends GetView<ProductEditorController> {
const ProductEditorDialog({super.key});
@override
Widget build(BuildContext context) {
final size = Get.size;
return AlertDialog(
title: controller.editedProduct != null
? const Text("Редактируем продукт")
: const Text("Новый продукт"),
content: SizedBox(
width: size.width * 0.5,
height: size.height * 0.9,
child: ListView(
children: [
TextFormField(
controller: controller.nameController,
decoration: const InputDecoration(label: Text("Наименование")),
),
Row(
children: [
Expanded(
child: Obx(
() => DropdownButtonFormField<ProductCategoryData>(
items: [
for (final item in controller.categories)
DropdownMenuItem(
value: item,
child: Row(
children: [
Icon(
ProductCategoryIcons.fromName(item.icon).icon,
),
const SizedBox(width: 8),
Text(item.name),
],
),
),
],
value: controller.category.value,
isExpanded: true,
hint: const Text("Категория"),
onChanged: (cat) {
if (cat != null) {
controller.category.value = cat;
}
},
),
),
),
IconButton(
onPressed: () => controller.newCategory(),
icon: const Icon(Icons.add),
),
],
),
Row(
children: [
Expanded(
child: Obx(
() => DropdownButtonFormField<StorageLocationData>(
items: [
for (final item in controller.storages)
DropdownMenuItem(
value: item,
child: Row(
children: [
Icon(
StorageLocationIcon.fromName(item.icon).icon,
),
const SizedBox(width: 8),
Text(item.name),
],
),
),
],
value: controller.storage.value,
isExpanded: true,
hint: const Text("Место хранения"),
onChanged: (st) {
if (st != null) {
controller.storage.value = st;
}
},
),
),
),
IconButton(
onPressed: () => controller.newStorage(),
icon: const Icon(Icons.add),
),
],
),
TextFormField(
controller: controller.quantityController,
decoration: const InputDecoration(label: Text("Количество")),
),
TextFormField(
controller: controller.unitController,
decoration:
const InputDecoration(label: Text("Единица измерения")),
),
Obx(
() => TextFormField(
readOnly: true,
controller: TextEditingController(
text: controller.expiryDate.value?.simpleDateFormat,
),
onTap: () async {
final dt = await showDatePicker(
context: context,
initialDate: controller.expiryDate.value ??
DateTime.now().add(1.days),
firstDate: DateTime.now(),
lastDate: DateTime(9999),
);
if (dt != null) {
controller.expiryDate.value = dt;
}
},
decoration: const InputDecoration(
label: Text("Годен до"),
),
),
),
],
),
),
actions: [
TextButton(
onPressed: () => controller.cancel(),
child: const Text("Отмена"),
),
TextButton(
onPressed: () => controller.save(),
child: const Text("Сохранить"),
),
],
);
}
static Future<void> show({
required RxList<ProductCategoryData> categories,
required RxList<StorageLocationData> storages,
ProductData? editedProduct,
StorageLocationData? preselectedStorage,
}) async {
final controller = Get.put(ProductEditorController());
controller.setCategories(categories);
controller.setStorages(storages);
if (preselectedStorage != null) {
controller.setStorage(preselectedStorage);
}
if (editedProduct != null) {
controller.setEditedProduct(editedProduct);
}
const transDuration = Duration(milliseconds: 300);
await Get.dialog(
const ProductEditorDialog(),
id: "ProductEditorDialog",
name: "ProductEditorDialog",
transitionDuration: transDuration,
);
await (transDuration + 10.milliseconds).delay();
Get.delete<ProductEditorController>();
}
}

View file

@ -0,0 +1,286 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:groceries_manager/common/icons.dart';
import 'package:groceries_manager/pages/main/dialogs/category_editor_dialog.dart';
import 'package:groceries_manager/pages/main/dialogs/storage_location_editor_dialog.dart';
import 'package:groceries_manager/services/db_service.dart';
import 'package:groceries_manager/services/toaster_service.dart';
import '../../../db/database.dart';
class ShoppingItemEditorController extends GetxController {
final editedItem = Rxn<ShoppingListItemData>();
late final RxList<ProductCategoryData> categories;
late final RxList<StorageLocationData> storages;
final nameController = TextEditingController();
final category = Rxn<ProductCategoryData>();
final storage = Rxn<StorageLocationData>();
final quantityController = TextEditingController();
final unitController = TextEditingController();
void setEditedItem(
(ShoppingListItemData, ProductCategoryData, StorageLocationData) esi) {
final (item, category, storage) = esi;
editedItem.value = item;
nameController.text = item.name;
this.category.value = category;
this.storage.value = storage;
quantityController.text = item.quantity.toString();
unitController.text = item.unit;
}
void fromProduct(ProductData product) {
nameController.text = product.name;
category.value = categories.firstWhere((e) => e.id == product.category);
storage.value = storages.firstWhere((e) => e.id == product.storage);
quantityController.text = product.quantity.toString();
unitController.text = product.unit;
}
Future<void> save({bool clone = false}) async {
final name = nameController.text.trim();
if (name.isEmpty) {
ToasterService.to.error(
title: "Ошибка",
message: "Имя категории не может быть пустым",
);
return;
}
final category = this.category.value;
if (category == null) {
ToasterService.to.error(
title: "Ошибка",
message: "Необходимо выбрать категорию продукта",
);
return;
}
final storage = this.storage.value;
if (storage == null) {
ToasterService.to.error(
title: "Ошибка",
message: "Необходимо выбрать место хранения продукта",
);
return;
}
final quantity = quantityController.text;
if (!quantity.isNum) {
ToasterService.to.error(
title: "Ошибка",
message: "Кол-во продукта должно быть числом",
);
return;
}
final unit = unitController.text;
if (unit.isEmpty) {
ToasterService.to.error(
title: "Ошибка",
message: "Единица измерения продукта должна быть указана",
);
return;
}
if (editedItem.value != null && !clone) {
await DBService.to.db.updateShoppingListItem(
id: editedItem.value!.id,
name: name,
category: category,
storage: storage,
quantity: double.parse(quantity),
unit: unit,
);
ToasterService.to.success(
title: "Успех",
message: "Запись списка покупок изменена: "
"'$name' - $quantity $unit",
);
} else {
await DBService.to.db.addShoppingListItem(
name: name,
category: category,
storage: storage,
quantity: double.parse(quantity),
unit: unit,
);
ToasterService.to.success(
title: "Успех",
message: "Запись добавлена в список покупок: "
"'$name' - $quantity $unit",
);
}
cancel();
}
void cancel() {
Get.backLegacy();
}
Future<void> newCategory() async {
await CategoryEditorDialog.show();
}
Future<void> newStorage() async {
await StorageLocationEditorDialog.show();
}
}
class ShoppingItemEditorDialog extends GetView<ShoppingItemEditorController> {
const ShoppingItemEditorDialog({super.key});
@override
Widget build(BuildContext context) {
final size = Get.size;
return AlertDialog(
title: controller.editedItem.value != null
? const Text("Редактируем запись списка покупок")
: const Text("Новая запись списка покупок"),
content: SizedBox(
width: size.width * 0.5,
height: size.height * 0.9,
child: ListView(
children: [
TextFormField(
controller: controller.nameController,
decoration: const InputDecoration(label: Text("Наименование")),
),
Row(
children: [
Expanded(
child: Obx(
() => DropdownButtonFormField<ProductCategoryData>(
items: [
for (final item in controller.categories)
DropdownMenuItem(
value: item,
child: Row(
children: [
Icon(
ProductCategoryIcons.fromName(item.icon).icon,
),
const SizedBox(width: 8),
Text(item.name),
],
),
),
],
value: controller.category.value,
isExpanded: true,
hint: const Text("Категория"),
onChanged: (prod) {
if (prod != null) {
controller.category.value = prod;
}
},
),
),
),
IconButton(
onPressed: () => controller.newCategory(),
icon: const Icon(Icons.add),
),
],
),
Row(
children: [
Expanded(
child: Obx(
() => DropdownButtonFormField<StorageLocationData>(
items: [
for (final item in controller.storages)
DropdownMenuItem(
value: item,
child: Row(
children: [
Icon(
StorageLocationIcon.fromName(item.icon).icon,
),
const SizedBox(width: 8),
Text(item.name),
],
),
),
],
value: controller.storage.value,
isExpanded: true,
hint: const Text("Место хранения"),
onChanged: (item) {
if (item != null) {
controller.storage.value = item;
}
},
),
),
),
IconButton(
onPressed: () => controller.newStorage(),
icon: const Icon(Icons.add),
),
],
),
TextFormField(
controller: controller.quantityController,
decoration: const InputDecoration(label: Text("Количество")),
),
TextFormField(
controller: controller.unitController,
decoration:
const InputDecoration(label: Text("Единица измерения")),
),
],
),
),
actions: [
if (controller.editedItem.value != null)
TextButton(
onPressed: () => controller.save(clone: true),
child: const Text("Клонировать"),
),
TextButton(
onPressed: () => controller.cancel(),
child: const Text("Отмена"),
),
TextButton(
onPressed: () => controller.save(),
child: const Text("Сохранить"),
),
],
);
}
static Future<void> show({
required RxList<ProductCategoryData> categories,
required RxList<StorageLocationData> storages,
(
ShoppingListItemData,
ProductCategoryData,
StorageLocationData
)? editedItem,
ProductData? fromProduct,
}) async {
final controller = Get.put(ShoppingItemEditorController());
controller.categories = categories;
controller.storages = storages;
if (editedItem != null) {
controller.setEditedItem(editedItem);
}
if (fromProduct != null) {
controller.fromProduct(fromProduct);
}
await Get.dialog(
const ShoppingItemEditorDialog(),
id: "ShoppingItemEditorDialog",
name: "ShoppingItemEditorDialog",
);
Get.delete<ShoppingItemEditorController>();
}
}

View file

@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:groceries_manager/common/icons.dart';
import 'package:groceries_manager/services/db_service.dart';
import 'package:groceries_manager/services/toaster_service.dart';
import 'package:icons_plus/icons_plus.dart';
import '../../../db/database.dart';
import '../../../models/enums/temperature_mode.dart';
class StorageLocationEditorController extends GetxController {
final editedStorageLocation = Rxn<StorageLocationData>();
final nameController = TextEditingController();
final descriptionController = TextEditingController();
final temperatureMode = TemperatureMode.unspecified.obs;
final storageLocationIcon = StorageLocationIcon.box_2.obs;
final setAsDefault = false.obs;
void setEditedStorageLocation(StorageLocationData ecd) {
editedStorageLocation.value = ecd;
nameController.text = ecd.name;
descriptionController.text = ecd.description;
temperatureMode.value = TemperatureMode.fromName(ecd.temperatureMode);
storageLocationIcon.value = StorageLocationIcon.fromName(ecd.icon);
}
Future<void> save() async {
final name = nameController.text.trim();
if (name.isEmpty) {
ToasterService.to.error(
title: "Ошибка",
message: "Имя места хранения не может быть пустым",
);
return;
}
final description = descriptionController.text.trim();
if (editedStorageLocation.value != null) {
final updateId = await DBService.to.db.updateStorageLocation(
id: editedStorageLocation.value!.id,
name: name,
description: description,
temperatureMode: temperatureMode.value,
icon: storageLocationIcon.value.name,
);
if (setAsDefault.value) {
await DBService.to.db.switchDefault(updateId);
}
ToasterService.to.success(
title: "Успех",
message: "Место хранения '$name' изменено",
);
} else {
final newId = await DBService.to.db.addStorageLocation(
name: name,
description: description,
temperatureMode: temperatureMode.value,
icon: storageLocationIcon.value.name,
);
if (setAsDefault.value) {
await DBService.to.db.switchDefault(newId);
}
ToasterService.to.success(
title: "Успех",
message: "Место хранения '$name' создано",
);
}
cancel();
}
void cancel() {
Get.backLegacy();
}
}
class StorageLocationEditorDialog
extends GetView<StorageLocationEditorController> {
const StorageLocationEditorDialog({super.key});
@override
Widget build(BuildContext context) {
final size = Get.size;
return AlertDialog(
title: controller.editedStorageLocation.value != null
? const Text("Редактируем место хранения")
: const Text("Новое место хранения"),
content: SizedBox(
width: size.width * 0.5,
height: size.height * 0.9,
child: ListView(
children: [
TextFormField(
controller: controller.nameController,
decoration: const InputDecoration(label: Text("Название")),
),
TextFormField(
controller: controller.descriptionController,
decoration: const InputDecoration(label: Text("Описание")),
),
Obx(
() => DropdownButtonFormField<TemperatureMode>(
items: [
for (final tempMode in TemperatureMode.values)
DropdownMenuItem(
value: tempMode,
child: Text(tempMode.betterName),
),
],
value: controller.temperatureMode.value,
isExpanded: true,
icon: const Icon(FontAwesome.temperature_empty_solid),
hint: const Text("Температурный режим"),
onChanged: (tempMode) {
if (tempMode != null) {
controller.temperatureMode.value = tempMode;
}
},
),
),
Obx(
() => DropdownButtonFormField<StorageLocationIcon>(
items: [
for (final item in StorageLocationIcon.values)
DropdownMenuItem(
value: item,
child: Row(
children: [
Icon(
StorageLocationIcon.fromName(item.name).icon,
),
const SizedBox(width: 8),
Text(item.betterName),
],
),
),
],
value: controller.storageLocationIcon.value,
isExpanded: true,
hint: const Text("Иконка"),
onChanged: (slIcon) {
if (slIcon != null) {
controller.storageLocationIcon.value = slIcon;
}
},
),
),
Obx(
() => SwitchListTile(
contentPadding: EdgeInsets.zero,
value: controller.setAsDefault.value,
onChanged: (newVal) {
controller.setAsDefault.value = newVal;
},
title: const Text("Назначить основным"),
),
),
],
),
),
actions: [
TextButton(
onPressed: () => controller.cancel(),
child: const Text("Отмена"),
),
TextButton(
onPressed: () => controller.save(),
child: const Text("Сохранить"),
),
],
);
}
static Future<void> show({
StorageLocationData? esl,
}) async {
final controller = Get.put(StorageLocationEditorController());
if (esl != null) {
controller.setEditedStorageLocation(esl);
}
await Get.dialog(
const StorageLocationEditorDialog(),
id: "StorageLocationEditorDialog",
name: "StorageLocationEditorDialog",
);
Get.delete<StorageLocationEditorController>();
}
}

View file

@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:groceries_manager/pages/main/home_controller.dart';
import 'package:groceries_manager/services/db_service.dart';
import 'package:groceries_manager/services/toaster_service.dart';
import 'package:groceries_manager/utils/get_interface_extension.dart';
import '../../../db/database.dart';
class UserManagerController extends GetxController {
final users = <UserData>[].obs;
@override
void onInit() {
super.onInit();
refreshData();
}
Future<void> refreshData() async {
final users = await DBService.to.db.getUsers();
this.users.clear();
this.users.addAll(users);
}
void cancel() {
Get.backLegacy();
}
Future<void> changeUserPassword(UserData item) async {
final newPassword = await Get.prompt(
title: "Изменение пароля для '${item.login}'",
label: "Новый пароль",
stringValidator: (value) {
if (RegExp(r'[^а-яА-Яa-zA-Z0-9]').hasMatch(value ?? "")) {
return "Пароль не соответствует требованиям: а-яА-Яa-zA-Z0-9";
}
return null;
},
);
if (newPassword == null) {
return;
}
await DBService.to.db.updateUserPassword(
item,
password: newPassword,
);
refreshData();
}
Future<void> deleteUser(UserData item) async {
final currentUser = Get<HomeController>().user.value;
if (currentUser.id == item.id) {
ToasterService.to.error(
title: "Ошибка",
message: "Нельзя удалить самого себя!",
);
return;
}
final confirmed = await Get.confirm(
title: "Внимание!",
content: "Удаление пользователя необратимо!\n"
"Точно хотите удалить '${item.login}'?",
);
if (!confirmed) return;
final deleted = await DBService.to.db.deleteUser(item);
if (!deleted) {
ToasterService.to.error(
title: "Ошибка",
message: "Нельзя единственного пользователя!",
);
}
refreshData();
}
}
class UserManagerDialog extends GetView<UserManagerController> {
const UserManagerDialog({super.key});
@override
Widget build(BuildContext context) {
final size = Get.size;
return AlertDialog(
title: const Text("Управление категориями"),
content: SizedBox(
width: size.width * 0.9,
height: size.height * 0.9,
child: SingleChildScrollView(
child: Obx(
() => Table(
border: TableBorder.all(color: Colors.blueGrey.withAlpha(50)),
children: [
const TableRow(
children: [
TableCell(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Логин"),
),
),
TableCell(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text("Частичный хэш пароля"),
),
),
TableCell(
child: SizedBox(),
),
],
),
for (final item in controller.users)
TableRow(
key: ValueKey(item),
children: [
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(item.login),
),
),
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("${item.password.substring(0, 20)}..."),
),
),
TableCell(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
ElevatedButton.icon(
onPressed: () =>
controller.changeUserPassword(item),
icon: const Icon(Icons.edit_rounded),
label: const Text("Изменить пароль"),
),
const SizedBox(height: 4),
ElevatedButton.icon(
onPressed: () => controller.deleteUser(item),
icon: const Icon(Icons.delete_forever),
label: const Text("Удалить"),
),
],
),
),
),
],
),
],
),
),
),
),
actions: [
TextButton(
onPressed: () => controller.cancel(),
child: const Text("Закрыть"),
),
],
);
}
static Future<void> show({
UserData? ecd,
}) async {
Get.put(UserManagerController());
await Get.dialog(
const UserManagerDialog(),
id: "UserManagerDialog",
name: "UserManagerDialog",
);
Get.delete<UserManagerController>();
}
}