Skip to content

Architecture

famoe03 edited this page Jul 12, 2024 · 36 revisions

Architecture Overview

We aimed a MVVM architecture, requiring that there are no references from models to the view layer. Take a look at our detailed architecture model: architecture

Relation between Core and UI

We focused on the separation of UI and core, making the core reusable for a different interface. Notice that there are only very few links between UI-Classes and classes from the Core-package. Those relations are implemented by using an Observer-Pattern via when:send:to:. The core triggers messages whenever events happen, that a user might find interesting. A UI subscribes to the messages it wants to handle and implements the displaying logic.

OS-dependent Classes

The only classes that depend on a specific operating system are the three Subclasses of TCCFFIClient: TCCLinuxClient, TCCWindowsClient and TCCMacClient. The FFIClient can be used to generate a fitting subclass. TCCTdlibClient is used as an abstraction of the FFI-methods and to keep an active handle to the current FFIClient. TCCTdlibClient>>send is called by the core or any handlers to send calls to TDLib.

Handlers

For separation of concerns we split different parts of the logic into different Handlers, which are maintained by TCCCore. AuthenticationHandler is used to send and handle events while authenticating. We use the ChatsHandler for message sending and chats loading.

Data Classes

Another important part of our architecture are the data classes which are used as an abstraction to JSON objects. It stores received events in a TCCEvent and for sending calls an instance of TCCRequest is created. To store the most important objects of a messenger, we use TCCChat and TCCMessage data classes, which store all relevant attributes we currently support. These make dealing with chats and messages a lot easier especially the possibility to construct them directly from TDLib events. Chats are maintained in a SortedCollection-Subclass TCCChats, which keeps all chats in the order they are displayed. This collection triggers events whenever it changes and is thereby good object to subscribe in the UI.

UI-Architecture

Whenever you create an new instance of TCUTelegram a TCUAuthentication is created to log you in. After you have logged in, or if your account has been logged in before, the window closes and an instance of TCUMain will be created, which is the main Telegram window. It comes with a TCUHeaderBar as well as a TCUChatsList and a TCUChatWindow. The chats list subscribes to events from TCCChats and redraws whenever the order of the chats changes. The chats window consists of a TCUChatMessageList which displays single messages in a chat (messages are stored in a collection in the chat data class in order). It always shows a fixed window of the messages in a chat, which shifts on scrolling.

Notification are created directly in TCUMain when new messages arrive.

Jumping to referenced Messages

Whenever you jump to a referenced message, then the message was either loaded individually or already inserted in the list of messages. In the last case, we can jump to the message. Otherwise, the application generates an instance of MessageRequest, which runs a block when the referenced message was loaded - in this case jumping to the loaded message. Additionally, the application loads old messages in reversed order of appearance and checks whether the new bunch of messages contains the referenced one.

Architecture Architecture

Dynamic loading of data

Every data object that has to dynamically request data has to know the running TCCCore as every event gets sent and received through it. The data object can benefit from other core objects that might already implement how to request the desired data.

Here are some considerations we had when deciding for this solution.

Option Pro Con
Data objects operate on the core directly
  • core does not know much and just sends out the requests
  • corresponds to OOP
  • every data object knows the core
Core requests desired data for every data object on its own
  • data objects don't have to know core
  • core has to know very much
Core passes event to the corresponding handler
  • core does not know much
  • data objects don't know much
  • UI has to implement logic for loading data

Emojis 😀

The TelegramClient uses its own static class-side parsing code - the TCUEmojiHelper - to decode emoji references in messages. Since Squeak uses UTF-16 as its character encoding, we first have to reencode the whole text to Unicode to extract meaningful codepoints. Luckily there exists Squeak-native-code for this. Furthermore emoji-references do not encode the given character-sequence's length. This is a problem as emoji-symbols are composed of multiple characters, e.g. the pride flag emoji (🏳️‍🌈) consists of the symbol for a flag (🏳️), a so called ZERO WIDTH JOINER and the rainbow-emoji (🌈). This means that we need to check emoji-character-sequences (also known as runs in the code) for all possible lengths (currently <= 10). Another problem with emojis is the lack of standardization in encoding them. This leads to different encodings on different devices, operating systems and applications. One very noticeable deviation for us was the amount of VARIATION SELECTOR-16 characters, which lead us to completely strip them from emoji-character-sequences. Finally to display emoji-characters we use the open-source icons from OpenMoji. Concluding, we are aware that this method of parsing emojis is currently very naïve and can be improved drastically in performance and code style.