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 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 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> listRooms() async { final db = Localstore.instance; final rooms = (await db.collection('rooms').get())!; List 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) cb) async { final db = Localstore.instance; final stream = db.collection('rooms').stream; stream.listen(cb); } factory Room.fromMap(Map 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 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 toDisk() async { final db = Localstore.instance; await db.collection('rooms').doc('$id@$serverTag').set(toMap()); } Future 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 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 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> 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(String server, String room) async { final db = Localstore.instance; final rooms = (await db.collection('categories:$room@$server').get()) ?? {}; List 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) cb) async { final db = Localstore.instance; final stream = db.collection('categories:$room@$server').stream; stream.listen(cb); } factory RoomCategory.fromMap(Map map) { return RoomCategory( server: map['server'], room: map['room'], id: map['id'], name: map['name'], color: colorFromString(map['color'])); } Map toMap() { return { 'server': server, 'room': room, 'id': id, 'name': name, 'color': colorIdFromColor(color) }; } Future toDisk() async { final db = Localstore.instance; await db.collection('categories:$room@$server').doc('$id').set(toMap()); } Future removeDisk() async { final db = Localstore.instance; await db.collection('categories:$room@$server').doc('$id').delete(); } static Future 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 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 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(String server, String room) async { final db = Localstore.instance; final rooms = (await db.collection('products:$room@$server').get()) ?? {}; List 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) cb) async { final db = Localstore.instance; final stream = db.collection('products:$room@$server').stream; stream.listen(cb); } factory RoomProduct.fromMap(Map 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 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 toDisk() async { final db = Localstore.instance; await db.collection('products:$room@$server').doc('$id').set(toMap()); } Future removeDisk() async { final db = Localstore.instance; await db.collection('products:$room@$server').doc('$id').delete(); } static Future 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); } }