Skip to content
Evgenii edited this page Jan 3, 2023 · 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
AccountLoadFailure String message State that is emitted when a failure occured while attempting to retrieve the account from remote or local data source

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

Add Channel Cubit

This cubit should manage the screen of new channel creation

Task performed by this cubit:

  • Change channel visibility
  • Change channel icon
  • Add member to channel
  • Create new channel by calling Channels Cubit's method

Add Member Cubit

This cubit should manage the screen adding and removing member from channel during its creation phase

Task performed by this cubit:

  • Search members among workspace members
  • Add members to channel
  • Remove members from channel

Channel Settings Cubit

This cubit should manage the screen of editing channel properties

Task performed by this cubit:

  • Change channel visibility
  • Start channel editing

Channel Settings Cubit

This cubit should manage the screen of editing channel properties

Task performed by this cubit:

  • Change channel name, description, icon
  • Persist changes to backend

Member Management Cubit

This cubit should manage the screen of editing existing channel's members list

Task performed by this cubit:

  • Search members among workspace members
  • Add new member to list
  • Remove member to the list
  • Persist changes to backend

New Direct Cubit

This cubit should manage the screen of creating new direct chat with only one user

Task performed by this cubit:

  • Search members among workspace members to start the chat with
  • List out recent direct chats in a given company
  • Create and persist new direct chat on backend

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

This cubit is responsible for continious synchronization of unread messages badges, which are in turn used in:

  • List of companies
  • List of workspaces
  • List of channels (public/private/direct)

Those badges are basically numbers, which represent the amount of unread messages, which contain some form of mention, which user has subscribed to.

Methods:

Name Arguments Return type Description
fetch - - Fetch the all the badges for globally selected company, fetch from local storage, and then try to fetch from remote.
listenToBadgeChanges - - Launches infinite loop which listens to the socketIO events which are related to badge counter updates, and updates cubit's state accordingly

States:

Name Fields Description
BadgesInitial - The initial state, set during cubit's initialization
BadgesLoadInProgress - State, which is emitted when the badges fetch has begun
BadgesLoadSuccess Badge[] badges, int hash State that is emitted when the badges has been successfully retrieved from either remote or local storage

Mentions Cubit

This cubit manages the list of users, which can be mentioned in given workspace.

Tasks performed by this cubit:

  • It can provide that list filtered, based on the input given by user when typing the message
  • It can parse any string, find all the substring which look like mentions, and run them against the list of users in given workspace, in order to find all the ids, which then (if found) can be attached to mentions. So what it does is it complements the mentions with users ids, for the subsequent use by twacode parser.

Methods:

Name Arguments Return type Description
fetch String search term - Fetch all the user from the selected workspace filtered by the searchTerm, update the cubit's state with the result
reset - - Reset the cubit's state to initial
completeMentions String messageText String Parses messageText, and check whether it includes any mentions, if it finds any, it tries to substitute all the mentions with mention + userId sequence if possible. And then it returns new string

States:

Name Fields Description
MentionsInitial - The initial state, set during cubit's initialization
MentionsLoadSuccess Account[] account State that is emitted, when accounts has been successfully retrieved from local storage, which satisfies searchTerm

File Cubit

This cubit is responsible for keeping track of the files, that are being uploaded/downloaded to/from channel.

Task performed by this cubit:

  • File upload (as multipart-form-data)
  • File download (using Dio)
  • Keeping the list of uploads before sending the message (act like an accumulator)

Methods:

Name Arguments Return type Description
upload String path - Upload the file from the provided path to channel, create File based on response, add the file to accumulator and update the cubit's state accordingly

| download | File file | - | not implemented yet |

States:

Name Fields Description
FileInitial - The initial state, set during cubit's initialization
FileUploadInProgress CancelToken token, String name, int size State that is emitted when file upload starts
FileUploadFailed String reason State that is emitted when file upload failes
FileUploadSuccess File[] State that is emitted when file has been successfully uploaded, contains all the files that has been uploaded so far
FileDownloadInProgress CancelToken token, File file State that is emitted when file download starts
FileDownloadFailed String reason State that is emitted when file download failes
FileDownloadSuccess String downloadPath State that is emitted when file download is successfully complete, contains the path where the file has been downloaded

Gallery Cubit

This cubit is responsible for receiving media files from the device's albums their display and selection

Task performed by this cubit:

  • Get Gallery Assets (photo_manager: ^)
  • Tab Change
  • Add Files Indexes

Methods:

Name Arguments Return type Description
getGalleryAssets bool isGettingNewAssets - get Gallery Assets from the device's albums
tabChange int tab - change selected Tab
addFileIndex int index - add file index which was selected

States:

Name Fields Description
init GalleryStateStatus galleryStateStatus, List assetsList, List assetEntity, List fileList, List selectedFilesIndex, int selectedTab, int loadedAssetsAmount, bool isAddingDummyAssets The initial state, set during cubit's initialization
loading GalleryStateStatus galleryStateStatus, List assetsList, List assetEntity, List fileList, List selectedFilesIndex, int selectedTab, int loadedAssetsAmount, bool isAddingDummyAssets State that is emitted when assets loading
done GalleryStateStatus galleryStateStatus, List assetsList, List assetEntity, List fileList, List selectedFilesIndex, int selectedTab, int loadedAssetsAmount, bool isAddingDummyAssets State that is emitted when assets successfully loaded
failed GalleryStateStatus galleryStateStatus, List assetsList, List assetEntity, List fileList, List selectedFilesIndex, int selectedTab, int loadedAssetsAmount, bool isAddingDummyAssets State that is emitted when an error occurred during the loading process
newSelect GalleryStateStatus galleryStateStatus, List assetsList, List assetEntity, List fileList, List selectedFilesIndex, int selectedTab, int loadedAssetsAmount, bool isAddingDummyAssets State that is emitted when user select a new asset

Cache in chat Cubit

This сubit is responsible for caching files in channels and keeps data in the сubit state

Task performed by this cubit:

  • Cache files
  • Find cached file
  • Clean cached files

Methods:

Name Arguments Return type Description
cacheFile File - add a file to cachedList
findCachedFile String fileId File? find cached file
cleanCachedFiles - - clean state

Camera cubit

This сubit is responsible for interacting with the device's camera

Task performed by this cubit:

  • Select the main camera or switch it if that is needed
  • Select flash modes

Methods:

Name Arguments Return type Description
getCamera - - Get the device's camera
cameraLensSwitch - - Switches between cameras
nextFlashMode - - Switches between the flash

States:

Name Fields Description
init final CameraStateStatus cameraStateStatus, final List availableCameras, final int flashMode, final bool cameraLensModeSwitch The initial state, set during cubit's initialization
loading final CameraStateStatus cameraStateStatus, final List availableCameras, final int flashMode, final bool cameraLensModeSwitch State that is emitted waiting for data to be received
found final CameraStateStatus cameraStateStatus, final List availableCameras, final int flashMode, final bool cameraLensModeSwitch State that is emitted when cameras are detected
done final CameraStateStatus cameraStateStatus, final List availableCameras, final int flashMode, final bool cameraLensModeSwitch State that is emitted when the camera is initialized
failed final CameraStateStatus cameraStateStatus, final List availableCameras, final int flashMode, final bool cameraLensModeSwitch State that is emitted when initialization is failed

Services

All services in application are implemented as singletons, and must be initialized at startup. They can be accessed from anywhere in the app, once initialized, and they provide all the different kinds of data access mechanisms.

Init Service

Main purpose of this service is to initialize other service before application starts and sync data with server on user's first successful login

Methods:

  1. preAuthenticationInit - initialize all the services that the application depends on, and create a Globals instance
  2. syncData - sequentially pull user's data from remote server and save it to local storage. The method should only be called once after successful login. Execution time might be long, dependind on the amount of data to sync.

API Service

This service is main entry point for communicating with Twake API. The ApiService class is implemented as a Singleton and like many other singletons can be accessed via instance static property.

The service is relatively simple, and acts as a very thin wrapper around Dio instance, which acts as HTTP client. The only somewhat complex logic handled by this service is done via Dio interceptors:

  1. It intercepts each request and completes the endpoint path with current host (accessed through Globals instance). After that it checks whether there's a need to add the authentication token to headers of the request, and adds one if necessary.
  2. It intercepts each error and sends error logs or messages to Sentry service, dependind on severity of the error.

Storage Service

Storage service provides very thin abstraction layer above sqflite package. Basically it only provides persistence mechanism only.

It's implemented as a class with a few static methods:

  • insert - create a new entry in given table (replace if exists)
  • multiInsert - create multiple entries in a given table (replace those which exist)
  • select - query entries from given table, which satisfy given criteria
  • first - light wrapper around select method, which returns the first element from result set
  • rawSelect - last resort method, if you need to make complex query (with joins for ex.)

List of available tables:

  • authentication
  • account
  • account2workspace (for relations between account and workspace)
  • company
  • workspace
  • channel
  • message
  • badge
  • globals

Each of the above given table is in one to one correspondence with Data models.
In order to reduce errors, service provides a special Table enum, which holds all available table names. If you decide to add another model, which requires persistency, then you have to also create another entry in Table enum.

Example usage of methods:

  1. insert
    final company = Company(id: "id", name: "name", logo: "http://logo.com");
    await StorageService.instance.insert(Table.company, company);
  2. multiInsert
    final companies = [
        Company(id: "id1", name: "name1", logo: "http://logo.com");
        Company(id: "id2", name: "name2", logo: "http://logo.com");
    ];
    await Storage.instance.multiInsert(Table.company, companies);
  3. select
    await Storage.instance.select(Table.company, where: "id = ?", whereArgs: ["id1"]);
    await Storage.instance.select(Table.company, where: "name LIKE ?", whereArgs: ["Coca-Cola"]);
  4. first
    await Storage.instance.first(Table.company, where: "id = ?", whereArgs: ["id1"]);
    await Storage.instance.first(Table.company, where: "name LIKE ?", whereArgs: ["Coca-Cola"]);
  5. rawSelect
    final sql = 'SELECT u.* FROM user AS u JOIN user2workspace u2w ON u.id = u2w.user_id WHERE u2w.workspace_id = ?';
    await Storage.instance.rawSelect(sql, args: ["id1"]);

Push Notifications Service

This service moderately abstracts away the nitty-gritties of using Firebase Messaging service and Flutter Local Notifications Plugin.

It provides the following streams of events, that can be more conveniently handled by consumers of the service:

  1. foregroundMessageStream - Stream of messages from Firebase, which was received, while the application was in foreground
  2. notificationClickStream - Stream of messages from Firebase which was triggered by user click on corresponding notification, while application was in background
  3. localNotifications - Stream of LocalNotification messages, which was triggered by user clicks on corresponding local notifications

The service also has a method to request all the necessary permissions, in order to be able to work with notifications requestPermission.

Service provides to async getters which can be used to determine whether the app has been launched via notification click:

  1. checkRemoteNotificationClick - checks for remote (firebase) notification and returns notification body if present
  2. checkLocalNotificationClick - checks for local notification and returns the payload if present

In order to show local notification on demand, one can make use of showLocal method, which will show the notification and returns the notification's id, which further can be used to cancel pending notification if it's still in notification area via cancelLocal method.

SocketIO Service

This service is the primary and lowest level way to work with socketIO channels.

Main functionality provided by the service is subscriptions management to socketIO rooms:

  1. subscribe - method to start listening to events in particular room
  2. unsubsribe - method to stop listening to events in particular room Navigator Service

The service internally manages socketio connections health and automatically tries to reconnect after network loss events. Consumers of the service are responsible for resubscribing to rooms after network connection loss.

Service provides two streams which can be listened to:

  1. eventStream - socketIO events that relate to message changes
  2. resourceStream - socketIO events that relate to everything else (channels, members, etc)

Streams can be filtered and transformed by the consumers.

Synchronization Service

This service is a higher level abstraction over SocketIO Service and Push Notifications Service. It provides some conveniences, such as filtered socketio streams, autoresubscriptions, and management of foreground notifications

Streams:

  1. socketIODirectStream - stream of socketio resources that are related to direct chats (changing the icon, or the name)
  2. socketIODirectActivityStream - stream of socketio resources that are related to activity in direct chats (changing lastActivity)
  3. socketIOChannelStream - stream of socketio resources that are related to public or private channels
  4. socketIOChannelActivityStream - stream of socketio resources that are related to activity in public or private channels
  5. socketIOChannelMessageStream - stream of message related events in channels or direct chats
  6. socketIOThreadMessageStream - stream of message related events in threads (channels and direct chats alike)

Getters:

  1. socketIORooms - the list of available for subscription socketIO rooms, in a globally selected workspace

Methods:

  1. foregroundMessagesCheck - launches infinite loop which listenes to push notification events from firebase while the application is in the foreground. Once the notification is received it's converted to LocalNotification and shown to user if he/she's not already viewing the notification's channel.
  2. subscribeForChannels - convenience method to subscribe to rooms which are related to channel or direct chat related changes over socketio.
  3. subscribeToBadges - convenience method, to subscribe to badge counter changes over socketio
  4. cancelNotificationsForChannel - convenience method, to cancel (remove) all the pending local notifications which are related to a given channel
  5. subscribeToMessages - subscribes to message events on socketIO channel, which occur in a given channel
  6. unsubsribeFromMessages - cancels subscribtion to message events in a given channel

After reacquiring network connection, service will try to resubscribe to channels and badge counter update on socketio channel.

Navigator Service

This service provides mechanisms to navigate to pages and update cubits' states (which the pages depend on) before navigation.

Upon initialization the service starts two loops, to check for notification clicks: one for firebase notifications, and one for local notifications, and navigates accordingly if the user click occurs.

Methods:

  1. navigateOnNotificationLaunch - method that should be called during app initialization. It checks whether notification click triggered application launch, and if so, navigates to appropriate page.
  2. listeneToLocals - loops over local notification click events, and performs navigation
  3. listeneToRemote - loops over firebase notification click events, and performs navigation
  4. navigate - heart of the service, used to navigate to required page

API endpoints

/logout - endpoint for terminating user session in twake

HTTP METHODS:

  1. POST - used to notify Twake backend to stop sending notifications to this device
POST /logout {
    "fcm_token": <fcm_token>
}

/user - endpoint for working with user entities

HTTP METHODS:

  1. GET - get user by provided ID, if ID is not provided get user by JWT
GET /user {
    "id": <user_id>
}

Expected result is Account compatible data structure in JSON format

/companies - endpoint for working with user's companies

HTTP METHODS:

  1. GET - get the list of all user's companies
GET /companies

Expected result is the list of Company compatible data structures in JSON format

/workspaces - endpoint for working with workspaces in a given company

HTTP METHODS:

  1. GET - get the list of all workspaces in a provided company
GET /workspaces {
    "company_id": <company_id>
}

Expected result is the list of Workspace compatible data structures in JSON format

  1. POST - create new workspaces with provided data
POST /workspaces {
    "company_id": <company_id>,
    "logo": <need to provide id of uploaded file?>,
    "name": <name>,
    "members": <Array<string> emails>
}

Expected result is Workspace compatible object in JSON format

workspaces/members/ - endpoint for working with workspace members

HTTP METHODS:

  1. GET - get all the members in a given workspace
GET /workspaces/members {
    "company_id": <company_id>,
    "workspace_id": <workspace_id>
}

Expected result is the list of Account compatible objects in JSON format

/badges - endpoint for working with unread message counters (aka badges)

HTTP METHODS:

  1. GET - get all the unread message counters for workspaces and channels in a given company
GET /badges {
    "company_id": <company_id>
}

Expected result is the list of Badge compatible objects in JSON format

/channels - endpoint for working with channel entities

HTTP METHODS:

  1. GET - get all the channels in a given workspace/company
GET /channels {
    "company_id": <company_id>,
    "workspace_id": <workspace_id>,
}

Expected result is the list of Channel compatible objects in JSON

  1. POST - create new channel with a given parameters
POST /channels {
    "company_id": <company_id>,
    "workspace_id": <workspace_id>,
    "name": <name>,
    "icon": <icon emoji>,
    "description": <description>,
    "members": <Array<string> ids>,
    "visibility": <public | private>
}

Expected result is Channel compatible object of newly created channel

  1. PUT - edit given channel, updating provided parameters
PUT /channels {
    "id": <channel_id>,
    "company_id": <company_id>,
    "workspace_id": <workspace_id>,
    "name": <name>,
    "icon": <icon emoji>,
    "description": <description>,
    "members": <Array<string> ids>,
    "visibility": <public | private>
}

Expected result is Channel compatible object edited channel

  1. DELETE - delete given channel
DELETE /channels {
    "id": <channel_id>,
    "company_id": <company_id>,
    "workspace_id": <workspace_id>
}

Expected result is boolean, regarding the status of operation

/direct - almost exactly the same as the endpoint for channels, but this one works with direct chats

HTTP METHODS:

  1. GET - get all the directs in a given company
GET /direct {
    "company_id": <company_id>,
}

Expected result is the list of Channel compatible objects in JSON

  1. POST - create new direct with a given user
POST /direct {
    "company_id": <company_id>,
    "member": <id of user>,
}

Expected result is the list of Channel compatible objects in JSON

/channels/read - helper endpoint to mark the channels as read

HTTP METHODS:

  1. POST - mark the channel as read, resetting unread messages counter and unread flag
POST /channels/read {
    "id": <channel_id>,
    "company_id": <company_id>,
    "workspace_id": <workspace_id>
}

Expected result is boolean, regarding the status of operation

/channels/members - endpoint for working with members in a channel

HTTP METHODS:

  1. POST - add new members to channel
POST /channels/members {
   "company_id": <company_id>,
   "workspace_id": <workspace_id>,
   "id": <channel_id>,
   "members": <Array<string> userids>
}

Expected result is boolean, regarding the status of operation

  1. DELETE - remove given members from channel
DELETE /channels/members {
   "company_id": <company_id>,
   "workspace_id": <workspace_id>,
   "id": <channel_id>,
   "members": <Array<string> userids>
}

Expected result is boolean, regarding the status of operation

/messages - endpoint for working with messages in channels and threads

HTTP METHODS:

  1. GET - get messages in a given channel or thread. Depending on parameters it should behave differently:
    • if thread_id IS NOT passed, it should only return top level channel messages
    • if thread_id IS passed, then it should return only the messages for the given thread
    • if id is passed, then it should only return one single message with given id
    • if after_date is passed, then it should return only messages (either top level or thread level) after the given date (if any)
GET /messages {
    "company_id": <company_id>,
    "workspace_id": <workspace_id | direct>,
    "channel_id": <channel_id>,
    "thread_id": <thread_id>,
    "id": <message_id>,
    "after_date": <timestamp>,
    "limit": <integer>
}

Expected result is the list of Message compatible objects in JSON

  1. POST - create new message in a given channel
POST /messages {
      "company_id": <company_id>,
      "workspace_id": <workspace_id>,
      "channel_id": <channel_id>,
      "thread_id": <thread_id>,
      "original_str": <message input>,
      "prepared": <JSON structure of parsed message>
}

Expected result is Message compatible JSON object

  1. PUT - update the contents of given message
PUT /messages {
      "company_id": <company_id>,
      "workspace_id": <workspace_id>,
      "channel_id": <channel_id>,
      "thread_id": <thread_id>,
      "message_id": <message_id>,
      "original_str": <message input>,
      "prepared": <JSON structure of parsed message>
}

Expected result is Message compatible JSON object

  1. DELETE - delete the given message
DELETE /messages {
      "company_id": <company_id>,
      "workspace_id": <workspace_id>,
      "channel_id": <channel_id>,
      "thread_id": <thread_id>,
      "message_id": <message_id>
}

Expected result is boolean, regarding the status of operation

/reactions - endpoint for working with message reactions

HTTP METHODS:

  1. POST - update the reactions list for a given message
POST /reactions {
      "company_id": <company_id>,
      "workspace_id": <workspace_id>,
      "channel_id": <channel_id>,
      "thread_id": <thread_id>,
      "message_id": <message_id>,
      "reaction": <emoji in unicode>,
}

Expected result is boolean, regarding the status of operation

/workspaces/notifications - endpoint for getting all the socket io rooms, that can be joined in a given workspace

HTTP METHODS:

  1. GET - get all the sockeio rooms in a given workspace
GET /workspace/notifications {
    "company_id": <company_id>,
    "workspace_id": <workspace_id>
}

Expected result is the list of SocketIORoom compatible objects in JSON

Pages

Widgets

Miscellaneous

Clone this wiki locally