5b398eb2ae
- Fixed bug where switching room pages (list,products,categories,about), would result an unknown error, due to setState being called on a widget that isn't mounted. This was solved by surrounding the setState function, with a condition to check if the widget is mounted - Fixed bug where room members weren't recognized as admins This was caused by a typedifference between the server and the app (The server now returns booleans, where as before a ==1 comparison was needed) - Fixed bug where successfully closing the admin/kick member dialog, would crash the application. This was caused by popping the same context twice. We are now using two navigator (the outer and the inner one)
556 lines
12 KiB
Dart
556 lines
12 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;
|
|
final String name;
|
|
final ColorSwatch<int> color;
|
|
|
|
const RoomCategory(
|
|
{required this.id, required this.name, required this.color});
|
|
|
|
factory RoomCategory.fromJSON(dynamic json) {
|
|
return RoomCategory(
|
|
id: json['id'],
|
|
name: json['title'],
|
|
color: colorFromString(json['color']));
|
|
}
|
|
factory RoomCategory.other(BuildContext context) {
|
|
return RoomCategory(
|
|
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();
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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;
|
|
|
|
RoomProduct(
|
|
{required this.id,
|
|
required this.name,
|
|
this.description = '',
|
|
this.category = -1,
|
|
this.defaultUnit = 0,
|
|
this.defaultValue = '',
|
|
this.ean,
|
|
this.parent});
|
|
|
|
factory RoomProduct.fromJSON(dynamic json) {
|
|
return RoomProduct(
|
|
id: json['listProdID'],
|
|
name: json['title'],
|
|
description: json['description'],
|
|
category: json['category'],
|
|
defaultUnit: json['defUnit'],
|
|
defaultValue: json['defValue'],
|
|
ean: json['ean'],
|
|
parent: json['parent']);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|