Skip to content
Makhmudov Babur edited this page Jun 9, 2021 · 51 revisions

Twake: Mobile application

Table of contents

State management

State management in Twake Mobile is handled via Cubits, which in itself is a subset of the BLoC pattern. The library used for this purpose is Flutter BLoC. For the insertion of the Cubits into the widget tree, the application makes use of the GetX library.

Data models

All the data the application works with is defined in data models, so everything is typed as much as possible, with minimal amount of dynamic data types. There are top level models (for describing entities) and also there's satellite models to represent nested data structures.

Authentication

The model contains only JWToken pair and their expiration timestamps. It's used to hold the current (most recent JWToken pair), which in turn is used in Twake API requests.

Fields:

Field name Field type Field description Nullable?
token String Main JWToken used to authenticate user with Twake backend false
refreshToken String Token used to get a new JWToken after the current one has expired false
expiration int Timestamp, when main token expires false
refreshExpiration int Timestamp, when refresh token expires false

Account

The model contains all the information about user (any user, not just the app user).

Fields:

Field name Field type Field description Nullable?
id String User's unique identifier false
email String User's registered email false
firstname String User's first name true
lastname String User's last name true
username String Username used in mentions false
thumbnail String Link to network resource from which user's avatar can be obtained true
consoleId String User's identifier in Twake Console management system true
statusIcon String Emoji code used as user's status icon true
status String Text describing user's current status true
language String User's preferred locale true
lastActivity int Last timestamp when user had some online activity false

Company

The model contains all the information about a particular company, of which app user might have many.

Fields:

Field name Field type Field description Nullable?
id String UUID like company's unique identifier false
name String Human readable name given to company false
logo String Link to network resource which points to logo image true
totalMembers int Number of collaborators invited to this company false
selectedWorkspace String Identifier of the last selected workspace in company true
permissions [] Permissions granted to app user to make changes in company false

Workspace

The model contains all the information about a particular workspace, there could be many workspaces in single company.

List of fields in Workspace model:

Field name Field type Field description Nullable?
id String UUID like workspace's unique identifier false
name String Human readable name given to workspace false
logo String Link to network resource which points to logo image true
companyId String Identifier of the parent company, to which workspace belongs false
totalMembers int Number of collaborators invited to this workspace false
userLastAccess int Timestamp value (in milliseconds), when the user last accessed the given workspace false
permissions [] Permissions granted to app user to make changes in given workspace false

Channel

The model contains all the information about a channel (public/private/direct), public and private channels belong to particular workspace, while direct channels belong to particular company.

Fields

Field name Field type Field description Nullable?
id String Unique identifier of the given channel false
name String Human readable name of the channel false
icon String Emoji short name like :emoji:, used as channel icon, direct channels don't have one true
description String Human readable description of channel's purpose true
companyId String Unique identifier of parent company false
workspaceId String Unique identifier of parent workspace, direct channels have it set to 'direct' false
membersCount int Number of members, that the channel contains false
members [] List of members' identifiers, only direct channels have it non empty false
visibility ChannelVisibility Visibility type of the channel false
lastActivity int Timestamp (in milliseconds), of when the last event occured in channel false
lastMessage MessageSummary The last message sent to channel true
userLastAccess int Timestamp value (in milliseconds), when the user last accessed the given channel false
draft String Unsent string by user, autosaved for further use true
permissions [] Permissions granted to app user to make changes in a given channel false

Getters:

Getter Type Description
hasUnread bool Whether userLastAccess is less than lastActivity field
membersCount int Amount of members in given channel
hash int A unique value, which encodes the state of given channel, found by: name.hashCode + icon.hashCode + lastActivity + members.length

Message

The model contains all the information regarding a single message, message is agnostic of the fact where it was submitted: public, private, direct channel or in a thread. Thus the same model is used for messages everywhere.

Fields:

Field name Field type Field description Nullable?
id String Unique identifier of the given message false
threadId String Identifier of the parent thread (if any) true
channelId String Identifier of the parent channel (public/private/direct) false
responsesCount int Number of responses to the message (only for channel level) false
userId String Identifier of user, who submitted the message, might be absent, if the message was sent by bot false
creationDate int Timestamp (in milliseconds), when the message was first submitted false
modificationDate int Timestamp (in milliseconds), when the message was edited, usually is the same as creationDate false
content MessageContent The content of the message false
reactions Reaction[] Reactions received by the given message from users false
username String Username of the the user, who submitted the message false
firstname String First name of the the user, who submitted the message true
lastname String Last name of the the user, who submitted the message true
thumbnail String Link to user's avatar, who submitted the message true
draft String Unsent string by user, autosaved for further use (only exists in parent messages) true
_isRead int binary value (int for easy storage in sqlite), indicates whether message is read by user or not, 1 by default false

Note: either userId or appId should be present

Getters:

Getter Type Description
hash int Pseudo unique hashCode of the message. See the calculation formula below
sender String Human readable string, either user's firstname + lastname or username (if firstname is absent)
isRead String wrapper around _isRead field, _isRead > 0 ? true : false

Another note: hash field is calulated as the sum of properties of the following fields:

  • hashCode of id
  • hashCode of content.originalStr
  • sum of hashCodes of names of each reaction in reactions field
  • sum of count of each reaction in reactions field

Example:

final hash = id.hashCode +
        content.originalStr.hashCode +
        reactions.fold(0, (acc, r) => r.name.hashCode + acc) +
        reactions.fold(0, (acc, r) => r.count + acc);

Globals

This class acts like big global variables storage, and keeps the following state.

List of getters/setters in Globals model:

Field name Field type Field description Nullable?
host String Selected HOST (e.g. https://chat.twake.app) false
companyId String Selected company id true
workspaceId String Selected workspace id true
channelsType ChannelsType Which type of channel tab is selected on main screen: direct or public/private (default) false
channelId String Selected channel id true
threadId String Selected thread id true
token String Current token to access the Twake API false
fcmToken String Token obtained from Firebase Cloud Messaging false
userId String Identifier of the logged in user false
isNetworkConnected bool Current state of network connection false

Note: The model should be a globally accessable singleton. The class instance should be initialized before everything else, if the user has already logged into the app, in which case the previous state of the app should be available in local storage, otherwise the instance should be initialized before login by user with default data.

All the fields of the singleton instance should be kept up to date by other cubits, and the class itself is responsible for backing up all the changes to local storage

File

This model contains the information about files, either uploaded or the ones that can be downloaded.

Fields:

Field name Field type Field description Nullable?
id String Unique identifier of the file in Twake storage false
name String Name of the file, as it stored in Twake storage false
preview String Relative link to preview image of the file true
download String Relative link to download the file false
size int The size of the file in bytes false

Badge

This model contains the information badge info: counters which are used to indicate the amount of unread messages on each level of hierarchy (company/workspace/channel)

Fields:

Field name Field type Field description Nullable?
type BadgeType Level of hierarchy the counter belongs to false
id String Identifier of the entity on that level of hierarchy false
count int The number of unread messages, that given entity has false

Methods:

Method name Arguments Return type Description
matches BadgeType type, String id bool Returns true if the given Badge has its type and id equal to the arguments

Getters:

Getter Type Description
hash int Value which encodes the current state of the Badge: type.hashCode + id.hashCode

Notifications

There two types of Notifications in Twake Mobile:

  1. FirebaseNotification - received via Firebase Cloud Messaging service, used for notifications about new messages. In order to communicate with Firebase Cloud Messaging the application makes use of the official plugin.
  2. LocalNotification - generated locally by the application itself. The plugin used for this purpose is Flutter local notification

FirebaseNotification

Fields:

Field name Field type Field description Nullable?
headers NotificationHeaders The information which is displayed in notification area of the device false
payload NotificationPayload The data, which is further used to retrieve the necessary message from API false

LocalNotification

Fields:

Field name Field type Field description Nullable?
type LocalNotificationType Discriminator field, used to distinguish different kinds of notification false
payload Map<String, dynamic> The key value store, which holds the dynamic payload, to be handle false

Getters:

Getter Type Description
stringified String Convenience method to convert the model state to json encoded string

SocketIO

This section describes all the socketIO related data models which are received via socketIO channel, which is the primary way the application can be kept in realtime sync with the Twake server.

SocketIOEvent

Fields:

Field name Field type Field description Nullable?
name String Name of the socketIO room, from which the event was received false
data MessageData Payload of the event, which contains the data, regarding the modified message false
Example:
{
    name: previous::channels/2982dc0a-65aa-47ae-a13c-082b2e3cc2a9/messages/updates, 
    data: {
        client_id: system, 
        action: update, 
        message_id: 5828c718-b49e-11eb-8ae0-0242ac120003, 
        thread_id: c920712e-b49d-11eb-8ed2-0242ac120003
    }
}

SocketIOResource

Fields:

Field name Field type Field description Nullable?
action ResourceAction Action that was performed on the resource false
type ResourceType Type of the resource, depending on it, the resource field is treated accordingly false
resource Map<String, dynamic> Data that should be updated in application false

Examples: Channel had some activity:

{
    action: updated, 
    room: /companies/ac1a0544-1dcc-11eb-bf9f-0242ac120004/workspaces/ae6e73e8-504e-11eb-9a9c-0242ac120004/channels?type=public, 
    type: channel_activity, 
    path: /companies/ac1a0544-1dcc-11eb-bf9f-0242ac120004/workspaces/ae6e73e8-504e-11eb-9a9c-0242ac120004/channels/2982dc0a-65aa-47ae-a13c-082b2e3cc2a9, 
    resource: {
        company_id: ac1a0544-1dcc-11eb-bf9f-0242ac120004, 
        workspace_id: ae6e73e8-504e-11eb-9a9c-0242ac120004, 
        id: 2982dc0a-65aa-47ae-a13c-082b2e3cc2a9, 
        last_activity: 1620987774000, 
        last_message: {
            date: 1620987774000, 
            sender: 46a68a02-1dcc-11eb-95bd-0242ac120004, 
            sender_name: First Last, 
            title: 📄 ch1 in TestCompany • WS1, text: Message 2
        }
    }
}

Channel was created in workspace:

{
    action: saved, 
    room: /companies/ac1a0544-1dcc-11eb-bf9f-0242ac120004/workspaces/ae6e73e8-504e-11eb-9a9c-0242ac120004/channels?type=public, 
    type: channel, 
    path: /companies/ac1a0544-1dcc-11eb-bf9f-0242ac120004/workspaces/ae6e73e8-504e-11eb-9a9c-0242ac120004/channels/2982dc0a-65aa-47ae-a13c-082b2e3cc2a9, 
    resource: {
        is_default: false, 
        archived: false, 
        members: [], 
        connectors: [], 
        last_activity: 1620987956000, 
        company_id: ac1a0544-1dcc-11eb-bf9f-0242ac120004, 
        workspace_id: ae6e73e8-504e-11eb-9a9c-0242ac120004, 
        id: 2982dc0a-65aa-47ae-a13c-082b2e3cc2a9, 
        archivation_date: 0, 
        channel_group: , 
        description: , 
        icon: 📄, 
        name: Primary channel, 
        owner: 46a68a02-1dcc-11eb-95bd-0242ac120004, 
        visibility: public
    }
}

SocketIORoom

Fields:

Field name Field type Field description Nullable?
key ResourceAction Path, which is used to subscribe to room false
type RoomType Type of the room to subscribe to false
id String Id of the entity, the changes of which the application listens to false
subscribed bool Whether the room is currently subscribed to false

Satellite models:

ChannelVisibility

This enum describes all the possible values of that the visibility property of channel can take. Each field of this enum is JSON serializable/deserializable.

Possible values:

  • public (JSON value 'public')
  • private (JSON value 'private')
  • direct (JSON value 'direct')

MessageSummary

This a complementary model which is used to hold information about last message in a given channel. Also used to hold push notification data.

Fields:

Field name Field type Field description Nullable?
date int Timestamp (in milliseconds), when the last message was submitted false
sender String Unique identifier of the user who submitted the message false
senderName String Human readable name of the sender, usually first name + last name false
title String Description of where the message was submitted (company, workspace, channel) false
text String Text content of the message true

Example JSON:

{
    "date": 1621233242000,
    "sender": "46a68a02-1dcc-11eb-95bd-0242ac120004",
    "sender_name": "Firstname Lastname",
    "title": "General in TestCompany • Analytics",
    "text": "some text"
}

MessageContent

This is a complementary model used to hold the content of the message which is composed of 2 parts:

  1. The string input by the user
  2. Parsed structure of the input + some additional elements (e.g. attachments)

Fields:

Field name Field type Field description Nullable?
originalStr String Text input by user, may be absent if the message contains only attachments true
prepared List Parsed structure of the message, which is the list of Strings, Maps and Lists false

Note: prepared field should be parsed by special parser which understands the syntax used by twake, in order to convert it to list of WidgetSpans, for later rendering.

Reaction

Complementary model to hold reaction to a particular message.

Fields:

Field name Field type Field description Nullable?
name String Emoji unicode (renderable) false
users String[] List of users' identifiers, who reacted with this emoji false
count int number of users reacted with this emoji false

ChannelsType

This enum describes the current tab, that is selected on main screen. Each tab lists different types of channels: either directs or public/private (commons) Each field of this enum is JSON serializable/deserializable.

Possible values:

  • commons (JSON value 'commons')
  • directs (JSON value 'directs')

BadgeType

This enum describes the level at which the given badge counter holds the information about

Possible values:

  • company (JSON value 'company')
  • workspace (JSON value 'workspace')
  • channel (JSON value 'channel')

NotificationHeaders

Complementary model to hold the information, which is shown on notification area

Fields:

Field name Field type Field description Nullable?
title String Title of the notification false
body String Content of the message false

NotificationPayload

Complementary model to hold the data regarding the new unread message

Fields:

Field name Field type Field description Nullable?
companyId String Identifier of the company where new message was received false
workspaceId String Identifier of the workspace where new message was received false
channelId String Identifier of the channel where new message was received false
threadId String Identifier of the thread where new message was received true
messageId String Identifier of the new message itself false

Getters:

Getter Type Description
stringified String Convenience method to convert the model state to json encoded string

LocalNotificationType

This enum describes all the possible types that the local notification can be of. Each field of this enum is JSON serializable/deserializable. As the application evolves, new types might be added.

Possible values:

  • message (JSON value 'message')
  • file (JSON value 'file')

MessageData

Complementary model to hold the data regarding the updated message, which is sent over socketIO channel.

Fields:

Field name Field type Field description Nullable?
action IOEventAction Describes what action was performed on the message false
threadId String Identifier of the thread where the message was modified false
messageId String Identifier of the modified message itself false

IOEventAction

Enum, which describes all the possible actions which can be performed over the message (socketIO channel specific)

Possible values:

  • remove (JSON value 'remove')
  • update (JSON value 'update')

ResourceAction

Enum, which describes all the possible actions which can be performed over the resource (socketIO channel specific)

Possible values:

  • updated (JSON value 'updated')
  • saved (JSON value 'saved')
  • deleted (JSON value 'deleted')

ResourceType

Enum, which describes all the possible resources that can be updated over the socketIO channel

Possible values:

  • channel (JSON value 'channel')
  • channelMember (JSON value 'channel_member')
  • channelActivity (JSON value 'channel_activity')

Cubits

For the purposes of building a reactive UI, the application makes use of various cubits, each of which is responsible for updating particular subset of data.

Authentication Cubit

This cubit is pretty straightforward and has the following tasks to perform:

  • authenticate user
  • keep the JWTokens fresh
  • keep the JWToken in Globals updated
  • logout user from system, cleaning up entire local storage

Methods:

Name Arguments Return type Description
authenticate String username, String password - Try to authenticate with the provided credentials on Twake, update the state depending on result. On success start internal token validation
checkAuthentication - - Try to validate token (if present in local storage), update the state depending on result. On success start internal token validation
logout - - Update the state to initial, and cleanup the entire local storage

States:

Name Fields Description
AuthenticationInitial - Initial state, which the cubit emits upon initialization
AuthenticationInProgress - State emitted, when the authentication process starts
AuthenticationFailure String username, String password State emitted, when authentication fails. It contains the entered username and password for user's convenience
PostAuthenticationSyncInProgress - State emitted, when the user logs in the first time, and the application starts syncing user's data from the server
PostAuthenticationSyncFailed - State emitted, when the user logs in the first time, and the data sync from twake server did not succeed
AuthenticationSuccess _ State emitted when user successfully authenticateed or he/she already had an active token pair

Account Cubit

This cubit is responsible for 2 main features:

  1. Retrieve and save user information for the Profile screen.
  2. Fetching other users' available data for different purposes like search.

Methods:

Name Arguments Return type Description
fetch String? userId - Fetch the user by his id, first fetch the user from local storage, then make attempt to fetch from remote API. If userId is not provided, fetch current user
fetchStateless String userId Account Fetch the user from local storage and return it, without updating cubit's state

States:

Name Fields Description
AccountInitial - The initial state, set during cubit's initialization
AccountLoadInProgress - State, which is emitted when the account fetch has begun
AccountLoadSuccess Account account, int hash State that is emitted when the account has been successfully retrieved from either remote or local storage

Companies Cubit

For now company management happens on console side, so this cubit is pretty simple. What it does:

  • Fetch the list of available companies
  • Manage companies selection
  • Update Globals instance after selection

Methods:

Name Arguments Return type Description
fetch - - Fetch all the user's companies, first fetch from local storage, and then try to fetch from remote.
selectCompany String companyId - Update the cubit's state in such a way that the currently selected company is changed
selectWorkspace String workspaceId - For the selected company update its selectedWorkspace field, for the purposes of restoring state
getSelectedCompany - Company? Returns selected company in case if the companies are already loaded or null otherwise

States:

Name Fields Description
CompaniesInitial - The initial state, set during cubit's initialization
CompaniesLoadInProgress - State, which is emitted when the companies fetch has begun
CompaniesLoadSuccess Company[] companies, Company selected State that is emitted when the companies has been successfully retrieved from either remote or local storage

Workspaces Cubit

Task that are handled by this cubit:

  • Fetching corresponding workspaces upon company selection
  • Changing current workspace
  • Update Globals accordingly
  • Adding new workspaces (provided user has necessary permissions)
  • Editing workspaces (including collaborators management)
  • Removing workspace (provided user has necessary permissions)

It should be possible to switch workspace programmatically, most common case being user click on notification.

Methods:

Name Arguments Return type Description
fetch String companyId - Fetch all the workspaces in a given company, first fetch from local storage, and then try to fetch from remote.
fetchMembers String workspaceId Account[] Fetch all the collaborators in given workspace and return them
selectWorkspace String workspaceId - Update the cubit's state with the new selected workspace, also update Globals
createWorkspace String companyId, String name, String[] members - Try to create new workspace in a given company with given members, select created workspace and update cubit's state

States:

Name Fields Description
WorkspacesInitial - The initial state, set during cubit's initialization
WorkspacesLoadInProgress - State, which is emitted when the workspaces fetch has begun
WorkspacesLoadSuccess Workspace[] companies, Workspace selected State that is emitted when the workspaces has been successfully retrieved from either remote or local storage

Channels Cubit

This cubit should manage everything related to channels, both public and private.

Task performed by this cubit:

  • Fetching the list of channels after workspace selection
  • Channel selection, to open the list of messages in it
  • Channel creation (public/private)
  • Channel edition
  • Channels deletion

Methods:

Name Arguments Return type Description
fetch String companyId, workspaceId - Fetch all the channels in a given company or workspace (depending on channel type), fetch from local storage, and then try to fetch from remote.
create String name, String icon, String description, ChannelVisibility visibility bool Try to create a channel, if successful return true, and update the current selected channel in cubit's state
edit Channel channel, String name, String icon, String description, ChannelVisibility visibility bool Try to edit a channel, if successful return true, and update the current selected channel in cubit's state
delete Channel channel bool Try to delete the channel, if successful return true, and update the current channels list in cubit's state
fetchMembers Channel channel Account[] Fetch all the members for the given channel
addMembers Channel channel, String[] usersToAdd bool Try to add given users to channel as members, if successful return true and update the cubit's state
removeMembers Channel channel, String[] usersToRemove bool Try to remove given users from channel's members list, if successful return true and update the cubit's state
selectChannel String channelId - Update the cubit's state with newly selected channel, and update Globals
clearSelection String channelId - Update the cubit's state removing selected channel, and update Globals
listenToActivityChanges - - Launches infinite loop, that listens to all the channel activity changes over socketIO channel, and on receiving any event, updates the cubit's state accordingly
listenToChannelChanges - - Launches infinite loop, that listens to all the channel changes over socketIO channel, and on receiving any event, updates the cubit's state accordingly

States:

Name Fields Description
ChannelsInitial - The initial state, set during cubit's initialization
ChannelsLoadInProgress - State, which is emitted when the channels fetch has begun
ChannelsLoadSuccess Channel[] companies, Channel selected State that is emitted when the channels has been successfully retrieved from either remote or local storage

Directs Cubit

Same as Channels Cubit

Note: the socketIO streams in both cubits differ.

Messages Cubit

This cubit is one the most used ones, because it's responsible for managing messages.

Task performed by Messages Cubit:

  • Loading the list of top levele messages in channel (public/private) or direct chats.
  • Sending new messages
  • Fetching messages on notification or socketIO event
  • Editing the message
  • Reacting to message
  • Deleting message

Methods:

Name Arguments Return type Description
fetch String channelId, threadId - Fetch the messages in a given channel (and possibly filter by thread), fetch from local storage, and then try to fetch from remote.
fetchBefore String threadId - Fetch all previous messages in a given channel (and possibly filter by thread), messages that preceed (by timestamp) the first one in current cubit's state. Fetch from local storage, and then try to fetch from remote.
send String originalStr, File[] attachments, String threadId - Try to send the message, update the cubit's state immediately before making request to API (hoping for the best case scenario). After successful API response, update the cubit's state again with new message
edit Message message, String editedText, File[] newAttachments, String threadId - Try to update the message's content, update the cubit's state immediately before making request to API (hoping for the best case scenario)
react Message message, String reaction - Try to update message's reaction, update the cubit's state immediately before making API request. The API request can awaited and in case of failure, the reactions can be rolled back
delete Message message - Try to delete the message, update the cubit's state immediately before making API request. The API request can awaited and in case of failure, the message can be restored
selectThread String messageId - Update Globals with the selected thread identifier
clearSelectedThread - - Update Globals removing selectedThread identifier
listenToMessageChanges - - Launches infinite loop, that listens to all the message related events over socketIO channel, and on receiving any event, updates the cubit's state accordingly

States:

Name Fields Description
MessagesInitial - The initial state, set during cubit's initialization
MessagesLoadInProgress - State, which is emitted when the messages fetch has begun
MessagesBeforeLoadInProgress Message[] messages, int hash State that is emitted when the previous messages fetch has begun
MessagesLoadSuccess Message[] messages, Message parentMessage, int hash State that is emitted when the messages has been successfully retrieved from either remote or local storage. Same state is used both for channel messages and thread messages

Badges Cubit

Mentions Cubit

File Cubit

Services

Pages

Widgets

Miscellaneous

Clone this wiki locally