Skip to content

Domain & Features

Marco Trivisonno edited this page May 5, 2024 · 16 revisions

Diagram

Domain diagram
@startuml Domain Diagram
!theme reddress-lightgreen
skinparam defaultFontName monospaced

title Domain Diagram

interface ProductEntity {
  {abstract} +String get id()
  {abstract} +String get name()
  {abstract} +String? get barcode()
  {abstract} +String? get brand()
  {abstract} +String? get imageFrontUrl()
  {abstract} +String? get imageFrontSmallUrl()
  {abstract} +Duration? get expectedShelfLife()
  {abstract} +StorageType? get suggestedStorageType()
  {abstract} +int? get unsealedLifeTimeInDays()
  {abstract} +Measure? get measure()
}
ProductEntity --> StorageType: suggestedStorageType

class ProductModel {
  +String id
  +String name
  +String? barcode
  +String? brand
  +String? imageFrontUrl
  +String? imageFrontSmallUrl
  +Duration? expectedShelfLife
  +StorageType? suggestedStorageType
  +int? unsealedLifeTimeInDays
  +Measure? measure
}

class Measure {
  +double quantity
  +UnitOfMeasure unitOfMeasure
}
ProductModel *-- Measure: measure
Measure *-- UnitOfMeasure: unitOfMeasure
enum UnitOfMeasure {
  KILOGRAM
  LITER
}
ProductEntity <|-- ProductModel

class User {
  +String id
  +String email
  +String displayName
  +List<String> houseIds
}

class House {
  +String id
  +String name
  +String? description
  +String ownerId
  +List<String> adminIds
  +List<String> userIds
}

User --* House: ownerId
User --* HouseUser: userId
HouseUser *-- House: houseId

class HouseUser {
  +String userId
  +String houseId
  +bool isOwner
  +bool isAdmin
}

House *-- Storage: houseId

class Storage {
  +String id
  +String name
  +String? description
  +StorageType storageType
  +String houseId
}

StorageType <-- Storage: storageType

enum StorageType {
  FRIDGE
  FREEZER
  PANTRY
}

' enum ItemCategory {
'   MEAT
'   FISH
'   EGG
'   FRUIT
'   VEGETABLE
'   LEGUMES
'   DAIRY
'   CEREALS
' }

interface ItemEntity {
  {abstract} +String get id()
  {abstract} +String get productId()
  {abstract} +Date get initialExpiryDate()
  {abstract} +DateTime get createdAt()
  {abstract} +String get storageId()
  {abstract} +DateTime? get openedAt()
  {abstract} +int? get unsealedLifeTimeInDays()
  {abstract} +int get amount()
  ---
  +Date get actualExpiryDate()
  {abstract} +ItemEntity copyWith()
}
ProductEntity <.. ItemEntity: references

class ItemModel {
  +String id
  +String productId
  +Date initialExpiryDate
  +DateTime createdAt
  +String storageId
  +DateTime? openedAt
  +int? unsealedLifeTimeInDays
  +int amount

  +Date get actualExpiryDate()
  +ItemEntity copyWith()
}

ItemEntity <|-- ItemModel

Storage *-- ItemEntity: storageId


abstract class Notification {
  +String id
  +DateTime createdAt
  +DateTime scheduledAt
  +DateTime? readAt
  +NotificationType type
  +int localId
  +String title
  +String body

  +int? intVal
  +String? stringVal
  +bool? boolVal
  +DateTime? dateTimeVal
  ---
  +bool get isRead()
}

class UserNotification {
  +String userId
}
Notification <|-- UserNotification
User <-- UserNotification: userId

class HouseUserNotification {
  +String houseUserId
  +int counter
}
Notification <|-- HouseUserNotification
HouseUser <-- HouseUserNotification: houseUserId

enum NotificationType {
  DAILY_REMINDER
  EXPIRING_ITEMS
  EXPIRED_ITEMS
}

Notification *-- NotificationType

@enduml

Entities

Entities vs Models

Product

A Product is a good that can be purchased and consumed. Think of it as a product in a supermarket, grocery store, etc., or even something that you made or grew yourself.

A product has the following properties:

  • id*: a unique identifier for the product
  • name*: the name of the product
  • barcode: the barcode of the product
  • brand: the brand of the product
  • imageFrontUrl: the URL of the image of the front of the product
  • imageFrontSmallUrl: the URL of a smaller version of the image of the front of the product
  • expectedShelfLife: how long the product is expected to last after it has been "produced" (e.g. packaged, harvested, etc.)
  • unsealedLifeTimeInDays: how long the product is expected to last after it has been opened
  • suggestedStorageType: the suggested StorageType for the product
  • measure: the measure of the product

Measure

The measure is a pair of:

  • quantity*: a number
  • unit of measure*: a unit of measure

Inventory

The inventory is the whole set of storages and all their items in the user's possession. It is not a real entity, but a concept that can be derived from the user's storages and items.

Storage

A storage is a place where items can be stored.

A storage has the following properties:

  • id*: a unique identifier for the storage
  • name*: the name of the storage
  • storageType*: the type of the storage
  • description: a description of the storage

Storage type

A storage has a mandatory type that can be one of the following:

  • FRIDGE
  • FREEZER
  • PANTRY

Item

An item is a product that has been added to the user's inventory.

An item has the following properties:

  • id*: a unique identifier for the item
  • productId*: the id of the product it refers to
  • initialExpiryDate*: the initial expiry date of the item as mentioned on the product's packaging or by common knowledge (e.g., a banana is known to last for a 5 to 7 days)
  • createdAt*: the date and time when the item was added to the inventory. This property is determined by the system.
  • storageId*: the id of the storage where the item is stored
  • openedAt: the date and time when the item was opened. This property is null if the item has not been opened yet.
  • unsealedLifeTimeInDays: the number of days the item can last after it has been opened. This property is initially copied from the product's property unsealedLifeTimeInDays and can be changed by the user.
  • amount*: the number of units of the item. By default, it is 1. See Grouping items for more information.

An item has the following derived properties:

  • actualExpiryDate: the actual expiry date of the item. This property is determined by the system based on the openedAt property:
    • if the item has not been opened, it is equal to the initialExpiryDate
    • if the item has been opened, it is the minimum between
      • the initialExpiryDate
      • the openedAt date + unsealedLifeTimeInDays

Users and Houses

Static Badge

A User is a person who uses the app. A user can be part of one or more houses.

A House is defined by a group of users and storages. A house must have at least one user.

A house has the following properties:

  • id*: a unique identifier for the house
  • name*: the name of the house, e.g., "Milan Home"
  • description: a description of the house
  • ownerId*: the id of the user who created the house. This user is the owner of the house. The owner is the only user allowed to delete the house even though a house cannot be deleted it it has other users than the owner themselves. The owner can transfer the ownership to another user by leaving the house.
  • adminIds: a list of ids of the users who are admins of the house. Admins can add and remove users from the house (the owner cannot be removed).

NB: it's the house who "owns" the storages with items and not the user!

Notifications

A notification is a message that the app sends to the user to inform them about something.

A notification has the following properties:

  • id*: a unique identifier for the notification
  • createdAt*: the date and time when the notification was created
  • scheduledAt*: the date and time when the notification is scheduled to be shown to the user via the app local notification system
  • readAt: the date and time when the user read the notification within the app. This property is null if the user has not read the notification yet.
  • type*: the type of the notification
  • localId*: the id assigned to the notification within the local notification system
  • title*: the title of the notification
  • body*: the content of the notification

A notification has several properties such as intVal, stringVal, boolVal, etc., that can be used to store additional information about the notification. Each notification specific implementation can leverage these properties in a different way.

UserNotification

A notification linked to a specific user regardless of the house they are in.

HouseUserNotification

A notification linked to a specific user in a specific house. If a user is in more than one house, they can have different notifications for each house they're in.

This class of notifications has an additional counter* property. It is used in case the same notification can be scheduled multiple times. An example is the EXPIRING_ITEMS notification. We foresee that a user will have multiple items that are going to expire soon on a certain day, so we want to show a single notification for all of them. The counter is used to group these notifications together.

When we're scheduling a new notification that may have already been scheduled we check if it exists and, in case, increment the counter. On the contrary, if we want to un-schedule a notification, we check:

  • if the counter is greater than 1, we decrement the counter
  • if the counter is equal to 1, we delete completely the notification

Notification flow

After that the notification is scheduled, when the notification is shown and tapped by the user, the plugin can listen and handle the notification and its payload. It knows If the app is been opened by tapping on a notification or the app was already opened when user taps on it. On notification tap the plugin run the callback and pass the payload to the notification service and the service adds the payload on its stream. The main app has a bloc listener with a subscription on the notification service stream, so when a payload is added it triggers the payload handler: for now a notification page is pushed.

Features

Grouping items

...

Add item

Item with barcode

User has to scan the product's barcode, and, if the product is recognised user should insert some info:

  • product expiration date
  • storage
  • measure (unit of measure and quantity) (e.g. 500 gr)
  • units (the number of product he buyer, 2 packs of 500gr cookies)

Fresh item

TBD

See add item discussion to know if user wants to add an item that is the same as an existing item.

Delete item

User deletes the item from his inventory. User can undo the deletion, in this case a new item with same information is created.

Open item

A user can open one item at a time. Opening an item means decrease the unit of the item by 1 each time and creating a new item with the date the user opened the item.

Consume an item

User chooses how many units of the item he has consumed and this value is decrease from the total item units. This action is allow only if the units of the item is greater than 1.

Completely consume an item

This action is similar to delete item. The item will be removed from the inventory. User can undo the action, in this case a new item with same information is created.

Notifications

Notification types

There are several type of notifications.

Notification - Daily reminder

A notification of type DAILY_REMINDER is sent to the user every day to remind them to use the app, e.g., "What did you consume today?".

Notification - Expiring items

A notification of type EXPIRING_ITEMS is sent to the user when one or more items are going to expire soon, e.g., "You have N items that are going to expire soon".

Ideally this notification should be sent at a time when the user is likely to decide what they're going to eat next, e.g., in the morning or in the early evening before dinner. The time should be configurable by the user.

Notification - Expired items

A notification of type EXPIRED_ITEMS is sent to the user when one or more items have expired, e.g., "You have N items that have expired".

Notification page

All notifications are listed inside the notifications page.