Compare commits
10 commits
cdf32454e6
...
80ab81f1f1
Author | SHA1 | Date | |
---|---|---|---|
80ab81f1f1 | |||
|
7174a03cf2 | ||
|
9ff6d97c90 | ||
|
6cdfcdf85c | ||
|
13c071b8ca | ||
|
384fbb0573 | ||
|
b013964615 | ||
|
a897d4c4af | ||
|
2fa9486db3 | ||
|
50b6d038c3 |
23 changed files with 2202 additions and 935 deletions
16
.gitea/workflows/build.yml
Normal file
16
.gitea/workflows/build.yml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: default
|
||||||
|
container:
|
||||||
|
image: ghcr.io/cirruslabs/flutter:stable
|
||||||
|
steps:
|
||||||
|
- run: apt-get update && apt-get install nodejs -y
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- run: flutter --version
|
||||||
|
- run: flutter build apk --release
|
16
README.md
16
README.md
|
@ -16,3 +16,19 @@ This app uses /l10n/ according to the official flutter
|
||||||
We use [weblate](https://translate.codeberg.org/engage/outbag/) to manage translations.
|
We use [weblate](https://translate.codeberg.org/engage/outbag/) to manage translations.
|
||||||
|
|
||||||
[![Translation status](https://translate.codeberg.org/widget/outbag/outbag-app/multi-auto.svg)](https://translate.codeberg.org/engage/outbag/)
|
[![Translation status](https://translate.codeberg.org/widget/outbag/outbag-app/multi-auto.svg)](https://translate.codeberg.org/engage/outbag/)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
To keep the code formatted properly,
|
||||||
|
we use git hooks to format files before committing.
|
||||||
|
|
||||||
|
You can easily add git-hooks using the following command:
|
||||||
|
``` sh
|
||||||
|
git config core.hooksPath ./hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
Afterwards the `pre-commit` hook will automatically be run,
|
||||||
|
when you commit your changes:
|
||||||
|
``` sh
|
||||||
|
git add # some files
|
||||||
|
git commit
|
||||||
|
```
|
||||||
|
|
12
hooks/pre-commit
Executable file
12
hooks/pre-commit
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# adapted from https://prettier.io/docs/en/precommit.html#option-5-shell-script
|
||||||
|
FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g' | grep '.dart$')
|
||||||
|
[ -z "$FILES" ] && exit 0
|
||||||
|
|
||||||
|
# Format all selected files
|
||||||
|
echo "$FILES" | xargs dart format
|
||||||
|
|
||||||
|
# Add back the formatted files to staging
|
||||||
|
echo "$FILES" | xargs git add
|
||||||
|
|
||||||
|
exit 0
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
arb-dir: lib/l10n
|
arb-dir: lib/l10n
|
||||||
template-arb-file: app_en.arb
|
template-arb-file: app_en.arb
|
||||||
output-localization-file: app_localizations.dart
|
output-localization-file: app_localizations.dart
|
||||||
|
|
|
@ -363,20 +363,30 @@ class RoomInfo {
|
||||||
|
|
||||||
class RoomCategory {
|
class RoomCategory {
|
||||||
final int? id;
|
final int? id;
|
||||||
final String name;
|
String name;
|
||||||
final ColorSwatch<int> color;
|
ColorSwatch<int> color;
|
||||||
|
final String room;
|
||||||
|
final String server;
|
||||||
|
|
||||||
const RoomCategory(
|
RoomCategory(
|
||||||
{required this.id, required this.name, required this.color});
|
{required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.color,
|
||||||
|
required this.server,
|
||||||
|
required this.room});
|
||||||
|
|
||||||
factory RoomCategory.fromJSON(dynamic json) {
|
factory RoomCategory.fromJSON(String server, String room, dynamic json) {
|
||||||
return RoomCategory(
|
return RoomCategory(
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
id: json['id'],
|
id: json['id'],
|
||||||
name: json['title'],
|
name: json['title'],
|
||||||
color: colorFromString(json['color']));
|
color: colorFromString(json['color']));
|
||||||
}
|
}
|
||||||
factory RoomCategory.other(BuildContext context) {
|
factory RoomCategory.other(String server, String room, BuildContext context) {
|
||||||
return RoomCategory(
|
return RoomCategory(
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
id: null,
|
id: null,
|
||||||
name: AppLocalizations.of(context)!.categoryNameOther,
|
name: AppLocalizations.of(context)!.categoryNameOther,
|
||||||
color: Colors.grey);
|
color: Colors.grey);
|
||||||
|
@ -398,6 +408,69 @@ class RoomCategory {
|
||||||
"purple-acc",
|
"purple-acc",
|
||||||
].map((txt) => colorFromString(txt)).toList();
|
].map((txt) => colorFromString(txt)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get list of all categories in a given room
|
||||||
|
static Future<List<RoomCategory>> list(String server, String room) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final rooms = (await db.collection('categories:$room@$server').get()) ?? {};
|
||||||
|
List<RoomCategory> builder = [];
|
||||||
|
for (MapEntry entry in rooms.entries) {
|
||||||
|
try {
|
||||||
|
builder.add(RoomCategory.fromMap(entry.value));
|
||||||
|
} catch (e) {
|
||||||
|
// skip invalid entries
|
||||||
|
// NOTE: might want to autodelete them in the future
|
||||||
|
// although keeping them might be ok,
|
||||||
|
// in case we ever get a new dataset to fix the current state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen to room change
|
||||||
|
static listen(
|
||||||
|
String server, String room, Function(Map<String, dynamic>) cb) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final stream = db.collection('categories:$room@$server').stream;
|
||||||
|
stream.listen(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory RoomCategory.fromMap(Map<String, dynamic> map) {
|
||||||
|
return RoomCategory(
|
||||||
|
server: map['server'],
|
||||||
|
room: map['room'],
|
||||||
|
id: map['id'],
|
||||||
|
name: map['name'],
|
||||||
|
color: colorFromString(map['color']));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'server': server,
|
||||||
|
'room': room,
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'color': colorIdFromColor(color)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('categories:$room@$server').doc('$id').set(toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('categories:$room@$server').doc('$id').delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<RoomCategory> fromDisk(
|
||||||
|
{required int id, required String server, required String room}) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final raw =
|
||||||
|
await db.collection('categories:$room@$server').doc('$id').get();
|
||||||
|
return RoomCategory.fromMap(raw!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorSwatch<int> colorFromString(String text) {
|
ColorSwatch<int> colorFromString(String text) {
|
||||||
|
@ -474,7 +547,7 @@ String colorIdFromColor(ColorSwatch<int> color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomProduct {
|
class RoomProduct {
|
||||||
int id;
|
final int id;
|
||||||
String name;
|
String name;
|
||||||
String description;
|
String description;
|
||||||
// category ID
|
// category ID
|
||||||
|
@ -490,9 +563,14 @@ class RoomProduct {
|
||||||
// parent product ID
|
// parent product ID
|
||||||
int? parent;
|
int? parent;
|
||||||
|
|
||||||
|
final String server;
|
||||||
|
final String room;
|
||||||
|
|
||||||
RoomProduct(
|
RoomProduct(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
required this.server,
|
||||||
|
required this.room,
|
||||||
this.description = '',
|
this.description = '',
|
||||||
this.category = -1,
|
this.category = -1,
|
||||||
this.defaultUnit = 0,
|
this.defaultUnit = 0,
|
||||||
|
@ -500,8 +578,10 @@ class RoomProduct {
|
||||||
this.ean,
|
this.ean,
|
||||||
this.parent});
|
this.parent});
|
||||||
|
|
||||||
factory RoomProduct.fromJSON(dynamic json) {
|
factory RoomProduct.fromJSON(String server, String room, dynamic json) {
|
||||||
return RoomProduct(
|
return RoomProduct(
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
id: json['listProdID'],
|
id: json['listProdID'],
|
||||||
name: json['title'],
|
name: json['title'],
|
||||||
description: json['description'],
|
description: json['description'],
|
||||||
|
@ -511,10 +591,82 @@ class RoomProduct {
|
||||||
ean: json['ean'],
|
ean: json['ean'],
|
||||||
parent: json['parent']);
|
parent: json['parent']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get list of all categories in a given room
|
||||||
|
static Future<List<RoomProduct>> list(String server, String room) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final rooms = (await db.collection('products:$room@$server').get()) ?? {};
|
||||||
|
List<RoomProduct> builder = [];
|
||||||
|
for (MapEntry entry in rooms.entries) {
|
||||||
|
try {
|
||||||
|
builder.add(RoomProduct.fromMap(entry.value));
|
||||||
|
} catch (e) {
|
||||||
|
// skip invalid entries
|
||||||
|
// NOTE: might want to autodelete them in the future
|
||||||
|
// although keeping them might be ok,
|
||||||
|
// in case we ever get a new dataset to fix the current state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen to room change
|
||||||
|
static listen(
|
||||||
|
String server, String room, Function(Map<String, dynamic>) cb) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final stream = db.collection('products:$room@$server').stream;
|
||||||
|
stream.listen(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory RoomProduct.fromMap(Map<String, dynamic> map) {
|
||||||
|
return RoomProduct(
|
||||||
|
server: map['server'],
|
||||||
|
room: map['room'],
|
||||||
|
id: map['id'],
|
||||||
|
name: map['name'],
|
||||||
|
description: map['description'],
|
||||||
|
category: map['category'],
|
||||||
|
defaultUnit: map['default_unit'],
|
||||||
|
defaultValue: map['default_value'],
|
||||||
|
ean: map['ean'],
|
||||||
|
parent: map['parent']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'server': server,
|
||||||
|
'room': room,
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'description': description,
|
||||||
|
'category': category,
|
||||||
|
'default_unit': defaultUnit,
|
||||||
|
'default_value': defaultValue,
|
||||||
|
'ean': ean,
|
||||||
|
'parent': parent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('products:$room@$server').doc('$id').set(toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('products:$room@$server').doc('$id').delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<RoomProduct> fromDisk(
|
||||||
|
{required int id, required String server, required String room}) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final raw = await db.collection('products:$room@$server').doc('$id').get();
|
||||||
|
return RoomProduct.fromMap(raw!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoomItem {
|
class RoomItem {
|
||||||
int id;
|
final int id;
|
||||||
int state;
|
int state;
|
||||||
String name;
|
String name;
|
||||||
String description;
|
String description;
|
||||||
|
@ -527,9 +679,14 @@ class RoomItem {
|
||||||
// may link to a product
|
// may link to a product
|
||||||
int? link;
|
int? link;
|
||||||
|
|
||||||
|
final String server;
|
||||||
|
final String room;
|
||||||
|
|
||||||
RoomItem(
|
RoomItem(
|
||||||
{required this.id,
|
{required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
required this.server,
|
||||||
|
required this.room,
|
||||||
this.description = '',
|
this.description = '',
|
||||||
this.state = 0,
|
this.state = 0,
|
||||||
this.category = -1,
|
this.category = -1,
|
||||||
|
@ -537,10 +694,12 @@ class RoomItem {
|
||||||
this.value = '',
|
this.value = '',
|
||||||
this.link});
|
this.link});
|
||||||
|
|
||||||
factory RoomItem.fromJSON(dynamic json) {
|
factory RoomItem.fromJSON(String server, String room, dynamic json) {
|
||||||
return RoomItem(
|
return RoomItem(
|
||||||
id: json['listItemID'],
|
id: json['listItemID'],
|
||||||
name: json['title'],
|
name: json['title'],
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
description: json['description'],
|
description: json['description'],
|
||||||
category: json['listCatID'],
|
category: json['listCatID'],
|
||||||
state: json['state'],
|
state: json['state'],
|
||||||
|
@ -553,10 +712,84 @@ class RoomItem {
|
||||||
return RoomItem(
|
return RoomItem(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
description: description,
|
description: description,
|
||||||
category: category,
|
category: category,
|
||||||
unit: unit,
|
unit: unit,
|
||||||
value: value,
|
value: value,
|
||||||
link: link);
|
link: link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get list of all categories in a given room
|
||||||
|
static Future<List<RoomItem>> list(String server, String room) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final rooms = (await db.collection('items:$room@$server').get()) ?? {};
|
||||||
|
List<RoomItem> builder = [];
|
||||||
|
for (MapEntry entry in rooms.entries) {
|
||||||
|
try {
|
||||||
|
builder.add(RoomItem.fromMap(entry.value));
|
||||||
|
} catch (e) {
|
||||||
|
// skip invalid entries
|
||||||
|
// NOTE: might want to autodelete them in the future
|
||||||
|
// although keeping them might be ok,
|
||||||
|
// in case we ever get a new dataset to fix the current state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// listen to room change
|
||||||
|
static listen(
|
||||||
|
String server, String room, Function(Map<String, dynamic>) cb) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final stream = db.collection('items:$room@$server').stream;
|
||||||
|
stream.listen(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory RoomItem.fromMap(Map<String, dynamic> map) {
|
||||||
|
return RoomItem(
|
||||||
|
server: map['server'],
|
||||||
|
room: map['room'],
|
||||||
|
id: map['id'],
|
||||||
|
name: map['name'],
|
||||||
|
description: map['description'],
|
||||||
|
category: map['category'],
|
||||||
|
state: map['state'],
|
||||||
|
unit: map['unit'],
|
||||||
|
value: map['value'],
|
||||||
|
link: map['product']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'server': server,
|
||||||
|
'room': room,
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'description': description,
|
||||||
|
'category': category,
|
||||||
|
'state': state,
|
||||||
|
'unit': unit,
|
||||||
|
'value': value,
|
||||||
|
'product': link
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> toDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('items:$room@$server').doc('$id').set(toMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeDisk() async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
await db.collection('items:$room@$server').doc('$id').delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<RoomProduct> fromDisk(
|
||||||
|
{required int id, required String server, required String room}) async {
|
||||||
|
final db = Localstore.instance;
|
||||||
|
final raw = await db.collection('items:$room@$server').doc('$id').get();
|
||||||
|
return RoomProduct.fromMap(raw!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,20 @@ import 'package:outbag_app/backend/room.dart';
|
||||||
|
|
||||||
class CategoryChip extends StatelessWidget {
|
class CategoryChip extends StatelessWidget {
|
||||||
final RoomCategory? category;
|
final RoomCategory? category;
|
||||||
|
final String server;
|
||||||
|
final String room;
|
||||||
|
|
||||||
const CategoryChip({super.key, this.category});
|
const CategoryChip(
|
||||||
|
{super.key, required this.server, required this.room, this.category});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ActionChip(
|
return ActionChip(
|
||||||
avatar: Icon(Icons.square_rounded,
|
avatar: Icon(Icons.square_rounded,
|
||||||
color: category?.color ?? RoomCategory.other(context).color),
|
color: category?.color ??
|
||||||
label: Text(category?.name ?? RoomCategory.other(context).name),
|
RoomCategory.other(server, room, context).color),
|
||||||
|
label: Text(
|
||||||
|
category?.name ?? RoomCategory.other(server, room, context).name),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,14 @@ class CategoryPicker extends StatelessWidget {
|
||||||
final String? hint;
|
final String? hint;
|
||||||
final String? label;
|
final String? label;
|
||||||
|
|
||||||
|
final String server;
|
||||||
|
final String room;
|
||||||
|
|
||||||
const CategoryPicker(
|
const CategoryPicker(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.categories,
|
required this.categories,
|
||||||
|
required this.server,
|
||||||
|
required this.room,
|
||||||
this.selected,
|
this.selected,
|
||||||
this.onSelect,
|
this.onSelect,
|
||||||
this.hint,
|
this.hint,
|
||||||
|
@ -31,7 +36,7 @@ class CategoryPicker extends StatelessWidget {
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixIcon: const Icon(Icons.category)),
|
prefixIcon: const Icon(Icons.category)),
|
||||||
value: selected,
|
value: selected,
|
||||||
items: [...categories, RoomCategory.other(context)]
|
items: [...categories, RoomCategory.other(server, room, context)]
|
||||||
.map((category) => DropdownMenuItem<int?>(
|
.map((category) => DropdownMenuItem<int?>(
|
||||||
value: category.id,
|
value: category.id,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
|
@ -424,5 +424,17 @@
|
||||||
"moveItemToCartTitle": "Move to Cart",
|
"moveItemToCartTitle": "Move to Cart",
|
||||||
"moveItemToCartSubtitle": "Mark item as in-cart, so others know, you bought it",
|
"moveItemToCartSubtitle": "Mark item as in-cart, so others know, you bought it",
|
||||||
"removeItemFromCartTitle": "Remove from Cart",
|
"removeItemFromCartTitle": "Remove from Cart",
|
||||||
"removeItemFromCartSubtitle": "Remove item from shopping cart, so others know, that you still need it"
|
"removeItemFromCartSubtitle": "Remove item from shopping cart, so others know, that you still need it",
|
||||||
|
|
||||||
|
"newItemQueryEmpty": "Type to show quick access buttons",
|
||||||
|
"newItemQueryHint": "Type item or product name",
|
||||||
|
"newItemQuickAccessPrefix": "Create:",
|
||||||
|
"newItemQuickProduct": "product: {text}",
|
||||||
|
"deleteItem": "Delete Item",
|
||||||
|
"deleteItemConfirm": "Do you really want to remove the item named {item}",
|
||||||
|
|
||||||
|
"deleteProductTitle": "Delete Product",
|
||||||
|
"deleteProductSubtitle": "Remove the product from list of known products",
|
||||||
|
"deleteProduct": "Delete Product",
|
||||||
|
"deleteProductConfirm": "DO you really want to remove the product named {product}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:outbag_app/backend/user.dart';
|
||||||
import 'package:outbag_app/backend/request.dart';
|
import 'package:outbag_app/backend/request.dart';
|
||||||
import 'package:outbag_app/screens/room/categories/edit.dart';
|
import 'package:outbag_app/screens/room/categories/edit.dart';
|
||||||
import 'package:outbag_app/screens/room/items/edit.dart';
|
import 'package:outbag_app/screens/room/items/edit.dart';
|
||||||
|
import 'package:outbag_app/screens/room/items/new.dart';
|
||||||
import 'package:outbag_app/screens/room/products/edit.dart';
|
import 'package:outbag_app/screens/room/products/edit.dart';
|
||||||
import 'package:outbag_app/screens/room/products/view.dart';
|
import 'package:outbag_app/screens/room/products/view.dart';
|
||||||
|
|
||||||
|
@ -306,7 +307,7 @@ class _OutbagAppState extends State {
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'new-item',
|
name: 'new-item',
|
||||||
path: 'new-item',
|
path: 'new-item',
|
||||||
builder: (context, state) => EditItemPage(
|
builder: (context, state) => NewItemPage(
|
||||||
server:
|
server:
|
||||||
state.params['server'] ?? '',
|
state.params['server'] ?? '',
|
||||||
room: state.params['id'] ?? '',
|
room: state.params['id'] ?? '',
|
||||||
|
|
|
@ -123,38 +123,48 @@ class _HomePageState extends State<HomePage> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView.builder(
|
body: Center(
|
||||||
itemCount: rooms.length,
|
child: Padding(
|
||||||
itemBuilder: (ctx, i) {
|
padding: const EdgeInsets.all(14),
|
||||||
final room = rooms[i];
|
child: ConstrainedBox(
|
||||||
return Card(
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
margin: const EdgeInsets.all(8.0),
|
child: ListView.builder(
|
||||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
itemCount: rooms.length,
|
||||||
semanticContainer: true,
|
itemBuilder: (ctx, i) {
|
||||||
child: InkWell(
|
final room = rooms[i];
|
||||||
onTap: () {
|
return Card(
|
||||||
// open room
|
margin: const EdgeInsets.all(8.0),
|
||||||
context.goNamed('room',
|
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||||
params: {'server': room.serverTag, 'id': room.id});
|
semanticContainer: true,
|
||||||
},
|
child: InkWell(
|
||||||
onLongPress: () {
|
onTap: () {
|
||||||
// open bottom sheet
|
// open room
|
||||||
// NOTE: feature yet to be confirmed
|
context.goNamed('room', params: {
|
||||||
},
|
'server': room.serverTag,
|
||||||
child: Container(
|
'id': room.id
|
||||||
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
});
|
||||||
child: ListTile(
|
},
|
||||||
title: Text(room.name),
|
onLongPress: () {
|
||||||
visualDensity: const VisualDensity(vertical: 3),
|
// open bottom sheet
|
||||||
subtitle: Text(room.description),
|
// NOTE: feature yet to be confirmed
|
||||||
leading: AspectRatio(
|
},
|
||||||
aspectRatio: 1 / 1,
|
child: Container(
|
||||||
child: SvgPicture.asset("${room.icon?.img}"),
|
padding:
|
||||||
),
|
const EdgeInsets.fromLTRB(10, 5, 5, 10),
|
||||||
hoverColor: Colors.transparent,
|
child: ListTile(
|
||||||
))));
|
title: Text(room.name),
|
||||||
},
|
visualDensity:
|
||||||
),
|
const VisualDensity(vertical: 3),
|
||||||
|
subtitle: Text(room.description),
|
||||||
|
leading: AspectRatio(
|
||||||
|
aspectRatio: 1 / 1,
|
||||||
|
child:
|
||||||
|
SvgPicture.asset("${room.icon?.img}"),
|
||||||
|
),
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
))));
|
||||||
|
},
|
||||||
|
)))),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
label: Text(AppLocalizations.of(context)!.addRoom),
|
label: Text(AppLocalizations.of(context)!.addRoom),
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
|
|
|
@ -202,8 +202,13 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
|
||||||
final id = body['data']['catID'];
|
final id = body['data']['catID'];
|
||||||
|
|
||||||
final cat = RoomCategory(
|
final cat = RoomCategory(
|
||||||
id: id, name: _ctrName.text, color: _ctrColor);
|
server: widget.server,
|
||||||
// TODO: cache category
|
room: widget.tag,
|
||||||
|
id: id,
|
||||||
|
name: _ctrName.text,
|
||||||
|
color: _ctrColor);
|
||||||
|
// cache category
|
||||||
|
await cat.toDisk();
|
||||||
|
|
||||||
// go back
|
// go back
|
||||||
router.pop();
|
router.pop();
|
||||||
|
@ -229,11 +234,13 @@ class _EditCategoryPageState extends State<EditCategoryPage> {
|
||||||
}),
|
}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final cat = RoomCategory(
|
final cat = RoomCategory(
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.tag,
|
||||||
id: widget.id!,
|
id: widget.id!,
|
||||||
name: _ctrName.text,
|
name: _ctrName.text,
|
||||||
color: _ctrColor);
|
color: _ctrColor);
|
||||||
// TODO: cache category
|
// cache category
|
||||||
|
await cat.toDisk();
|
||||||
// go back
|
// go back
|
||||||
router.pop();
|
router.pop();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,10 +14,13 @@ class EditItemPage extends StatefulWidget {
|
||||||
final String room;
|
final String room;
|
||||||
final String server;
|
final String server;
|
||||||
|
|
||||||
final int? item;
|
final int item;
|
||||||
|
|
||||||
const EditItemPage(
|
const EditItemPage(
|
||||||
{super.key, required this.room, required this.server, this.item});
|
{super.key,
|
||||||
|
required this.room,
|
||||||
|
required this.server,
|
||||||
|
required this.item});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _EditItemPageState();
|
State<StatefulWidget> createState() => _EditItemPageState();
|
||||||
|
@ -31,18 +34,26 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
int _ctrUnit = 0;
|
int _ctrUnit = 0;
|
||||||
String _ctrValue = '';
|
String _ctrValue = '';
|
||||||
int? _ctrLink;
|
int? _ctrLink;
|
||||||
|
int _ctrState = 0;
|
||||||
|
|
||||||
// data cache
|
// data cache
|
||||||
List<RoomCategory> categories = [];
|
List<RoomCategory> categories = [];
|
||||||
List<RoomProduct> products = [];
|
List<RoomProduct> products = [];
|
||||||
RoomItem? item;
|
RoomItem? item;
|
||||||
|
|
||||||
void fetchCategories() {
|
void fetchCategories() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached categories first
|
// load cached categories
|
||||||
|
final cache = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
categories = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -50,7 +61,8 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
body: {'room': widget.room, 'server': widget.server}),
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
.map<RoomCategory>((raw) =>
|
||||||
|
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -59,12 +71,19 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchProducts() {
|
void fetchProducts() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached products first
|
// load cached products first
|
||||||
|
final cache = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
products = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -72,7 +91,8 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
body: {'room': widget.room, 'server': widget.server}),
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
.map<RoomProduct>((raw) =>
|
||||||
|
RoomProduct.fromJSON(widget.server, widget.room, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -81,12 +101,19 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchItem() {
|
void fetchItem() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached item first
|
// load cached item first
|
||||||
|
try {
|
||||||
|
await RoomItem.fromDisk(
|
||||||
|
id: widget.item, server: widget.server, room: widget.room);
|
||||||
|
} catch (_) {
|
||||||
|
// cache miss
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -97,9 +124,17 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
'listItemID': widget.item
|
'listItemID': widget.item
|
||||||
}),
|
}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = RoomItem.fromJSON(body['data']);
|
final resp =
|
||||||
|
RoomItem.fromJSON(widget.server, widget.room, body['data']);
|
||||||
setState(() {
|
setState(() {
|
||||||
item = resp;
|
item = resp;
|
||||||
|
_ctrName.text = resp.name;
|
||||||
|
_ctrDescription.text = resp.description;
|
||||||
|
_ctrValue = resp.value;
|
||||||
|
_ctrCategory = resp.category;
|
||||||
|
_ctrLink = resp.link;
|
||||||
|
_ctrUnit = resp.unit;
|
||||||
|
_ctrState = resp.state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -108,13 +143,31 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room product changes
|
||||||
|
RoomProduct.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
setState(() {
|
||||||
|
products = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for background room category changes
|
||||||
|
RoomCategory.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
setState(() {
|
||||||
|
categories = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
|
|
||||||
if (widget.item != null) {
|
fetchItem();
|
||||||
fetchItem();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,16 +175,14 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text((widget.item == null)
|
title: Text(AppLocalizations.of(context)!.editItem),
|
||||||
? AppLocalizations.of(context)!.createItem
|
|
||||||
: AppLocalizations.of(context)!.editItem),
|
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 400),
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
@ -198,6 +249,8 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CategoryPicker(
|
CategoryPicker(
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
label: AppLocalizations.of(context)!
|
label: AppLocalizations.of(context)!
|
||||||
.selectCategoryLabel,
|
.selectCategoryLabel,
|
||||||
hint: AppLocalizations.of(context)!
|
hint: AppLocalizations.of(context)!
|
||||||
|
@ -227,52 +280,43 @@ class _EditItemPageState extends State<EditItemPage> {
|
||||||
|
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
|
||||||
if (widget.item == null) {
|
doNetworkRequest(scaffMgr,
|
||||||
doNetworkRequest(scaffMgr,
|
req: () => postWithCreadentials(
|
||||||
req: () => postWithCreadentials(
|
credentials: user,
|
||||||
credentials: user,
|
target: user.server,
|
||||||
target: user.server,
|
path: 'changeItem',
|
||||||
path: 'addItem',
|
body: {
|
||||||
body: {
|
'listItemID': widget.item,
|
||||||
'room': widget.room,
|
'room': widget.room,
|
||||||
'server': widget.server,
|
'server': widget.server,
|
||||||
'state': 0,
|
'title': _ctrName.text,
|
||||||
'title': _ctrName.text,
|
'state': _ctrState,
|
||||||
'description': _ctrDescription.text,
|
'description': _ctrDescription.text,
|
||||||
'listCatID': _ctrCategory,
|
'listCatID': _ctrCategory,
|
||||||
'unit': _ctrUnit,
|
'unit': _ctrUnit,
|
||||||
'value': _ctrValue,
|
'value': _ctrValue,
|
||||||
'listProdID': _ctrLink
|
'listProdID': _ctrLink
|
||||||
}),
|
}),
|
||||||
onOK: (_) async {
|
onOK: (_) async {
|
||||||
nav.pop();
|
// cache item
|
||||||
});
|
final item = RoomItem(
|
||||||
} else {
|
id: widget.item,
|
||||||
doNetworkRequest(scaffMgr,
|
server: widget.server,
|
||||||
req: () => postWithCreadentials(
|
room: widget.room,
|
||||||
credentials: user,
|
name: _ctrName.text,
|
||||||
target: user.server,
|
state: _ctrState,
|
||||||
path: 'changeItem',
|
description: _ctrDescription.text,
|
||||||
body: {
|
category: _ctrCategory,
|
||||||
'listItemID': widget.item,
|
unit: _ctrUnit,
|
||||||
'room': widget.room,
|
value: _ctrValue,
|
||||||
'server': widget.server,
|
link: _ctrLink);
|
||||||
'title': _ctrName.text,
|
await item.toDisk();
|
||||||
'description': _ctrDescription.text,
|
|
||||||
'listCatID': _ctrCategory,
|
nav.pop();
|
||||||
'defUnit': _ctrUnit,
|
});
|
||||||
'defValue': _ctrValue,
|
|
||||||
'listProdID': _ctrLink
|
|
||||||
}),
|
|
||||||
onOK: (_) async {
|
|
||||||
nav.pop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
label: Text(widget.item != null
|
label: Text(AppLocalizations.of(context)!.editItemShort),
|
||||||
? AppLocalizations.of(context)!.editItemShort
|
icon: const Icon(Icons.edit)),
|
||||||
: AppLocalizations.of(context)!.createItemShort),
|
|
||||||
icon: Icon(widget.item != null ? Icons.edit : Icons.add)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
321
lib/screens/room/items/new.dart
Normal file
321
lib/screens/room/items/new.dart
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:outbag_app/backend/request.dart';
|
||||||
|
import 'package:outbag_app/backend/room.dart';
|
||||||
|
import 'package:outbag_app/backend/user.dart';
|
||||||
|
import 'package:outbag_app/components/category_chip.dart';
|
||||||
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class NewItemPage extends StatefulWidget {
|
||||||
|
final String room;
|
||||||
|
final String server;
|
||||||
|
|
||||||
|
final int? item;
|
||||||
|
|
||||||
|
const NewItemPage(
|
||||||
|
{super.key, required this.room, required this.server, this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _NewItemPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewItemPageState extends State<NewItemPage> {
|
||||||
|
// input controllers
|
||||||
|
final _ctrInput = TextEditingController();
|
||||||
|
|
||||||
|
// data cache
|
||||||
|
List<RoomCategory> categories = [];
|
||||||
|
List<RoomProduct> products = [];
|
||||||
|
|
||||||
|
void fetchCategories() async {
|
||||||
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
|
// load cached categories
|
||||||
|
final cache = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
categories = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doNetworkRequest(scaffmgr,
|
||||||
|
req: () => postWithCreadentials(
|
||||||
|
credentials: user,
|
||||||
|
target: user.server,
|
||||||
|
path: 'getCategories',
|
||||||
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
|
onOK: (body) async {
|
||||||
|
final resp = body['data']
|
||||||
|
.map<RoomCategory>((raw) =>
|
||||||
|
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
categories = resp;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void fetchProducts() async {
|
||||||
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
|
// load cached products first
|
||||||
|
final cache = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
products = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doNetworkRequest(scaffmgr,
|
||||||
|
req: () => postWithCreadentials(
|
||||||
|
credentials: user,
|
||||||
|
target: user.server,
|
||||||
|
path: 'getProducts',
|
||||||
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
|
onOK: (body) async {
|
||||||
|
final resp = body['data']
|
||||||
|
.map<RoomProduct>((raw) =>
|
||||||
|
RoomProduct.fromJSON(widget.server, widget.room, raw))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
products = resp;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room product changes
|
||||||
|
RoomProduct.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
setState(() {
|
||||||
|
products = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for background room category changes
|
||||||
|
RoomCategory.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
setState(() {
|
||||||
|
categories = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
fetchCategories();
|
||||||
|
fetchProducts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String _query = "";
|
||||||
|
|
||||||
|
void createItem(BuildContext ctx, String name, int? productID) {
|
||||||
|
final scaffMgr = ScaffoldMessenger.of(context);
|
||||||
|
final router = GoRouter.of(context);
|
||||||
|
final user = context.read<User>();
|
||||||
|
|
||||||
|
doNetworkRequest(scaffMgr,
|
||||||
|
req: () => postWithCreadentials(
|
||||||
|
target: user.server,
|
||||||
|
credentials: user,
|
||||||
|
path: 'addItem',
|
||||||
|
body: {
|
||||||
|
'room': widget.room,
|
||||||
|
'server': widget.server,
|
||||||
|
'state': 0,
|
||||||
|
'title': name,
|
||||||
|
'description': '',
|
||||||
|
'listCatID': null,
|
||||||
|
'unit': 0,
|
||||||
|
'value': '',
|
||||||
|
'listProdID': productID
|
||||||
|
}),
|
||||||
|
onOK: (body) async {
|
||||||
|
final id = body["data"]["listItemID"];
|
||||||
|
// cache item
|
||||||
|
final item = RoomItem(
|
||||||
|
id: id,
|
||||||
|
room: widget.room,
|
||||||
|
server: widget.server,
|
||||||
|
state: 0,
|
||||||
|
name: name,
|
||||||
|
description: '',
|
||||||
|
category: null,
|
||||||
|
unit: 0,
|
||||||
|
value: '',
|
||||||
|
link: productID);
|
||||||
|
await item.toDisk();
|
||||||
|
// launch edit item screen
|
||||||
|
router.pushReplacementNamed('edit-item', params: {
|
||||||
|
'server': widget.server,
|
||||||
|
'id': widget.room,
|
||||||
|
'item': id.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void createProduct(BuildContext ctx, String name, Function(int) cb) {
|
||||||
|
final scaffMgr = ScaffoldMessenger.of(context);
|
||||||
|
final user = context.read<User>();
|
||||||
|
|
||||||
|
doNetworkRequest(scaffMgr,
|
||||||
|
req: () => postWithCreadentials(
|
||||||
|
target: user.server,
|
||||||
|
credentials: user,
|
||||||
|
path: 'addProduct',
|
||||||
|
body: {
|
||||||
|
'room': widget.room,
|
||||||
|
'server': widget.server,
|
||||||
|
'title': name,
|
||||||
|
'description': '',
|
||||||
|
'listCatID': null,
|
||||||
|
'defUnit': 0,
|
||||||
|
'defValue': '',
|
||||||
|
'ean': '',
|
||||||
|
'parent': null
|
||||||
|
}),
|
||||||
|
onOK: (body) async {
|
||||||
|
final id = body["data"]["listProdID"];
|
||||||
|
// cache product
|
||||||
|
final prod = RoomProduct(
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
|
description: '',
|
||||||
|
category: null,
|
||||||
|
defaultUnit: 0,
|
||||||
|
defaultValue: '',
|
||||||
|
ean: '',
|
||||||
|
parent: null);
|
||||||
|
await prod.toDisk();
|
||||||
|
|
||||||
|
cb(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text((widget.item == null)
|
||||||
|
? AppLocalizations.of(context)!.createItem
|
||||||
|
: AppLocalizations.of(context)!.editItem),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 400),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SearchBar(
|
||||||
|
controller: _ctrInput,
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
hintText:
|
||||||
|
AppLocalizations.of(context)!.newItemQueryHint,
|
||||||
|
onChanged: (text) {
|
||||||
|
setState(() {
|
||||||
|
_query = text;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
// button bar
|
||||||
|
...((_query == "")
|
||||||
|
? ([
|
||||||
|
Text(AppLocalizations.of(context)!
|
||||||
|
.newItemQueryEmpty)
|
||||||
|
])
|
||||||
|
: ([
|
||||||
|
Wrap(
|
||||||
|
spacing: 20,
|
||||||
|
runSpacing: 10,
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context)!
|
||||||
|
.newItemQuickAccessPrefix),
|
||||||
|
// new item
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// create new named item
|
||||||
|
// launch edit item screen once done
|
||||||
|
createItem(context, _query, null);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(_query)),
|
||||||
|
// new product
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
// create new product with name,
|
||||||
|
// create new item with name
|
||||||
|
// and link to the created product
|
||||||
|
// launch edit item screen once done
|
||||||
|
createProduct(
|
||||||
|
context,
|
||||||
|
_query,
|
||||||
|
(p0) => createItem(
|
||||||
|
context, _query, p0));
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.newItemQuickProduct(_query))),
|
||||||
|
])
|
||||||
|
])),
|
||||||
|
const Divider(),
|
||||||
|
// link products search
|
||||||
|
...((products
|
||||||
|
// show all products if query is empty
|
||||||
|
// when query isn't empty show products
|
||||||
|
// that contain the query in the title
|
||||||
|
// or description
|
||||||
|
.where((element) =>
|
||||||
|
(_query == "") ||
|
||||||
|
element.name.contains(_query) ||
|
||||||
|
element.description.contains(_query))
|
||||||
|
.map((e) => ListTile(
|
||||||
|
title: Text(e.name),
|
||||||
|
subtitle: Text(e.description),
|
||||||
|
trailing: CategoryChip(
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
|
category: categories
|
||||||
|
.where((element) =>
|
||||||
|
element.id == e.category)
|
||||||
|
.firstOrNull ??
|
||||||
|
RoomCategory.other(widget.server,
|
||||||
|
widget.room, context),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
// create new item and link it to the product
|
||||||
|
// launch edit item screen once done
|
||||||
|
createItem(
|
||||||
|
context,
|
||||||
|
// use productname as item name
|
||||||
|
e.name,
|
||||||
|
e.id);
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
],
|
||||||
|
))))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -105,7 +105,7 @@ class _JoinRoomPageState extends State {
|
||||||
icon: const Icon(Icons.search),
|
icon: const Icon(Icons.search),
|
||||||
tooltip: AppLocalizations.of(context)!.search,
|
tooltip: AppLocalizations.of(context)!.search,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// show searchbar
|
// TODO: show searchbar
|
||||||
// NOTE: location currently unknown
|
// NOTE: location currently unknown
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -170,7 +170,8 @@ class _JoinRoomPageState extends State {
|
||||||
return BottomSheet(
|
return BottomSheet(
|
||||||
onClosing: () {},
|
onClosing: () {},
|
||||||
builder: (ctx) {
|
builder: (ctx) {
|
||||||
return Column(
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment.center,
|
CrossAxisAlignment.center,
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
|
@ -282,7 +283,7 @@ class _JoinRoomPageState extends State {
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
);
|
));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,303 +33,383 @@ class _AboutRoomPageState extends State<AboutRoomPage> {
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(children: [
|
child: Padding(
|
||||||
// room meta display
|
|
||||||
...(widget.room != null)
|
|
||||||
? [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: Column(
|
child: ConstrainedBox(
|
||||||
children: [
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
SvgPicture.asset(
|
child: Column(children: [
|
||||||
(widget.room?.icon?.img)!,
|
// room meta display
|
||||||
width: smallest * 0.2,
|
...(widget.room != null)
|
||||||
height: smallest * 0.2,
|
? [
|
||||||
),
|
Padding(
|
||||||
Text(
|
padding: const EdgeInsets.all(14),
|
||||||
widget.room?.name ?? '',
|
child: Column(
|
||||||
style: textTheme.displayMedium,
|
children: [
|
||||||
),
|
SvgPicture.asset(
|
||||||
Text(
|
(widget.room?.icon?.img)!,
|
||||||
'${widget.room?.id}@${widget.room?.serverTag}',
|
width: smallest * 0.2,
|
||||||
style: textTheme.bodySmall,
|
height: smallest * 0.2,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.room?.description ?? '',
|
widget.room?.name ?? '',
|
||||||
style: textTheme.bodyMedium,
|
style: textTheme.displayMedium,
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
),
|
Text(
|
||||||
Padding(
|
'${widget.room?.id}@${widget.room?.serverTag}',
|
||||||
padding: const EdgeInsets.all(8),
|
style: textTheme.bodySmall,
|
||||||
child: SegmentedButton<int>(
|
),
|
||||||
showSelectedIcon: true,
|
Text(
|
||||||
multiSelectionEnabled: false,
|
widget.room?.description ?? '',
|
||||||
emptySelectionAllowed: false,
|
style: textTheme.bodyMedium,
|
||||||
segments: RoomVisibility.list().map((vis) {
|
textAlign: TextAlign.center,
|
||||||
return ButtonSegment<int>(
|
),
|
||||||
value: vis.type,
|
Padding(
|
||||||
label: Text(vis.text(context)),
|
padding: const EdgeInsets.all(8),
|
||||||
icon: Icon(vis.icon));
|
child: SegmentedButton<int>(
|
||||||
}).toList(),
|
showSelectedIcon: true,
|
||||||
onSelectionChanged: ((vset) {
|
multiSelectionEnabled: false,
|
||||||
// check permission
|
emptySelectionAllowed: false,
|
||||||
// only show confirm dialog when user
|
segments:
|
||||||
// is admin, owner or has CHANGE_ADMIN permission
|
RoomVisibility.list().map((vis) {
|
||||||
if (widget.info == null ||
|
return ButtonSegment<int>(
|
||||||
(!(widget.info?.isAdmin ?? false) &&
|
value: vis.type,
|
||||||
!(widget.info?.isOwner ?? false) &&
|
label: Text(vis.text(context)),
|
||||||
((widget.info?.permissions)! &
|
icon: Icon(vis.icon));
|
||||||
RoomPermission.ota ==
|
}).toList(),
|
||||||
0))) {
|
onSelectionChanged: ((vset) {
|
||||||
// action not permitted
|
// check permission
|
||||||
// NOTE: no error dialog should be shown
|
// only show confirm dialog when user
|
||||||
// because the action is supposed to be hidden
|
// is admin, owner or has CHANGE_ADMIN permission
|
||||||
return;
|
if (widget.info == null ||
|
||||||
}
|
(!(widget.info?.isAdmin ??
|
||||||
|
false) &&
|
||||||
|
!(widget.info?.isOwner ??
|
||||||
|
false) &&
|
||||||
|
((widget.info
|
||||||
|
?.permissions)! &
|
||||||
|
RoomPermission
|
||||||
|
.ota ==
|
||||||
|
0))) {
|
||||||
|
// action not permitted
|
||||||
|
// NOTE: no error dialog should be shown
|
||||||
|
// because the action is supposed to be hidden
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final vis = RoomVisibility(vset.first);
|
final vis =
|
||||||
showDialog(
|
RoomVisibility(vset.first);
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (ctx) => AlertDialog(
|
context: context,
|
||||||
title: Text(AppLocalizations.of(context)!
|
builder: (ctx) => AlertDialog(
|
||||||
.changeRoomVisibilityTitle),
|
title: Text(AppLocalizations
|
||||||
content: Text(
|
.of(context)!
|
||||||
AppLocalizations.of(context)!
|
.changeRoomVisibilityTitle),
|
||||||
.changeRoomVisibilitySubtitle(
|
content: Text(AppLocalizations
|
||||||
vis.text(context))),
|
.of(context)!
|
||||||
actions: [
|
.changeRoomVisibilitySubtitle(
|
||||||
TextButton(
|
vis.text(
|
||||||
onPressed: () {
|
context))),
|
||||||
context.pop();
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final scaffMgr =
|
||||||
|
ScaffoldMessenger
|
||||||
|
.of(context);
|
||||||
|
final nav =
|
||||||
|
Navigator.of(
|
||||||
|
context);
|
||||||
|
final user = context
|
||||||
|
.read<User>();
|
||||||
|
|
||||||
|
doNetworkRequest(
|
||||||
|
scaffMgr,
|
||||||
|
req: () =>
|
||||||
|
postWithCreadentials(
|
||||||
|
path:
|
||||||
|
'setVisibility',
|
||||||
|
target: user
|
||||||
|
.server,
|
||||||
|
body: {
|
||||||
|
'room': widget
|
||||||
|
.room
|
||||||
|
?.id,
|
||||||
|
'server': (widget
|
||||||
|
.room
|
||||||
|
?.serverTag)!,
|
||||||
|
'visibility':
|
||||||
|
vset.first
|
||||||
|
},
|
||||||
|
credentials:
|
||||||
|
user),
|
||||||
|
onOK: (_) {
|
||||||
|
Room r = widget
|
||||||
|
.room!;
|
||||||
|
r.visibility =
|
||||||
|
vis;
|
||||||
|
r.toDisk();
|
||||||
|
},
|
||||||
|
after: () {
|
||||||
|
nav.pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.ok),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
selected: {
|
||||||
|
(widget.room?.visibility?.type)!
|
||||||
},
|
},
|
||||||
child: Text(
|
selectedIcon: Icon(
|
||||||
AppLocalizations.of(context)!
|
(widget.room?.visibility?.icon)!),
|
||||||
.cancel),
|
)),
|
||||||
),
|
],
|
||||||
FilledButton(
|
),
|
||||||
onPressed: () async {
|
)
|
||||||
final scaffMgr =
|
]
|
||||||
ScaffoldMessenger.of(context);
|
: [],
|
||||||
final nav = Navigator.of(context);
|
|
||||||
final user = context.read<User>();
|
|
||||||
|
|
||||||
doNetworkRequest(scaffMgr,
|
Padding(
|
||||||
req: () => postWithCreadentials(
|
padding: const EdgeInsets.all(14),
|
||||||
path: 'setVisibility',
|
child: Column(
|
||||||
target: user.server,
|
children: [
|
||||||
body: {
|
// edit room meta button
|
||||||
'room': widget.room?.id,
|
...(widget.info != null &&
|
||||||
'server': (widget
|
((widget.info?.isAdmin ?? false) ||
|
||||||
.room?.serverTag)!,
|
(widget.info?.isOwner ?? false) ||
|
||||||
'visibility': vset.first
|
((widget.info?.permissions)! &
|
||||||
},
|
RoomPermission.changeMeta !=
|
||||||
credentials: user),
|
0)))
|
||||||
onOK: (_) {
|
? [
|
||||||
Room r = widget.room!;
|
ListTile(
|
||||||
r.visibility = vis;
|
trailing:
|
||||||
r.toDisk();
|
const Icon(Icons.chevron_right),
|
||||||
},
|
title: Text(
|
||||||
after: () {
|
AppLocalizations.of(context)!
|
||||||
nav.pop();
|
.editRoomMetadata),
|
||||||
});
|
subtitle: Text(
|
||||||
},
|
AppLocalizations.of(context)!
|
||||||
child: Text(
|
.editRoomMetadataSubtitle),
|
||||||
AppLocalizations.of(context)!.ok),
|
onTap: () {
|
||||||
)
|
// show edit room screen
|
||||||
],
|
context.goNamed('edit-room', params: {
|
||||||
));
|
'server': (widget.room?.serverTag)!,
|
||||||
}),
|
'id': (widget.room?.id)!
|
||||||
selected: {(widget.room?.visibility?.type)!},
|
});
|
||||||
selectedIcon: Icon((widget.room?.visibility?.icon)!),
|
},
|
||||||
)),
|
),
|
||||||
],
|
]
|
||||||
),
|
: [],
|
||||||
)
|
// open members view
|
||||||
]
|
ListTile(
|
||||||
: [],
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
title: Text(AppLocalizations.of(context)!
|
||||||
Padding(
|
.showRoomMembers),
|
||||||
padding: const EdgeInsets.all(14),
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
child: Column(
|
.showRoomMembersSubtitle),
|
||||||
children: [
|
onTap: () {
|
||||||
// edit room meta button
|
// open member view screen
|
||||||
...(widget.info != null &&
|
context.goNamed('room-members', params: {
|
||||||
((widget.info?.isAdmin ?? false) ||
|
'server': (widget.room?.serverTag)!,
|
||||||
(widget.info?.isOwner ?? false) ||
|
'id': (widget.room?.id)!
|
||||||
((widget.info?.permissions)! &
|
});
|
||||||
RoomPermission.changeMeta !=
|
},
|
||||||
0)))
|
),
|
||||||
? [
|
// edit default member permission
|
||||||
ListTile(
|
...(widget.info != null &&
|
||||||
trailing: const Icon(Icons.chevron_right),
|
((widget.info?.isAdmin ?? false) ||
|
||||||
title: Text(
|
(widget.info?.isOwner ?? false) ||
|
||||||
AppLocalizations.of(context)!.editRoomMetadata),
|
((widget.info?.permissions)! &
|
||||||
subtitle: Text(AppLocalizations.of(context)!
|
RoomPermission.changeAdmin !=
|
||||||
.editRoomMetadataSubtitle),
|
0)))
|
||||||
onTap: () {
|
? [
|
||||||
// show edit room screen
|
ListTile(
|
||||||
context.goNamed('edit-room', params: {
|
trailing:
|
||||||
'server': (widget.room?.serverTag)!,
|
const Icon(Icons.chevron_right),
|
||||||
'id': (widget.room?.id)!
|
title: Text(
|
||||||
});
|
AppLocalizations.of(context)!
|
||||||
},
|
.editRoomPermissions),
|
||||||
),
|
subtitle: Text(
|
||||||
]
|
AppLocalizations.of(context)!
|
||||||
: [],
|
.editRoomPermissionsSubtitle),
|
||||||
// open members view
|
onTap: () {
|
||||||
ListTile(
|
// show checkbox screen
|
||||||
trailing: const Icon(Icons.chevron_right),
|
context.goNamed('room-permissions',
|
||||||
title: Text(AppLocalizations.of(context)!.showRoomMembers),
|
params: {
|
||||||
subtitle:
|
|
||||||
Text(AppLocalizations.of(context)!.showRoomMembersSubtitle),
|
|
||||||
onTap: () {
|
|
||||||
// open member view screen
|
|
||||||
context.goNamed('room-members', params: {
|
|
||||||
'server': (widget.room?.serverTag)!,
|
|
||||||
'id': (widget.room?.id)!
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// edit default member permission
|
|
||||||
...(widget.info != null &&
|
|
||||||
((widget.info?.isAdmin ?? false) ||
|
|
||||||
(widget.info?.isOwner ?? false) ||
|
|
||||||
((widget.info?.permissions)! &
|
|
||||||
RoomPermission.changeAdmin !=
|
|
||||||
0)))
|
|
||||||
? [
|
|
||||||
ListTile(
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
title: Text(
|
|
||||||
AppLocalizations.of(context)!.editRoomPermissions),
|
|
||||||
subtitle: Text(AppLocalizations.of(context)!
|
|
||||||
.editRoomPermissionsSubtitle),
|
|
||||||
onTap: () {
|
|
||||||
// show checkbox screen
|
|
||||||
context.goNamed('room-permissions', params: {
|
|
||||||
'server': (widget.room?.serverTag)!,
|
|
||||||
'id': (widget.room?.id)!
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
...(widget.info != null &&
|
|
||||||
((widget.info?.isAdmin ?? false) ||
|
|
||||||
(widget.info?.isOwner ?? false) ||
|
|
||||||
((widget.info?.permissions)! & RoomPermission.ota !=
|
|
||||||
0)))
|
|
||||||
? [
|
|
||||||
ListTile(
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
title:
|
|
||||||
Text(AppLocalizations.of(context)!.manageRoomOTA),
|
|
||||||
subtitle: Text(AppLocalizations.of(context)!
|
|
||||||
.manageRoomOTASubtitle),
|
|
||||||
onTap: () {
|
|
||||||
// show manage ota screen
|
|
||||||
context.goNamed('room-ota', params: {
|
|
||||||
'server': (widget.room?.serverTag)!,
|
|
||||||
'id': (widget.room?.id)!
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
title: Text(
|
|
||||||
AppLocalizations.of(context)!.manageRoomInvites),
|
|
||||||
subtitle: Text(AppLocalizations.of(context)!
|
|
||||||
.manageRoomInvitesSubtitle),
|
|
||||||
onTap: () {
|
|
||||||
// show manage ota screen
|
|
||||||
context.goNamed('room-invite', params: {
|
|
||||||
'server': (widget.room?.serverTag)!,
|
|
||||||
'id': (widget.room?.id)!
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
|
|
||||||
...(widget.info != null)
|
|
||||||
? [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: FilledButton.tonal(
|
|
||||||
child: Text(((widget.info?.isOwner)!)
|
|
||||||
? AppLocalizations.of(context)!.deleteRoom
|
|
||||||
: AppLocalizations.of(context)!.leaveRoom),
|
|
||||||
onPressed: () {
|
|
||||||
// show confirm dialog
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (ctx) => AlertDialog(
|
|
||||||
title: Text(((widget.info?.isOwner)!)
|
|
||||||
? AppLocalizations.of(context)!.deleteRoom
|
|
||||||
: AppLocalizations.of(context)!.leaveRoom),
|
|
||||||
content: Text(((widget.info?.isOwner)!)
|
|
||||||
? AppLocalizations.of(context)!
|
|
||||||
.deleteRoomConfirm
|
|
||||||
: AppLocalizations.of(context)!
|
|
||||||
.leaveRoomConfirm),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
// close popup
|
|
||||||
Navigator.of(ctx).pop();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!.cancel),
|
|
||||||
),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () async {
|
|
||||||
// send request
|
|
||||||
final scaffMgr =
|
|
||||||
ScaffoldMessenger.of(ctx);
|
|
||||||
final nav = Navigator.of(ctx);
|
|
||||||
final router = GoRouter.of(context);
|
|
||||||
final user = context.read<User>();
|
|
||||||
|
|
||||||
doNetworkRequest(scaffMgr,
|
|
||||||
req: () => postWithCreadentials(
|
|
||||||
path: ((widget.info?.isOwner)!)
|
|
||||||
? 'deleteRoom'
|
|
||||||
: 'leaveRoom',
|
|
||||||
target: user.server,
|
|
||||||
body: {
|
|
||||||
'room': widget.room?.id,
|
|
||||||
'server':
|
'server':
|
||||||
(widget.room?.serverTag)!,
|
(widget.room?.serverTag)!,
|
||||||
},
|
'id': (widget.room?.id)!
|
||||||
credentials: user),
|
});
|
||||||
onOK: (_) async {
|
},
|
||||||
// try delete room from disk
|
),
|
||||||
try {
|
]
|
||||||
await widget.room?.removeDisk();
|
: [],
|
||||||
} catch (_) {}
|
...(widget.info != null &&
|
||||||
|
((widget.info?.isAdmin ?? false) ||
|
||||||
// go back home
|
(widget.info?.isOwner ?? false) ||
|
||||||
router.pushReplacementNamed('home');
|
((widget.info?.permissions)! &
|
||||||
},
|
RoomPermission.ota !=
|
||||||
after: () {
|
0)))
|
||||||
// close popup
|
? [
|
||||||
nav.pop();
|
ListTile(
|
||||||
|
trailing:
|
||||||
|
const Icon(Icons.chevron_right),
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.manageRoomOTA),
|
||||||
|
subtitle: Text(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.manageRoomOTASubtitle),
|
||||||
|
onTap: () {
|
||||||
|
// show manage ota screen
|
||||||
|
context.goNamed('room-ota', params: {
|
||||||
|
'server': (widget.room?.serverTag)!,
|
||||||
|
'id': (widget.room?.id)!
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
trailing:
|
||||||
|
const Icon(Icons.chevron_right),
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.manageRoomInvites),
|
||||||
|
subtitle: Text(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.manageRoomInvitesSubtitle),
|
||||||
|
onTap: () {
|
||||||
|
// show manage ota screen
|
||||||
|
context
|
||||||
|
.goNamed('room-invite', params: {
|
||||||
|
'server': (widget.room?.serverTag)!,
|
||||||
|
'id': (widget.room?.id)!
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
|
||||||
|
...(widget.info != null)
|
||||||
|
? [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: FilledButton.tonal(
|
||||||
child: Text(((widget.info?.isOwner)!)
|
child: Text(((widget.info?.isOwner)!)
|
||||||
? AppLocalizations.of(context)!
|
? AppLocalizations.of(context)!
|
||||||
.deleteRoomShort
|
.deleteRoom
|
||||||
: AppLocalizations.of(context)!
|
: AppLocalizations.of(context)!
|
||||||
.leaveRoomShort),
|
.leaveRoom),
|
||||||
)
|
onPressed: () {
|
||||||
],
|
// show confirm dialog
|
||||||
));
|
showDialog(
|
||||||
},
|
context: context,
|
||||||
))
|
builder: (ctx) => AlertDialog(
|
||||||
]
|
title: Text(
|
||||||
: [],
|
((widget.info?.isOwner)!)
|
||||||
])));
|
? AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.deleteRoom
|
||||||
|
: AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.leaveRoom),
|
||||||
|
content: Text(
|
||||||
|
((widget.info?.isOwner)!)
|
||||||
|
? AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.deleteRoomConfirm
|
||||||
|
: AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.leaveRoomConfirm),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
// close popup
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// send request
|
||||||
|
final scaffMgr =
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
ctx);
|
||||||
|
final nav =
|
||||||
|
Navigator.of(ctx);
|
||||||
|
final router =
|
||||||
|
GoRouter.of(context);
|
||||||
|
final user =
|
||||||
|
context.read<User>();
|
||||||
|
|
||||||
|
doNetworkRequest(scaffMgr,
|
||||||
|
req: () =>
|
||||||
|
postWithCreadentials(
|
||||||
|
path: ((widget
|
||||||
|
.info
|
||||||
|
?.isOwner)!)
|
||||||
|
? 'deleteRoom'
|
||||||
|
: 'leaveRoom',
|
||||||
|
target: user
|
||||||
|
.server,
|
||||||
|
body: {
|
||||||
|
'room': widget
|
||||||
|
.room
|
||||||
|
?.id,
|
||||||
|
'server': (widget
|
||||||
|
.room
|
||||||
|
?.serverTag)!,
|
||||||
|
},
|
||||||
|
credentials:
|
||||||
|
user),
|
||||||
|
onOK: (_) async {
|
||||||
|
// try delete room from disk
|
||||||
|
try {
|
||||||
|
await widget.room
|
||||||
|
?.removeDisk();
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
// go back home
|
||||||
|
router
|
||||||
|
.pushReplacementNamed(
|
||||||
|
'home');
|
||||||
|
},
|
||||||
|
after: () {
|
||||||
|
// close popup
|
||||||
|
nav.pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(((widget
|
||||||
|
.info?.isOwner)!)
|
||||||
|
? AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.deleteRoomShort
|
||||||
|
: AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.leaveRoomShort),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
},
|
||||||
|
))
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
])))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,18 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room category changes
|
||||||
|
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
||||||
|
(_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomCategory.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
setState(() {
|
||||||
|
list = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
});
|
});
|
||||||
|
@ -32,10 +44,18 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
||||||
|
|
||||||
void fetchCategories() async {
|
void fetchCategories() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached rooms
|
// load cached categories
|
||||||
|
final cache = await RoomCategory.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
list = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -43,8 +63,12 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
||||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||||
onOK: (json) {
|
onOK: (json) {
|
||||||
final resp = json['data']
|
final resp = json['data']
|
||||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
.map<RoomCategory>((raw) => RoomCategory.fromJSON(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
for (RoomCategory ce in resp) {
|
||||||
|
ce.toDisk();
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -61,178 +85,210 @@ class _RoomCategoriesPageState extends State<RoomCategoriesPage> {
|
||||||
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
.apply(displayColor: Theme.of(context).colorScheme.onSurface);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: ReorderableListView.builder(
|
body: Center(
|
||||||
buildDefaultDragHandles: false,
|
child: Padding(
|
||||||
itemBuilder: (context, index) {
|
padding: const EdgeInsets.all(14),
|
||||||
final item = list[index];
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
|
child: ReorderableListView.builder(
|
||||||
|
buildDefaultDragHandles: false,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = list[index];
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
key: Key('cat-${item.id}'),
|
key: Key('cat-${item.id}'),
|
||||||
leading: Icon(Icons.square_rounded, color: item.color),
|
leading: Icon(Icons.square_rounded, color: item.color),
|
||||||
trailing: ((widget.info?.isAdmin ?? false) ||
|
trailing: ((widget.info?.isAdmin ?? false) ||
|
||||||
(widget.info?.isOwner ?? false) ||
|
(widget.info?.isOwner ?? false) ||
|
||||||
((widget.info?.permissions)! &
|
((widget.info?.permissions)! &
|
||||||
RoomPermission.editRoomContent !=
|
RoomPermission.editRoomContent !=
|
||||||
0))
|
0))
|
||||||
? ReorderableDragStartListener(
|
? ReorderableDragStartListener(
|
||||||
index: index, child: const Icon(Icons.drag_handle))
|
index: index,
|
||||||
: null,
|
child: const Icon(Icons.drag_handle))
|
||||||
title: Text(item.name),
|
: null,
|
||||||
onTap: () {
|
title: Text(item.name),
|
||||||
// TODO show edit category popup
|
onTap: () {
|
||||||
// NOTE: maybe use ModalBottomSheet
|
// TODO show edit category popup
|
||||||
// and show delete button in there
|
// NOTE: maybe use ModalBottomSheet
|
||||||
|
// and show delete button in there
|
||||||
|
|
||||||
if (!((widget.info?.isAdmin ?? false) ||
|
if (!((widget.info?.isAdmin ?? false) ||
|
||||||
(widget.info?.isOwner ?? false) ||
|
(widget.info?.isOwner ?? false) ||
|
||||||
((widget.info?.permissions)! &
|
((widget.info?.permissions)! &
|
||||||
RoomPermission.editRoomContent !=
|
RoomPermission.editRoomContent !=
|
||||||
0))) {
|
0))) {
|
||||||
// user is not allowed to edit or delete categories
|
// user is not allowed to edit or delete categories
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
|
||||||
builder: (context) => BottomSheet(
|
|
||||||
builder: (context) => Column(children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.square_rounded,
|
|
||||||
size: 48.0, color: item.color),
|
|
||||||
Text(item.name, style: textTheme.titleLarge)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
// edit category
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.edit),
|
|
||||||
title: Text(AppLocalizations.of(context)!.editCategory),
|
|
||||||
subtitle:
|
|
||||||
Text(AppLocalizations.of(context)!.editCategoryLong),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
// close the modal bottom sheet
|
|
||||||
// so the user returns to the list,
|
|
||||||
// when leaving the category editor
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
// launch category editor
|
|
||||||
context.pushNamed('edit-category', params: {
|
|
||||||
'server': widget.room!.serverTag,
|
|
||||||
'id': widget.room!.id,
|
|
||||||
'category': item.id.toString()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// delete category
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Icons.delete),
|
|
||||||
title: Text(AppLocalizations.of(context)!.deleteCategory),
|
|
||||||
subtitle: Text(
|
|
||||||
AppLocalizations.of(context)!.deleteCategoryLong),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
// show popup
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (context) => BottomSheet(
|
||||||
icon: const Icon(Icons.delete),
|
builder: (context) => Column(children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.square_rounded,
|
||||||
|
size: 48.0, color: item.color),
|
||||||
|
Text(item.name,
|
||||||
|
style: textTheme.titleLarge)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
// edit category
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.edit),
|
||||||
|
title: Text(AppLocalizations.of(context)!
|
||||||
|
.editCategory),
|
||||||
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
|
.editCategoryLong),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
// close the modal bottom sheet
|
||||||
|
// so the user returns to the list,
|
||||||
|
// when leaving the category editor
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
// launch category editor
|
||||||
|
context.pushNamed('edit-category', params: {
|
||||||
|
'server': widget.room!.serverTag,
|
||||||
|
'id': widget.room!.id,
|
||||||
|
'category': item.id.toString()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// delete category
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.delete),
|
||||||
title: Text(AppLocalizations.of(context)!
|
title: Text(AppLocalizations.of(context)!
|
||||||
.deleteCategory),
|
.deleteCategory),
|
||||||
content: Text(AppLocalizations.of(context)!
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
.deleteCategoryConfirm(item.name)),
|
.deleteCategoryLong),
|
||||||
actions: [
|
trailing: const Icon(Icons.chevron_right),
|
||||||
TextButton(
|
onTap: () {
|
||||||
onPressed: () {
|
// show popup
|
||||||
// close popup
|
showDialog(
|
||||||
Navigator.of(ctx).pop();
|
context: context,
|
||||||
},
|
builder: (ctx) => AlertDialog(
|
||||||
child: Text(
|
icon: const Icon(Icons.delete),
|
||||||
AppLocalizations.of(context)!.cancel),
|
title: Text(
|
||||||
),
|
AppLocalizations.of(context)!
|
||||||
FilledButton(
|
.deleteCategory),
|
||||||
onPressed: () async {
|
content: Text(
|
||||||
// send request
|
AppLocalizations.of(context)!
|
||||||
final scaffMgr =
|
.deleteCategoryConfirm(
|
||||||
ScaffoldMessenger.of(ctx);
|
item.name)),
|
||||||
// popup context
|
actions: [
|
||||||
final navInner = Navigator.of(ctx);
|
TextButton(
|
||||||
// bottomsheet context
|
onPressed: () {
|
||||||
final nav = Navigator.of(context);
|
// close popup
|
||||||
final user = context.read<User>();
|
Navigator.of(ctx).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// send request
|
||||||
|
final scaffMgr =
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
ctx);
|
||||||
|
// popup context
|
||||||
|
final navInner =
|
||||||
|
Navigator.of(ctx);
|
||||||
|
// bottomsheet context
|
||||||
|
final nav =
|
||||||
|
Navigator.of(context);
|
||||||
|
final user =
|
||||||
|
context.read<User>();
|
||||||
|
|
||||||
doNetworkRequest(scaffMgr,
|
doNetworkRequest(scaffMgr,
|
||||||
req: () => postWithCreadentials(
|
req: () =>
|
||||||
path: 'deleteCategory',
|
postWithCreadentials(
|
||||||
target: user.server,
|
path:
|
||||||
body: {
|
'deleteCategory',
|
||||||
'room': widget.room?.id,
|
target:
|
||||||
'server':
|
user.server,
|
||||||
widget.room?.serverTag,
|
body: {
|
||||||
'listCatID': item.id
|
'room': widget
|
||||||
},
|
.room?.id,
|
||||||
credentials: user),
|
'server': widget
|
||||||
onOK: (_) async {
|
.room
|
||||||
// TODO: remove cached category
|
?.serverTag,
|
||||||
fetchCategories();
|
'listCatID':
|
||||||
},
|
item.id
|
||||||
after: () {
|
},
|
||||||
// close popup
|
credentials:
|
||||||
navInner.pop();
|
user),
|
||||||
// close modal bottom sheet
|
onOK: (_) async {
|
||||||
nav.pop();
|
// remove cached category
|
||||||
});
|
item.removeDisk();
|
||||||
},
|
fetchCategories();
|
||||||
child: Text(AppLocalizations.of(context)!
|
},
|
||||||
.deleteCategory),
|
after: () {
|
||||||
)
|
// close popup
|
||||||
],
|
navInner.pop();
|
||||||
));
|
// close modal bottom sheet
|
||||||
},
|
nav.pop();
|
||||||
),
|
});
|
||||||
]),
|
},
|
||||||
onClosing: () {},
|
child: Text(
|
||||||
),
|
AppLocalizations.of(
|
||||||
);
|
context)!
|
||||||
},
|
.deleteCategory),
|
||||||
);
|
)
|
||||||
},
|
],
|
||||||
itemCount: list.length,
|
));
|
||||||
onReorder: (int oldIndex, int newIndex) {
|
},
|
||||||
if (!((widget.info?.isAdmin ?? false) ||
|
),
|
||||||
(widget.info?.isOwner ?? false) ||
|
]),
|
||||||
((widget.info?.permissions)! & RoomPermission.editRoomContent !=
|
onClosing: () {},
|
||||||
0))) {
|
),
|
||||||
// user is not allowed to edit or delete categories
|
);
|
||||||
return;
|
},
|
||||||
}
|
);
|
||||||
|
},
|
||||||
|
itemCount: list.length,
|
||||||
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
|
if (!((widget.info?.isAdmin ?? false) ||
|
||||||
|
(widget.info?.isOwner ?? false) ||
|
||||||
|
((widget.info?.permissions)! &
|
||||||
|
RoomPermission.editRoomContent !=
|
||||||
|
0))) {
|
||||||
|
// user is not allowed to edit or delete categories
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
if (oldIndex < newIndex) {
|
if (oldIndex < newIndex) {
|
||||||
newIndex -= 1;
|
newIndex -= 1;
|
||||||
}
|
}
|
||||||
final item = list.removeAt(oldIndex);
|
final item = list.removeAt(oldIndex);
|
||||||
list.insert(newIndex, item);
|
list.insert(newIndex, item);
|
||||||
|
|
||||||
// network request
|
// network request
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(ScaffoldMessenger.of(context),
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
path: 'changeCategoriesOrder',
|
path: 'changeCategoriesOrder',
|
||||||
body: {
|
body: {
|
||||||
'room': widget.room?.id,
|
'room': widget.room?.id,
|
||||||
'server': widget.room?.serverTag,
|
'server': widget.room?.serverTag,
|
||||||
'listCatIDs': list.map((item) => item.id).toList()
|
'listCatIDs':
|
||||||
}));
|
list.map((item) => item.id).toList()
|
||||||
});
|
}));
|
||||||
},
|
});
|
||||||
),
|
},
|
||||||
|
)))),
|
||||||
floatingActionButton: (widget.info != null &&
|
floatingActionButton: (widget.info != null &&
|
||||||
((widget.info?.isAdmin ?? false) ||
|
((widget.info?.isAdmin ?? false) ||
|
||||||
(widget.info?.isOwner ?? false) ||
|
(widget.info?.isOwner ?? false) ||
|
||||||
|
|
|
@ -5,9 +5,7 @@ import 'package:outbag_app/backend/request.dart';
|
||||||
import 'package:outbag_app/backend/room.dart';
|
import 'package:outbag_app/backend/room.dart';
|
||||||
import 'package:outbag_app/backend/user.dart';
|
import 'package:outbag_app/backend/user.dart';
|
||||||
import 'package:outbag_app/components/category_chip.dart';
|
import 'package:outbag_app/components/category_chip.dart';
|
||||||
import 'package:outbag_app/components/category_picker.dart';
|
|
||||||
import 'package:outbag_app/components/labeled_divider.dart';
|
import 'package:outbag_app/components/labeled_divider.dart';
|
||||||
import 'package:outbag_app/components/product_picker.dart';
|
|
||||||
import 'package:outbag_app/components/value_unit_input.dart';
|
import 'package:outbag_app/components/value_unit_input.dart';
|
||||||
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
import 'package:outbag_app/tools/fetch_wrapper.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -30,12 +28,37 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
Map<int?, RoomCategory> categories = {};
|
Map<int?, RoomCategory> categories = {};
|
||||||
List<RoomProduct> products = [];
|
List<RoomProduct> products = [];
|
||||||
|
|
||||||
void fetchItems() {
|
void fetchItems() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached items first
|
// load cached items first
|
||||||
|
final cache = await RoomItem.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
final List<RoomItem> l = [];
|
||||||
|
final List<RoomItem> c = [];
|
||||||
|
|
||||||
|
for (RoomItem item in cache) {
|
||||||
|
if (item.state == 0) {
|
||||||
|
l.add(item);
|
||||||
|
} else {
|
||||||
|
c.add(item);
|
||||||
|
}
|
||||||
|
// cache items
|
||||||
|
await item.toDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
list = l;
|
||||||
|
cart = c;
|
||||||
|
|
||||||
|
sortAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -43,7 +66,8 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomItem>((raw) => RoomItem.fromJSON(raw))
|
.map<RoomItem>((raw) => RoomItem.fromJSON(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final List<RoomItem> l = [];
|
final List<RoomItem> l = [];
|
||||||
|
@ -55,10 +79,10 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
} else {
|
} else {
|
||||||
c.add(item);
|
c.add(item);
|
||||||
}
|
}
|
||||||
|
// cache items
|
||||||
|
await item.toDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: cache items
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
list = l;
|
list = l;
|
||||||
|
@ -108,12 +132,31 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchCategories() {
|
void fetchCategories() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached categories first
|
// load cached categories
|
||||||
|
final resp = await RoomCategory.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
if (mounted) {
|
||||||
|
Map<int, int> map = {};
|
||||||
|
Map<int?, RoomCategory> cat = {};
|
||||||
|
for (int i = 0; i < resp.length; i++) {
|
||||||
|
map[resp[i].id ?? 0] = i;
|
||||||
|
cat[resp[i].id ?? 0] = resp[i];
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
weights = map;
|
||||||
|
categories = cat;
|
||||||
|
sortAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -121,7 +164,8 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
.map<RoomCategory>((raw) => RoomCategory.fromJSON(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "", raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
Map<int, int> map = {};
|
Map<int, int> map = {};
|
||||||
|
@ -141,12 +185,20 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchProducts() {
|
void fetchProducts() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached products first
|
// load cached products first
|
||||||
|
final cache = await RoomProduct.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
products = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -154,7 +206,8 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
.map<RoomProduct>((raw) => RoomProduct.fromJSON(
|
||||||
|
widget.room!.serverTag, widget.room!.id, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
@ -169,6 +222,68 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room item changes
|
||||||
|
RoomItem.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
||||||
|
(_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomItem.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
|
||||||
|
final List<RoomItem> l = [];
|
||||||
|
final List<RoomItem> c = [];
|
||||||
|
|
||||||
|
for (RoomItem item in updated) {
|
||||||
|
if (item.state == 0) {
|
||||||
|
l.add(item);
|
||||||
|
} else {
|
||||||
|
c.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
list = l;
|
||||||
|
cart = c;
|
||||||
|
|
||||||
|
sortAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
// wait for background room product changes
|
||||||
|
RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
||||||
|
(_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomProduct.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
setState(() {
|
||||||
|
products = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
// wait for background room category changes
|
||||||
|
RoomCategory.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
||||||
|
(_) async {
|
||||||
|
try {
|
||||||
|
final resp = await RoomCategory.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
Map<int, int> map = {};
|
||||||
|
Map<int?, RoomCategory> cat = {};
|
||||||
|
for (int i = 0; i < resp.length; i++) {
|
||||||
|
map[resp[i].id ?? 0] = i;
|
||||||
|
cat[resp[i].id ?? 0] = resp[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
weights = map;
|
||||||
|
categories = cat;
|
||||||
|
sortAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
fetchItems();
|
fetchItems();
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
|
@ -194,110 +309,121 @@ class _ShoppingListPageState extends State<ShoppingListPage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: ListView(children: [
|
body: Center(
|
||||||
LabeledDivider(AppLocalizations.of(context)!.shoppingList),
|
child: Padding(
|
||||||
ListView.builder(
|
padding: const EdgeInsets.all(14),
|
||||||
shrinkWrap: true,
|
child: ConstrainedBox(
|
||||||
physics: const ClampingScrollPhysics(),
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
itemCount: list.length,
|
child: ListView(children: [
|
||||||
itemBuilder: (context, index) {
|
LabeledDivider(AppLocalizations.of(context)!.shoppingList),
|
||||||
final item = list[index];
|
ListView.builder(
|
||||||
final cat =
|
shrinkWrap: true,
|
||||||
categories[item.category] ?? RoomCategory.other(context);
|
physics: const ClampingScrollPhysics(),
|
||||||
return ShoppingListItem(
|
itemCount: list.length,
|
||||||
name: item.name,
|
itemBuilder: (context, index) {
|
||||||
description: item.description,
|
final item = list[index];
|
||||||
category: cat,
|
final cat = categories[item.category] ??
|
||||||
key: Key(item.id.toString()),
|
RoomCategory.other(widget.room?.serverTag ?? "",
|
||||||
inCart: item.state != 0,
|
widget.room?.id ?? "", context);
|
||||||
onDismiss: () {
|
return ShoppingListItem(
|
||||||
// move to cart
|
name: item.name,
|
||||||
item.state = 1;
|
server: widget.room!.serverTag,
|
||||||
|
room: widget.room!.id,
|
||||||
|
description: item.description,
|
||||||
|
category: cat,
|
||||||
|
key: Key(item.id.toString()),
|
||||||
|
inCart: item.state != 0,
|
||||||
|
onDismiss: () {
|
||||||
|
// move to cart
|
||||||
|
item.state = 1;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
list.removeAt(index);
|
list.removeAt(index);
|
||||||
cart.add(item);
|
cart.add(item);
|
||||||
sortAll();
|
sortAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
// network request
|
// network request
|
||||||
// NOTE: for now we do not care if it is successfull,
|
// NOTE: for now we do not care if it is successfull,
|
||||||
// because the shopping cart is supposed to work even
|
// because the shopping cart is supposed to work even
|
||||||
// without network
|
// without network
|
||||||
changeItemState(item);
|
changeItemState(item);
|
||||||
},
|
},
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: show modal bottom sheet
|
// TODO: show modal bottom sheet
|
||||||
// containing
|
// containing
|
||||||
// - item info
|
// - item info
|
||||||
// - option to view linked product (if available)
|
// - option to view linked product (if available)
|
||||||
// - edit item (if allowed)
|
// - edit item (if allowed)
|
||||||
// - delete item (if allowed)
|
// - delete item (if allowed)
|
||||||
// - move to/from shopping cart?
|
// - move to/from shopping cart?
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ShoppingListItemInfo(
|
builder: (context) => ShoppingListItemInfo(
|
||||||
products: products,
|
products: products,
|
||||||
category: cat,
|
category: cat,
|
||||||
info: widget.info,
|
info: widget.info,
|
||||||
item: item,
|
item: item,
|
||||||
room: widget.room!.id,
|
room: widget.room!.id,
|
||||||
server: widget.room!.serverTag));
|
server: widget.room!.serverTag));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
LabeledDivider(AppLocalizations.of(context)!.shoppingCart),
|
LabeledDivider(AppLocalizations.of(context)!.shoppingCart),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
itemCount: cart.length,
|
itemCount: cart.length,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = cart[index];
|
final item = cart[index];
|
||||||
final cat =
|
final cat = categories[item.category] ??
|
||||||
categories[item.category] ?? RoomCategory.other(context);
|
RoomCategory.other(widget.room!.serverTag,
|
||||||
|
widget.room!.id, context);
|
||||||
|
|
||||||
return ShoppingListItem(
|
return ShoppingListItem(
|
||||||
name: item.name,
|
server: widget.room!.serverTag,
|
||||||
description: item.description,
|
room: widget.room!.id,
|
||||||
category: cat,
|
name: item.name,
|
||||||
key: Key(item.id.toString()),
|
description: item.description,
|
||||||
inCart: item.state != 0,
|
category: cat,
|
||||||
onDismiss: () {
|
key: Key(item.id.toString()),
|
||||||
// move back to list
|
inCart: item.state != 0,
|
||||||
item.state = 0;
|
onDismiss: () {
|
||||||
setState(() {
|
// move back to list
|
||||||
cart.removeAt(index);
|
item.state = 0;
|
||||||
list.add(item);
|
setState(() {
|
||||||
sortAll();
|
cart.removeAt(index);
|
||||||
});
|
list.add(item);
|
||||||
|
sortAll();
|
||||||
|
});
|
||||||
|
|
||||||
// network request
|
// network request
|
||||||
// NOTE: for now we do not care if it is successfull,
|
// NOTE: for now we do not care if it is successfull,
|
||||||
// because the shopping cart is supposed to work even
|
// because the shopping cart is supposed to work even
|
||||||
// without network
|
// without network
|
||||||
changeItemState(item);
|
changeItemState(item);
|
||||||
},
|
},
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// show modal bottom sheet
|
// show modal bottom sheet
|
||||||
// containing
|
// containing
|
||||||
// - item info
|
// - item info
|
||||||
// - option to view linked product (if available)
|
// - option to view linked product (if available)
|
||||||
// - edit item (if allowed)
|
// - edit item (if allowed)
|
||||||
// - delete item (if allowed)
|
// - delete item (if allowed)
|
||||||
// - move to/from shopping cart?
|
// - move to/from shopping cart?
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ShoppingListItemInfo(
|
builder: (context) => ShoppingListItemInfo(
|
||||||
products: products,
|
products: products,
|
||||||
category: cat,
|
category: cat,
|
||||||
item: item,
|
item: item,
|
||||||
info: widget.info,
|
info: widget.info,
|
||||||
room: widget.room!.id,
|
room: widget.room!.id,
|
||||||
server: widget.room!.serverTag));
|
server: widget.room!.serverTag));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]),
|
])))),
|
||||||
floatingActionButton: (widget.info != null &&
|
floatingActionButton: (widget.info != null &&
|
||||||
((widget.info?.isAdmin ?? false) ||
|
((widget.info?.isAdmin ?? false) ||
|
||||||
(widget.info?.isOwner ?? false) ||
|
(widget.info?.isOwner ?? false) ||
|
||||||
|
@ -329,12 +455,16 @@ class ShoppingListItem extends StatelessWidget {
|
||||||
final Key _key;
|
final Key _key;
|
||||||
final Function()? onDismiss;
|
final Function()? onDismiss;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
final String server;
|
||||||
|
final String room;
|
||||||
|
|
||||||
const ShoppingListItem(
|
const ShoppingListItem(
|
||||||
{required this.name,
|
{required this.name,
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.inCart,
|
required this.inCart,
|
||||||
required this.description,
|
required this.description,
|
||||||
|
required this.server,
|
||||||
|
required this.room,
|
||||||
required key,
|
required key,
|
||||||
this.onDismiss,
|
this.onDismiss,
|
||||||
this.onTap})
|
this.onTap})
|
||||||
|
@ -367,6 +497,8 @@ class ShoppingListItem extends StatelessWidget {
|
||||||
title: Text(name),
|
title: Text(name),
|
||||||
subtitle: Text(description),
|
subtitle: Text(description),
|
||||||
trailing: CategoryChip(
|
trailing: CategoryChip(
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
category: category,
|
category: category,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -410,6 +542,8 @@ class ShoppingListItemInfo extends StatelessWidget {
|
||||||
Text(item.name, style: textTheme.headlineLarge),
|
Text(item.name, style: textTheme.headlineLarge),
|
||||||
Text(item.description, style: textTheme.titleMedium),
|
Text(item.description, style: textTheme.titleMedium),
|
||||||
CategoryChip(
|
CategoryChip(
|
||||||
|
server: server,
|
||||||
|
room: room,
|
||||||
category: category,
|
category: category,
|
||||||
),
|
),
|
||||||
Text(Unit.fromId(item.unit).display(context, item.value))
|
Text(Unit.fromId(item.unit).display(context, item.value))
|
||||||
|
@ -445,11 +579,14 @@ class ShoppingListItemInfo extends StatelessWidget {
|
||||||
subtitle: Text(AppLocalizations.of(context)!.editItemLong),
|
subtitle: Text(AppLocalizations.of(context)!.editItemLong),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.pushNamed('edit-product', params: {
|
context.pushNamed('edit-item', params: {
|
||||||
'server': server,
|
'server': server,
|
||||||
'id': room,
|
'id': room,
|
||||||
'item': item.id.toString()
|
'item': item.id.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final navInner = Navigator.of(context);
|
||||||
|
navInner.pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
@ -459,16 +596,70 @@ class ShoppingListItemInfo extends StatelessWidget {
|
||||||
AppLocalizations.of(context)!.deleteItemSubtitle),
|
AppLocalizations.of(context)!.deleteItemSubtitle),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// TODO: show confirm dialog
|
// show popup
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!.deleteItem),
|
||||||
|
content: Text(AppLocalizations.of(context)!
|
||||||
|
.deleteItemConfirm(item.name)),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
// close popup
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// send request
|
||||||
|
final scaffMgr =
|
||||||
|
ScaffoldMessenger.of(ctx);
|
||||||
|
// popup context
|
||||||
|
final navInner = Navigator.of(ctx);
|
||||||
|
// bottomsheet context
|
||||||
|
final nav = Navigator.of(context);
|
||||||
|
final user = context.read<User>();
|
||||||
|
|
||||||
|
doNetworkRequest(scaffMgr,
|
||||||
|
req: () => postWithCreadentials(
|
||||||
|
path: 'deleteItem',
|
||||||
|
target: user.server,
|
||||||
|
body: {
|
||||||
|
'room': room,
|
||||||
|
'server': server,
|
||||||
|
'listItemID': item.id
|
||||||
|
},
|
||||||
|
credentials: user),
|
||||||
|
onOK: (_) async {
|
||||||
|
// remove cached item
|
||||||
|
await item.removeDisk();
|
||||||
|
},
|
||||||
|
after: () {
|
||||||
|
// close popup
|
||||||
|
navInner.pop();
|
||||||
|
// close modal bottom sheet
|
||||||
|
nav.pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context)!
|
||||||
|
.deleteItem),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(item.state == 0
|
title: Text(item.state == 0
|
||||||
? AppLocalizations.of(context)!.moveItemToCartTitle
|
? AppLocalizations.of(context)!.moveItemToCartTitle
|
||||||
: AppLocalizations.of(context)!.moveItemToCartSubtitle),
|
: AppLocalizations.of(context)!.removeItemFromCartTitle),
|
||||||
subtitle: Text(item.state == 0
|
subtitle: Text(item.state == 0
|
||||||
? AppLocalizations.of(context)!.removeItemFromCartTitle
|
? AppLocalizations.of(context)!.moveItemToCartSubtitle
|
||||||
: AppLocalizations.of(context)!.removeItemFromCartSubtitle),
|
: AppLocalizations.of(context)!.removeItemFromCartSubtitle),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// flip state
|
// flip state
|
||||||
|
@ -484,7 +675,12 @@ class ShoppingListItemInfo extends StatelessWidget {
|
||||||
'server': server,
|
'server': server,
|
||||||
'listItemID': item.id,
|
'listItemID': item.id,
|
||||||
'state': item.state
|
'state': item.state
|
||||||
}));
|
}),
|
||||||
|
onOK: (_) async {
|
||||||
|
final navInner = Navigator.of(context);
|
||||||
|
await item.toDisk();
|
||||||
|
navInner.pop();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -21,10 +21,20 @@ class RoomProductsPage extends StatefulWidget {
|
||||||
class _RoomProductsPageState extends State<RoomProductsPage> {
|
class _RoomProductsPageState extends State<RoomProductsPage> {
|
||||||
List<RoomProduct> products = [];
|
List<RoomProduct> products = [];
|
||||||
|
|
||||||
void fetchProducts() {
|
void fetchProducts() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
// load cached products first
|
||||||
|
final cache = await RoomProduct.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
products = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -32,10 +42,13 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
|
||||||
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
body: {'room': widget.room?.id, 'server': widget.room?.serverTag}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
.map<RoomProduct>((raw) => RoomProduct.fromJSON(
|
||||||
|
widget.room!.serverTag, widget.room!.id, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// TODO: cache products
|
for (RoomProduct prod in resp) {
|
||||||
|
prod.toDisk();
|
||||||
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -49,38 +62,55 @@ class _RoomProductsPageState extends State<RoomProductsPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room product changes
|
||||||
|
RoomProduct.listen(widget.room?.serverTag ?? "", widget.room?.id ?? "",
|
||||||
|
(_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomProduct.list(
|
||||||
|
widget.room?.serverTag ?? "", widget.room?.id ?? "");
|
||||||
|
setState(() {
|
||||||
|
products = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => fetchProducts());
|
WidgetsBinding.instance.addPostFrameCallback((_) => fetchProducts());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: ListView.builder(
|
body: Center(
|
||||||
itemCount: products.length,
|
child: Padding(
|
||||||
itemBuilder: (context, index) {
|
padding: const EdgeInsets.all(14),
|
||||||
final item = products[index];
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: products.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = products[index];
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(item.name),
|
title: Text(item.name),
|
||||||
subtitle: Text(item.description),
|
subtitle: Text(item.description),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// NOTE: we could also show a bottom sheet here,
|
// NOTE: we could also show a bottom sheet here,
|
||||||
// but because we need a seperate page/route either way
|
// but because we need a seperate page/route either way
|
||||||
// (for viewing item-links and exploring the product-tree)
|
// (for viewing item-links and exploring the product-tree)
|
||||||
// we might as well use the view-product page here
|
// we might as well use the view-product page here
|
||||||
// NOTE: This might seem inconsistent,
|
// NOTE: This might seem inconsistent,
|
||||||
// but you probably wont ever need to read a product description,
|
// but you probably wont ever need to read a product description,
|
||||||
// where as reading the shopping item description,
|
// where as reading the shopping item description,
|
||||||
// might be a good idea
|
// might be a good idea
|
||||||
context.pushNamed('view-product', params: {
|
context.pushNamed('view-product', params: {
|
||||||
'server': widget.room!.serverTag,
|
'server': widget.room!.serverTag,
|
||||||
'id': widget.room!.id,
|
'id': widget.room!.id,
|
||||||
'product': item.id.toString()
|
'product': item.id.toString()
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
)))),
|
||||||
floatingActionButton: (widget.info != null &&
|
floatingActionButton: (widget.info != null &&
|
||||||
((widget.info?.isAdmin ?? false) ||
|
((widget.info?.isAdmin ?? false) ||
|
||||||
(widget.info?.isOwner ?? false) ||
|
(widget.info?.isOwner ?? false) ||
|
||||||
|
|
|
@ -36,12 +36,19 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
List<RoomCategory> categories = [];
|
List<RoomCategory> categories = [];
|
||||||
List<RoomProduct> products = [];
|
List<RoomProduct> products = [];
|
||||||
|
|
||||||
void fetchCategories() {
|
void fetchCategories() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached categories first
|
// load cached categories
|
||||||
|
final cache = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
categories = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -49,7 +56,8 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
body: {'room': widget.room, 'server': widget.server}),
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
.map<RoomCategory>((raw) =>
|
||||||
|
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -58,12 +66,19 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchProducts() {
|
void fetchProducts() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached products first
|
// load cached products first
|
||||||
|
final cache = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
products = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -71,7 +86,8 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
body: {'room': widget.room, 'server': widget.server}),
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
.map<RoomProduct>((raw) =>
|
||||||
|
RoomProduct.fromJSON(widget.server, widget.room, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
if (widget.product != null) {
|
if (widget.product != null) {
|
||||||
|
@ -102,6 +118,16 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room category changes
|
||||||
|
RoomCategory.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
setState(() {
|
||||||
|
categories = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
|
@ -192,6 +218,8 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
CategoryPicker(
|
CategoryPicker(
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
label: AppLocalizations.of(context)!
|
label: AppLocalizations.of(context)!
|
||||||
.selectCategoryLabel,
|
.selectCategoryLabel,
|
||||||
hint: AppLocalizations.of(context)!
|
hint: AppLocalizations.of(context)!
|
||||||
|
@ -252,7 +280,22 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
'ean': _ctrEAN.text,
|
'ean': _ctrEAN.text,
|
||||||
'parent': _ctrParent
|
'parent': _ctrParent
|
||||||
}),
|
}),
|
||||||
onOK: (_) async {
|
onOK: (body) async {
|
||||||
|
// cache product
|
||||||
|
final id = body["data"]["listProdID"];
|
||||||
|
final prod = RoomProduct(
|
||||||
|
id: id,
|
||||||
|
name: _ctrName.text,
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
|
description: _ctrDescription.text,
|
||||||
|
category: _ctrCategory,
|
||||||
|
defaultUnit: _ctrUnit,
|
||||||
|
defaultValue: _ctrValue,
|
||||||
|
ean: _ctrEAN.text,
|
||||||
|
parent: _ctrParent);
|
||||||
|
await prod.toDisk();
|
||||||
|
|
||||||
nav.pop();
|
nav.pop();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -274,6 +317,20 @@ class _EditProductPageState extends State<EditProductPage> {
|
||||||
'parent': _ctrParent
|
'parent': _ctrParent
|
||||||
}),
|
}),
|
||||||
onOK: (_) async {
|
onOK: (_) async {
|
||||||
|
// cache product
|
||||||
|
final prod = RoomProduct(
|
||||||
|
id: widget.product!,
|
||||||
|
name: _ctrName.text,
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
|
description: _ctrDescription.text,
|
||||||
|
category: _ctrCategory,
|
||||||
|
defaultUnit: _ctrUnit,
|
||||||
|
defaultValue: _ctrValue,
|
||||||
|
ean: _ctrEAN.text,
|
||||||
|
parent: _ctrParent);
|
||||||
|
await prod.toDisk();
|
||||||
|
|
||||||
nav.pop();
|
nav.pop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,12 +53,24 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchCategories() {
|
void fetchCategories() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached categories first
|
// load cached categories
|
||||||
|
final cache = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
Map<int?, RoomCategory> map = {};
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
for (RoomCategory cat in cache) {
|
||||||
|
map[cat.id] = cat;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
categories = map;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -66,7 +78,8 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
||||||
body: {'room': widget.room, 'server': widget.server}),
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomCategory>((raw) => RoomCategory.fromJSON(raw))
|
.map<RoomCategory>((raw) =>
|
||||||
|
RoomCategory.fromJSON(widget.server, widget.room, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
Map<int?, RoomCategory> map = {};
|
Map<int?, RoomCategory> map = {};
|
||||||
|
@ -80,12 +93,19 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchProducts() {
|
void fetchProducts() async {
|
||||||
final user = context.read<User>();
|
final user = context.read<User>();
|
||||||
|
final scaffmgr = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
// TODO: load cached products first
|
// load cached products first
|
||||||
|
final cache = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
products = cache;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
doNetworkRequest(ScaffoldMessenger.of(context),
|
doNetworkRequest(scaffmgr,
|
||||||
req: () => postWithCreadentials(
|
req: () => postWithCreadentials(
|
||||||
credentials: user,
|
credentials: user,
|
||||||
target: user.server,
|
target: user.server,
|
||||||
|
@ -93,7 +113,8 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
||||||
body: {'room': widget.room, 'server': widget.server}),
|
body: {'room': widget.room, 'server': widget.server}),
|
||||||
onOK: (body) async {
|
onOK: (body) async {
|
||||||
final resp = body['data']
|
final resp = body['data']
|
||||||
.map<RoomProduct>((raw) => RoomProduct.fromJSON(raw))
|
.map<RoomProduct>((raw) =>
|
||||||
|
RoomProduct.fromJSON(widget.server, widget.room, raw))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
for (RoomProduct prod in resp) {
|
for (RoomProduct prod in resp) {
|
||||||
|
@ -115,6 +136,39 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// wait for background room product changes
|
||||||
|
RoomProduct.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomProduct.list(widget.server, widget.room);
|
||||||
|
for (RoomProduct prod in updated) {
|
||||||
|
// load product info
|
||||||
|
// for current product
|
||||||
|
if (prod.id == widget.product) {
|
||||||
|
setState(() {
|
||||||
|
product = prod;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
products = updated;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
// wait for background room category changes
|
||||||
|
RoomCategory.listen(widget.server, widget.room, (_) async {
|
||||||
|
try {
|
||||||
|
final updated = await RoomCategory.list(widget.server, widget.room);
|
||||||
|
Map<int?, RoomCategory> map = {};
|
||||||
|
|
||||||
|
for (RoomCategory cat in updated) {
|
||||||
|
map[cat.id] = cat;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
categories = map;
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
fetchCategories();
|
fetchCategories();
|
||||||
fetchProducts();
|
fetchProducts();
|
||||||
|
@ -131,83 +185,192 @@ class _ViewProductPageState extends State<ViewProductPage> {
|
||||||
title: Text(product?.name ?? ''),
|
title: Text(product?.name ?? ''),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(children: [
|
child: Center(
|
||||||
// display product into
|
child: Padding(
|
||||||
Center(
|
padding: const EdgeInsets.all(14),
|
||||||
child: Padding(
|
child: ConstrainedBox(
|
||||||
padding: const EdgeInsets.all(14),
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
child: Column(
|
child: Column(children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
// display product into
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Center(
|
||||||
children: [
|
child: Padding(
|
||||||
Text(product?.name ?? '', style: textTheme.headlineLarge),
|
padding: const EdgeInsets.all(14),
|
||||||
Text(product?.description ?? '',
|
child: Column(
|
||||||
style: textTheme.titleMedium),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Text(product?.ean ?? ''),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
CategoryChip(category: categories[product?.category]),
|
children: [
|
||||||
Text(product != null
|
Text(product?.name ?? '',
|
||||||
? Unit.fromId(product!.defaultUnit)
|
style: textTheme.headlineLarge),
|
||||||
.display(context, product!.defaultValue)
|
Text(product?.description ?? '',
|
||||||
: '')
|
style: textTheme.titleMedium),
|
||||||
],
|
Text(product?.ean ?? ''),
|
||||||
))),
|
CategoryChip(
|
||||||
|
server: widget.server,
|
||||||
|
room: widget.room,
|
||||||
|
category:
|
||||||
|
categories[product?.category]),
|
||||||
|
Text(product != null
|
||||||
|
? Unit.fromId(product!.defaultUnit)
|
||||||
|
.display(
|
||||||
|
context, product!.defaultValue)
|
||||||
|
: '')
|
||||||
|
],
|
||||||
|
))),
|
||||||
|
|
||||||
// show actions (if allowed / available
|
// show actions (if allowed / available
|
||||||
// edit product button
|
// edit product button
|
||||||
...(info != null &&
|
...(info != null &&
|
||||||
(info!.isAdmin ||
|
(info!.isAdmin ||
|
||||||
info!.isOwner ||
|
info!.isOwner ||
|
||||||
(info!.permissions & RoomPermission.editRoomContent != 0)))
|
(info!.permissions &
|
||||||
? [
|
RoomPermission.editRoomContent !=
|
||||||
ListTile(
|
0)))
|
||||||
title: Text(AppLocalizations.of(context)!.editProductTitle),
|
? [
|
||||||
subtitle:
|
ListTile(
|
||||||
Text(AppLocalizations.of(context)!.editProductSubtitle),
|
title: Text(AppLocalizations.of(context)!
|
||||||
onTap: () {
|
.editProductTitle),
|
||||||
context.pushNamed('edit-product', params: {
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
'server': widget.server,
|
.editProductSubtitle),
|
||||||
'id': widget.room,
|
onTap: () {
|
||||||
'product': widget.product.toString()
|
context.pushNamed('edit-product', params: {
|
||||||
});
|
'server': widget.server,
|
||||||
},
|
'id': widget.room,
|
||||||
trailing: const Icon(Icons.chevron_right),
|
'product': widget.product.toString()
|
||||||
),
|
});
|
||||||
]
|
},
|
||||||
: [],
|
trailing: const Icon(Icons.chevron_right),
|
||||||
// show parent?
|
),
|
||||||
...(product?.parent != null)
|
]
|
||||||
? [
|
: [],
|
||||||
ListTile(
|
// show parent?
|
||||||
title: Text(
|
...(product?.parent != null)
|
||||||
AppLocalizations.of(context)!.viewParentProductTitle),
|
? [
|
||||||
subtitle: Text(
|
ListTile(
|
||||||
AppLocalizations.of(context)!.viewParentProductSubtitle),
|
title: Text(AppLocalizations.of(context)!
|
||||||
onTap: () {
|
.viewParentProductTitle),
|
||||||
context.pushNamed('view-product', params: {
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
'server': widget.server,
|
.viewParentProductSubtitle),
|
||||||
'id': widget.room,
|
onTap: () {
|
||||||
'product': product!.parent.toString()
|
context.pushNamed('view-product', params: {
|
||||||
});
|
'server': widget.server,
|
||||||
},
|
'id': widget.room,
|
||||||
trailing: const Icon(Icons.chevron_right),
|
'product': product!.parent.toString()
|
||||||
),
|
});
|
||||||
]
|
},
|
||||||
: [],
|
trailing: const Icon(Icons.chevron_right),
|
||||||
// show/manage children
|
),
|
||||||
ListTile(
|
]
|
||||||
title: Text(AppLocalizations.of(context)!.viewProductChildrenTitle),
|
: [],
|
||||||
subtitle:
|
// show/manage children
|
||||||
Text(AppLocalizations.of(context)!.viewProductChildrenSubtitle),
|
ListTile(
|
||||||
onTap: () {
|
title: Text(AppLocalizations.of(context)!
|
||||||
context.pushNamed('view-product-children', params: {
|
.viewProductChildrenTitle),
|
||||||
'server': widget.server,
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
'id': widget.room,
|
.viewProductChildrenSubtitle),
|
||||||
'product': widget.product.toString()
|
onTap: () {
|
||||||
});
|
context.pushNamed('view-product-children', params: {
|
||||||
},
|
'server': widget.server,
|
||||||
trailing: const Icon(Icons.chevron_right),
|
'id': widget.room,
|
||||||
),
|
'product': widget.product.toString()
|
||||||
])),
|
});
|
||||||
|
},
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
),
|
||||||
|
...(info != null &&
|
||||||
|
((info?.isAdmin ?? false) ||
|
||||||
|
(info?.isOwner ?? false) ||
|
||||||
|
((info?.permissions)! &
|
||||||
|
RoomPermission.editRoomContent !=
|
||||||
|
0)))
|
||||||
|
? [
|
||||||
|
// delete product
|
||||||
|
ListTile(
|
||||||
|
title: Text(AppLocalizations.of(context)!
|
||||||
|
.deleteProductTitle),
|
||||||
|
subtitle: Text(AppLocalizations.of(context)!
|
||||||
|
.deleteProductSubtitle),
|
||||||
|
onTap: () {
|
||||||
|
// show popup
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.deleteProduct),
|
||||||
|
content: Text(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.deleteProductConfirm(
|
||||||
|
product?.name ?? "")),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
// close popup
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.cancel),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// send request
|
||||||
|
final scaffMgr =
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
ctx);
|
||||||
|
// popup context
|
||||||
|
final navInner =
|
||||||
|
Navigator.of(ctx);
|
||||||
|
// bottomsheet context
|
||||||
|
final nav =
|
||||||
|
Navigator.of(context);
|
||||||
|
final user =
|
||||||
|
context.read<User>();
|
||||||
|
|
||||||
|
doNetworkRequest(scaffMgr,
|
||||||
|
req: () =>
|
||||||
|
postWithCreadentials(
|
||||||
|
path:
|
||||||
|
'deleteProduct',
|
||||||
|
target:
|
||||||
|
user.server,
|
||||||
|
body: {
|
||||||
|
'room': widget
|
||||||
|
.room,
|
||||||
|
'server': widget
|
||||||
|
.server,
|
||||||
|
'listProdID':
|
||||||
|
product?.id ??
|
||||||
|
""
|
||||||
|
},
|
||||||
|
credentials:
|
||||||
|
user),
|
||||||
|
onOK: (_) async {
|
||||||
|
// remove cached product
|
||||||
|
await product!
|
||||||
|
.removeDisk();
|
||||||
|
},
|
||||||
|
after: () {
|
||||||
|
// close popup
|
||||||
|
navInner.pop();
|
||||||
|
// close modal bottom sheet
|
||||||
|
nav.pop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.deleteProduct),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
},
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
]))))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,6 @@ Future<void> doNetworkRequest(ScaffoldMessengerState? sm,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (after != null) {
|
if (after != null) {
|
||||||
after();
|
await after();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
232
pubspec.lock
232
pubspec.lock
|
@ -5,26 +5,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d
|
sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.6"
|
version: "3.4.9"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: args
|
name: args
|
||||||
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
|
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.2"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
|
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.10.0"
|
version: "2.11.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -37,18 +37,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.3.0"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: checked_yaml
|
name: checked_yaml
|
||||||
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
|
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.3"
|
||||||
cli_util:
|
cli_util:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -69,10 +69,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.18.0"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -85,10 +85,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.3"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -101,18 +101,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.1.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file
|
name: file
|
||||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.4"
|
version: "7.0.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -130,10 +130,10 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
|
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.3"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -143,10 +143,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
sha256: "12006889e2987c549c4c1ec1a5ba4ec4b24d34d2469ee5f9476c926dcecff266"
|
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.9"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -161,18 +161,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "432409518740645ce7f28802171b78783197d01149fad44f9b8ae55f40277139"
|
sha256: bd7e671d26fd39c78cba82070fa34ef1f830b0e7ed1aeebccabc6561302a7ee5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
version: "6.5.9"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
|
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.5"
|
version: "0.13.6"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -185,42 +185,42 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227"
|
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.15"
|
version: "4.1.3"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.17.0"
|
version: "0.18.1"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: js
|
name: js
|
||||||
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.5"
|
version: "0.6.7"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
|
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.8.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
|
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.1.1"
|
||||||
localstore:
|
localstore:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -233,34 +233,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: logging
|
name: logging
|
||||||
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
|
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.2.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
|
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.13"
|
version: "0.12.16"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.5.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
|
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.10.0"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -273,10 +273,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
|
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.8.3"
|
||||||
path_parsing:
|
path_parsing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -289,154 +289,146 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9"
|
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.13"
|
version: "2.1.1"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7"
|
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.24"
|
version: "2.2.1"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059"
|
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.1"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1"
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.10"
|
version: "2.2.1"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
|
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6"
|
version: "2.1.1"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
|
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.2.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.0"
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: platform
|
name: platform
|
||||||
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.3"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.7"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: ae73e842cdd27a3467a71d70cefd9b198538aab4fc7dde1d0e8c78c96225abf0
|
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.7.1"
|
version: "3.7.3"
|
||||||
process:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: process
|
|
||||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.2.4"
|
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.5"
|
version: "6.1.1"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "78528fd87d0d08ffd3e69551173c026e8eacc7b7079c82eb6a77413957b7e394"
|
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.20"
|
version: "2.2.2"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521
|
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.17"
|
version: "2.2.1"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310"
|
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.3.4"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_linux
|
name: shared_preferences_linux
|
||||||
sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707"
|
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.3.2"
|
||||||
shared_preferences_platform_interface:
|
shared_preferences_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc"
|
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.3.1"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8"
|
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.6"
|
version: "2.2.2"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_windows
|
name: shared_preferences_windows
|
||||||
sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436"
|
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.3.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -446,26 +438,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.10.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stream_channel
|
name: stream_channel
|
||||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -486,42 +478,42 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.16"
|
version: "0.6.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: typed_data
|
name: typed_data
|
||||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.2"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics
|
name: vector_graphics
|
||||||
sha256: "4cf8e60dbe4d3a693d37dff11255a172594c0793da542183cbfe7fe978ae4aaa"
|
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.4"
|
version: "1.1.9+1"
|
||||||
vector_graphics_codec:
|
vector_graphics_codec:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics_codec
|
name: vector_graphics_codec
|
||||||
sha256: "278ad5f816f58b1967396d1f78ced470e3e58c9fe4b27010102c0a595c764468"
|
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.4"
|
version: "1.1.9+1"
|
||||||
vector_graphics_compiler:
|
vector_graphics_compiler:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics_compiler
|
name: vector_graphics_compiler
|
||||||
sha256: "0bf61ad56e6fd6688a2865d3ceaea396bc6a0a90ea0d7ad5049b1b76c09d6163"
|
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.4"
|
version: "1.1.9+1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -530,38 +522,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "5.1.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
|
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.3"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.2"
|
version: "6.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: yaml
|
name: yaml
|
||||||
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.3 <3.0.0"
|
dart: ">=3.2.0 <4.0.0"
|
||||||
flutter: ">=3.7.0-0"
|
flutter: ">=3.16.0"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
name: outbag_app
|
name: outbag_app
|
||||||
description: Official Outbag App
|
description: Official Outbag App
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
|
|
Loading…
Reference in a new issue