9ff6d97c90
BUG: when deleting a product the product list won't update
714 lines
17 KiB
Dart
714 lines
17 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:localstore/localstore.dart';
|
|
import 'package:outbag_app/tools/assets.dart';
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
|
|
class RoomVisibility {
|
|
final int type;
|
|
const RoomVisibility(this.type);
|
|
|
|
static RoomVisibility get private {
|
|
return const RoomVisibility(0);
|
|
}
|
|
|
|
static RoomVisibility get local {
|
|
return const RoomVisibility(1);
|
|
}
|
|
|
|
static RoomVisibility get public {
|
|
return const RoomVisibility(2);
|
|
}
|
|
|
|
IconData get icon {
|
|
if (type == 2) {
|
|
return Icons.public;
|
|
} else if (type == 1) {
|
|
return Icons.home;
|
|
}
|
|
|
|
return Icons.lock;
|
|
}
|
|
|
|
String text(BuildContext context) {
|
|
final trans = AppLocalizations.of(context);
|
|
|
|
if (type == 2) {
|
|
return trans!.roomVisibilityGlobal;
|
|
} else if (type == 1) {
|
|
return trans!.roomVisibilityLocal;
|
|
}
|
|
|
|
return trans!.roomVisibilityPrivate;
|
|
}
|
|
|
|
static List<RoomVisibility> list() {
|
|
return [
|
|
RoomVisibility.private,
|
|
RoomVisibility.local,
|
|
RoomVisibility.public,
|
|
];
|
|
}
|
|
}
|
|
|
|
class RoomIcon {
|
|
final String type;
|
|
RoomIcon({required this.type});
|
|
|
|
static RoomIcon get love {
|
|
return RoomIcon(type: "Love");
|
|
}
|
|
|
|
static RoomIcon get sports {
|
|
return RoomIcon(type: "Sports");
|
|
}
|
|
|
|
static RoomIcon get pets {
|
|
return RoomIcon(type: "Pets");
|
|
}
|
|
|
|
static RoomIcon get vacation {
|
|
return RoomIcon(type: "Vacation");
|
|
}
|
|
|
|
static RoomIcon get gifts {
|
|
return RoomIcon(type: "Gifts");
|
|
}
|
|
|
|
static RoomIcon get groceries {
|
|
return RoomIcon(type: "Groceries");
|
|
}
|
|
|
|
static RoomIcon get fashion {
|
|
return RoomIcon(type: "Fashion");
|
|
}
|
|
|
|
static RoomIcon get art {
|
|
return RoomIcon(type: "Art");
|
|
}
|
|
|
|
static RoomIcon get tech {
|
|
return RoomIcon(type: "Tech");
|
|
}
|
|
|
|
static RoomIcon get home {
|
|
return RoomIcon(type: "Home");
|
|
}
|
|
|
|
static RoomIcon get family {
|
|
return RoomIcon(type: "Family");
|
|
}
|
|
|
|
static RoomIcon get social {
|
|
return RoomIcon(type: "Social");
|
|
}
|
|
|
|
static RoomIcon get other {
|
|
return RoomIcon(type: "Other");
|
|
}
|
|
|
|
static List<RoomIcon> list() {
|
|
return [
|
|
RoomIcon.love,
|
|
RoomIcon.sports,
|
|
RoomIcon.pets,
|
|
RoomIcon.vacation,
|
|
RoomIcon.gifts,
|
|
RoomIcon.groceries,
|
|
RoomIcon.fashion,
|
|
RoomIcon.art,
|
|
RoomIcon.tech,
|
|
RoomIcon.home,
|
|
RoomIcon.family,
|
|
RoomIcon.social,
|
|
RoomIcon.other,
|
|
];
|
|
}
|
|
|
|
String get text {
|
|
switch (type.toLowerCase()) {
|
|
case 'love':
|
|
return 'Friends';
|
|
case 'sports':
|
|
return 'Sports';
|
|
case 'pets':
|
|
return 'Pets';
|
|
case 'vacation':
|
|
return 'Vacation';
|
|
case 'gifts':
|
|
return 'Gifts';
|
|
case 'groceries':
|
|
return 'Groceries';
|
|
case 'fashion':
|
|
return 'Clothing';
|
|
case 'art':
|
|
return 'Arts & Crafts';
|
|
case 'tech':
|
|
return 'Electronics';
|
|
case 'home':
|
|
return 'Home supplies';
|
|
case 'family':
|
|
return 'Family';
|
|
case 'social':
|
|
return 'Social';
|
|
case 'other':
|
|
default:
|
|
return 'Other';
|
|
}
|
|
}
|
|
|
|
// return image name
|
|
String get img {
|
|
String path = "";
|
|
switch (type.toLowerCase()) {
|
|
case 'love':
|
|
path = 'undraw/undraw_couple.svg';
|
|
break;
|
|
case 'sports':
|
|
path = 'undraw/undraw_greek_freak.svg';
|
|
break;
|
|
case 'pets':
|
|
path = 'undraw/undraw_dog.svg';
|
|
break;
|
|
case 'vacation':
|
|
path = 'undraw/undraw_trip.svg';
|
|
break;
|
|
case 'gifts':
|
|
path = 'undraw/undraw_gifts.svg';
|
|
break;
|
|
case 'groceries':
|
|
path = 'undraw/undraw_gone_shopping.svg';
|
|
break;
|
|
case 'fashion':
|
|
path = 'undraw/undraw_jewelry.svg';
|
|
break;
|
|
case 'art':
|
|
path = 'undraw/undraw_sculpting.svg';
|
|
break;
|
|
case 'tech':
|
|
path = 'undraw/undraw_progressive_app.svg';
|
|
break;
|
|
case 'home':
|
|
path = 'undraw/undraw_under_construction.svg';
|
|
break;
|
|
case 'family':
|
|
path = 'undraw/undraw_family.svg';
|
|
break;
|
|
case 'social':
|
|
path = 'undraw/undraw_pizza_sharing.svg';
|
|
break;
|
|
case 'other':
|
|
default:
|
|
path = 'undraw/undraw_file_manager.svg';
|
|
}
|
|
|
|
return asset(path);
|
|
}
|
|
}
|
|
|
|
class Room {
|
|
String id;
|
|
String serverTag;
|
|
String name;
|
|
String description;
|
|
RoomIcon? icon = RoomIcon.other;
|
|
RoomVisibility? visibility = RoomVisibility.private;
|
|
|
|
Room(
|
|
{required this.id,
|
|
required this.serverTag,
|
|
this.name = "",
|
|
this.description = "",
|
|
this.icon,
|
|
this.visibility});
|
|
|
|
@override
|
|
bool operator ==(Object r) {
|
|
if (r.runtimeType != runtimeType) {
|
|
return false;
|
|
}
|
|
final me = humanReadable;
|
|
final other = (r as Room).humanReadable;
|
|
|
|
return me == other;
|
|
}
|
|
|
|
String get humanReadable {
|
|
return '$id@$serverTag';
|
|
}
|
|
|
|
// get list of all known rooms
|
|
static Future<List<Room>> listRooms() async {
|
|
final db = Localstore.instance;
|
|
final rooms = (await db.collection('rooms').get())!;
|
|
List<Room> builder = [];
|
|
for (MapEntry entry in rooms.entries) {
|
|
try {
|
|
builder.add(Room.fromMap(entry.value));
|
|
} catch (e) {
|
|
// skip invalid rooms
|
|
// 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(Function(Map<String, dynamic>) cb) async {
|
|
final db = Localstore.instance;
|
|
final stream = db.collection('rooms').stream;
|
|
stream.listen(cb);
|
|
}
|
|
|
|
factory Room.fromMap(Map<String, dynamic> map) {
|
|
return Room(
|
|
id: map['id'],
|
|
serverTag: map['server'],
|
|
name: map['name'],
|
|
description: map['description'] ?? '',
|
|
icon: RoomIcon(type: map['icon'] ?? 'Other'),
|
|
visibility: RoomVisibility(map['visibility'] ?? 0));
|
|
}
|
|
|
|
Map<String, dynamic> toMap() {
|
|
return {
|
|
'id': id,
|
|
'server': serverTag,
|
|
'description': description,
|
|
'name': name,
|
|
'icon': icon?.type,
|
|
'visibility': visibility?.type
|
|
};
|
|
}
|
|
|
|
factory Room.fromJSON(dynamic json) {
|
|
return Room(
|
|
id: json['name'],
|
|
serverTag: json['server'],
|
|
name: json['title'],
|
|
description: json['description'],
|
|
icon: RoomIcon(type: json['icon']),
|
|
visibility: RoomVisibility(json['visibility']));
|
|
}
|
|
|
|
Future<void> toDisk() async {
|
|
final db = Localstore.instance;
|
|
await db.collection('rooms').doc('$id@$serverTag').set(toMap());
|
|
}
|
|
|
|
Future<void> removeDisk() async {
|
|
final db = Localstore.instance;
|
|
// NOTE: Because categories, products and entries
|
|
// are supposed to be stored in the room object
|
|
// (they are just not read by Room.fromDisk)
|
|
// deleting the document from the collection
|
|
// should also get rid of affiliated collections
|
|
await db.collection('rooms').doc('$id@$serverTag').delete();
|
|
}
|
|
|
|
static Future<Room> fromDisk(
|
|
{required String id, required String serverTag}) async {
|
|
final db = Localstore.instance;
|
|
final raw = await db.collection('rooms').doc('$id@$serverTag').get();
|
|
return Room.fromMap(raw!);
|
|
}
|
|
}
|
|
|
|
class RoomMember {
|
|
final String id;
|
|
final String serverTag;
|
|
final bool isAdmin;
|
|
final bool isInvitePending;
|
|
|
|
const RoomMember(
|
|
{required this.id,
|
|
required this.serverTag,
|
|
required this.isAdmin,
|
|
this.isInvitePending = false});
|
|
|
|
factory RoomMember.fromJSON(dynamic json) {
|
|
return RoomMember(
|
|
id: json['name'],
|
|
serverTag: json['server'],
|
|
isAdmin: json['admin'],
|
|
isInvitePending: json['confirmed']);
|
|
}
|
|
|
|
String get humanReadableName {
|
|
return '$id@$serverTag';
|
|
}
|
|
}
|
|
|
|
class RoomInfo {
|
|
final String owner;
|
|
final bool isAdmin;
|
|
final bool isOwner;
|
|
final int permissions;
|
|
|
|
const RoomInfo(
|
|
{required this.permissions,
|
|
required this.owner,
|
|
required this.isAdmin,
|
|
required this.isOwner});
|
|
|
|
factory RoomInfo.fromJSON(dynamic json) {
|
|
return RoomInfo(
|
|
permissions: json['rights'],
|
|
owner: json['owner'],
|
|
isAdmin: json['isAdmin'],
|
|
isOwner: json['isOwner']);
|
|
}
|
|
}
|
|
|
|
class RoomCategory {
|
|
final int? id;
|
|
String name;
|
|
ColorSwatch<int> color;
|
|
final String room;
|
|
final String server;
|
|
|
|
RoomCategory(
|
|
{required this.id,
|
|
required this.name,
|
|
required this.color,
|
|
required this.server,
|
|
required this.room});
|
|
|
|
factory RoomCategory.fromJSON(String server, String room, dynamic json) {
|
|
return RoomCategory(
|
|
server: server,
|
|
room: room,
|
|
id: json['id'],
|
|
name: json['title'],
|
|
color: colorFromString(json['color']));
|
|
}
|
|
factory RoomCategory.other(String server, String room, BuildContext context) {
|
|
return RoomCategory(
|
|
server: server,
|
|
room: room,
|
|
id: null,
|
|
name: AppLocalizations.of(context)!.categoryNameOther,
|
|
color: Colors.grey);
|
|
}
|
|
|
|
static List<ColorSwatch<int>> listColors() {
|
|
return [
|
|
"red",
|
|
"green",
|
|
"yellow",
|
|
"blue",
|
|
"aqua",
|
|
"purple",
|
|
"red-acc",
|
|
"green-acc",
|
|
"yellow-acc",
|
|
"blue-acc",
|
|
"aqua-acc",
|
|
"purple-acc",
|
|
].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) {
|
|
switch (text.toLowerCase()) {
|
|
case 'red-acc':
|
|
return Colors.redAccent;
|
|
case 'green-acc':
|
|
return Colors.greenAccent;
|
|
case 'yellow-acc':
|
|
return Colors.yellowAccent;
|
|
case 'blue-acc':
|
|
return Colors.blueAccent;
|
|
case 'aqua-acc':
|
|
return Colors.tealAccent;
|
|
case 'purple-acc':
|
|
return Colors.purpleAccent;
|
|
|
|
case 'red':
|
|
return Colors.red;
|
|
case 'green':
|
|
return Colors.green;
|
|
case 'yellow':
|
|
return Colors.yellow;
|
|
case 'blue':
|
|
return Colors.blue;
|
|
case 'aqua':
|
|
return Colors.teal;
|
|
case 'purple':
|
|
default:
|
|
return Colors.purple;
|
|
}
|
|
}
|
|
|
|
String colorIdFromColor(ColorSwatch<int> color) {
|
|
if (color == Colors.redAccent) {
|
|
return 'red-acc';
|
|
}
|
|
if (color == Colors.greenAccent) {
|
|
return 'green-acc';
|
|
}
|
|
if (color == Colors.yellowAccent) {
|
|
return 'yellow-acc';
|
|
}
|
|
if (color == Colors.blueAccent) {
|
|
return 'blue-acc';
|
|
}
|
|
if (color == Colors.tealAccent) {
|
|
return 'teal-acc';
|
|
}
|
|
if (color == Colors.purpleAccent) {
|
|
return 'purple-acc';
|
|
}
|
|
|
|
if (color == Colors.red) {
|
|
return 'red';
|
|
}
|
|
if (color == Colors.green) {
|
|
return 'green';
|
|
}
|
|
if (color == Colors.yellow) {
|
|
return 'yellow';
|
|
}
|
|
if (color == Colors.blue) {
|
|
return 'blue';
|
|
}
|
|
if (color == Colors.teal) {
|
|
return 'teal';
|
|
}
|
|
if (color == Colors.purple) {
|
|
return 'purple';
|
|
}
|
|
|
|
return 'purple';
|
|
}
|
|
|
|
class RoomProduct {
|
|
final int id;
|
|
String name;
|
|
String description;
|
|
// category ID
|
|
// or null for category: "other"
|
|
int? category;
|
|
// unitID
|
|
int defaultUnit;
|
|
// NOTE: has to be string,
|
|
// as it may hold plain text,
|
|
// integers or doubles
|
|
String defaultValue;
|
|
String? ean;
|
|
// parent product ID
|
|
int? parent;
|
|
|
|
final String server;
|
|
final String room;
|
|
|
|
RoomProduct(
|
|
{required this.id,
|
|
required this.name,
|
|
required this.server,
|
|
required this.room,
|
|
this.description = '',
|
|
this.category = -1,
|
|
this.defaultUnit = 0,
|
|
this.defaultValue = '',
|
|
this.ean,
|
|
this.parent});
|
|
|
|
factory RoomProduct.fromJSON(String server, String room, dynamic json) {
|
|
return RoomProduct(
|
|
server: server,
|
|
room: room,
|
|
id: json['listProdID'],
|
|
name: json['title'],
|
|
description: json['description'],
|
|
category: json['category'],
|
|
defaultUnit: json['defUnit'],
|
|
defaultValue: json['defValue'],
|
|
ean: json['ean'],
|
|
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 {
|
|
int id;
|
|
int state;
|
|
String name;
|
|
String description;
|
|
// may link to a category
|
|
// null for other
|
|
int? category;
|
|
|
|
int unit;
|
|
String value;
|
|
// may link to a product
|
|
int? link;
|
|
|
|
RoomItem(
|
|
{required this.id,
|
|
required this.name,
|
|
this.description = '',
|
|
this.state = 0,
|
|
this.category = -1,
|
|
this.unit = 0,
|
|
this.value = '',
|
|
this.link});
|
|
|
|
factory RoomItem.fromJSON(dynamic json) {
|
|
return RoomItem(
|
|
id: json['listItemID'],
|
|
name: json['title'],
|
|
description: json['description'],
|
|
category: json['listCatID'],
|
|
state: json['state'],
|
|
unit: json['unit'],
|
|
value: json['value'],
|
|
link: json['listProdID']);
|
|
}
|
|
|
|
RoomItem clone() {
|
|
return RoomItem(
|
|
id: id,
|
|
name: name,
|
|
description: description,
|
|
category: category,
|
|
unit: unit,
|
|
value: value,
|
|
link: link);
|
|
}
|
|
}
|