# Telegram X: Comprehensive guide for fellow contributors This guide is intended for future contributors and potential maintainers. It includes both common and technical information about the project. It's **important** to read this document before starting working with the project to understand how everything works in the app. ## 1: Fresh start Refer to [README](/README.md) for the project setup and build instructions. ## 2: Translations Whenever you'd like to translate **Telegram X** to a new language, or improve some existing string, you should refer to the cloud [Translations Platform](https://translations.telegram.org/en/android_x/). All changes there affect **Telegram X** without need in updates, and, in most cases, even without need in the app restart. **Telegram X** has only English language embedded (`strings.xml`). `strings.xml` files in locale-specific folders contain only strings that are somewhat important to be available before the very first connection with Telegram servers could be established after the clean app installation. **Telegram X** also has translation tools available inside the app: you can create, install, or edit existing localizations from `.xml` files. These tools might be useful when creating a new translation and checking how it looks before the language would be available on the [Translations Platform](https://translations.telegram.org/en/android_x/). ### 2.1: Adding ordinary strings 1. Add new string to `app/src/main/res/values/strings.xml` file; 2. **Do not** edit `strings.xml` in locale-specific folders (`values-ja`, `values-it`, `values-ru`, etc); 3. Import new `strings.xml` file to the [Translations Platform](https://translations.telegram.org/en/android_x/) and add new keys; 4. In case translated string should be accessible immediately after app installation (i.e. before the first connection with Telegram server could be established), it should be added to all locale-specific `strings.xml` files as well (which should be autogenerated & based on the latest string version on the platform). Such strings are usually used only on the intro screen & proxy settings (which might be useful for regions with Internet censorship), thus you most likely will never need them there; 5. **Do not** concatenate two strings manually. Add another string to `strings.xml` (for example, using `format_` key prefix), which would contain the format for concatenation that will allow translators to customize it in any way. Then use it like: `Lang.getString(R.string.format_example, Lang.getString(R.string.stringA), Lang.getString(R.string.stringB))`. ### 2.2: Plurals 1. Add two strings to `strings.xml` with preserved key suffixes `_one` and `_other`, i.e. with keys `xExamples_one` (for singular form) and `xExamples_other` (for plural form); 2. `%1$s` will contain the formatted counter. Format arguments are accessible as well: `%2$s`, `%3$s`, …; 3. Use `Lang.plural(R.string.xExamples, number, …)` to get the final form based on the number. ### 2.3: Relative dates Relative date strings are a special form of strings used to display a difference between a specific time in the past and current time (server or local). When adding new relative date string, you shall define strings with **8** or **10** suffixes, depending on which form you use, otherwise the build will intentionally fail. Dates in future are not supported: string with `_now` suffix will be used instead. ##### Suffixes Following 4 string suffixes are used when `allowDuration` argument is `true`: 1. `_now`, _ordinary_: less than 5 or 30 seconds ago (exact amount is passed through the `justNowSeconds` argument). e.g. `seen just now` 2. `_seconds`, _plural_: less than a minute ago. Example: `seen 30 seconds ago` 3. `_minutes`, _plural_: less than an hour. Example: `seen 5 minutes ago` 4. `_hours`, _plural_: less than 4 hours. Example: `seen 3 hours ago` These suffixes are always used: 5. `_today`, _ordinary_: today with _time_ in `%1$s`. Example: `seen today at 12:00 PM` 6. `_yesterday`, _ordinary_: yesterday, with _time_ in `%1$s`. Example: `seen yesterday at 5:12 AM` ##### Precise form: with the specific date and time 7. `_weekday`, _ordinary_: less than 7 days ago. Example: `seen on Tue at 6:30 PM` 8. `_date`, _ordinary_: more than a week ago. Example: `seen on 07.07 at 11:11 AM` ##### Approximate form: with an amount of days, weeks, months, or years, if difference is more than 2 calendar days 7. `_days`, _plural_: before yesterday, but less than 14 days ago. Example: `joined 2 days ago` 8. `_weeks`, _plural_: more than 14 days ago. Example: `joined 2 weeks ago` 9. `_months`, _plural_: more than 30 days ago. Example: `joined 2 months ago` 10. `_years`, _plural_: more than a year ago. Example: `joined 1 year ago` ##### TLDR: How to use them? 1. Define **8** or **10** strings in `strings.xml` with the suffixes described above 2. Build project to refresh auto-generated strings resources 3. Call `Lang.getRelativeDate` with the string **without** suffix. `approximate` argument must correspond to the chosen form, otherwise **Telegram X** will crash when undefined form is used. #### Calculating time remaining until the date refresh Whenever you display a relative date, it's important to keep it updated, especially when the difference between two dates is small. To calculate amount of milliseconds until the next update use one of `Lang.getNextRelativeDateUpdateMs` methods depending on which form you used. Then schedule the refresher action, for example, via `Handler`'s `sendMessage` method, but do not forget to cancel it whenever you are sure it won't longer needed (e.g. corresponding view got destroyed, screen closed, etc). ### 2.4: Date formats Date format methods are available in `Lang` class. Never use custom patterns when displaying dates, as different regions might have completely different preferred formats. Users must see them in a way they are used to. If app language differs from the system language, it should rely on the preferred format for the chosen locale. ## 3: Themes Whenever you need some color, like with translations strings, you should never use hardcoded colors. You also shall register all theme update listeners for a smooth transition on any screen when theme switches automatically. There're several built-in themes. Custom themes can be installed via exported `.tgx-theme` files ##### Accessing current color value `Theme.getColor(R.id.theme_color_$colorId`, where `$colorId` – desired theme color key. This will return currently effective color: either from the effective theme, or an intermediate value when switching themes. `Theme.getColor` structure is designed in a heavily-optimized way so it could be called from methods such as `onDraw` directly, without need in caching the value. ##### Adding new colors Defined theme colors and properties are located in [colors-and-properties.xml](/app/src/main/other/themes/colors-and-properties.xml) file and built-in themes can be found in the [same folder](/app/src/main/other/themes). ##### Subscribing to the updates -- TODO ## 4: Animating things In **Telegram X** you shall forget about the usual way of animating `View` or properties in Android and use one of the classes located in `me.vkryl.android.animator` package. Main idea is to use as few animators as possible to avoid desync in choreography, improve animations performance and reduce battery usage. ### 4.1: `FactorAnimator` Base class that animates `float` value, which can be used to animate pretty much anything. ``` class Example implements FactorAnimator.Target, View.OnClickListener { private static final int ANIMATOR_ROTATION = 0; private static final int ANIMATOR_DISAPPEARANCE = 1; private final FactorAnimator disappearAnimator = new FactorAnimator(ANIMATOR_DISAPPEARANCE, this, AnimatorUtils.OVERSHOOT_INTERPOLATOR, 200l); // ... @Override public void onClick (View v) { switch (v.getId()) { case R.id.btn_showAnimated: disappearAnimator.animateTo(1f); break; case R.id.btn_show: disappearAnimator.forceFactor(1f); break; case R.id.btn_hideAnimated: disappearAnimator.animateTo(0f); break; case R.id.btn_hide: disappearAnimator.forceFactor(0f); break; case R.id.btn_rotate: rotateAnimator.animateTo(MathUtils.random(0, 360)); break; } } @Override public void onFactorChanged (int id, float factor, float fraction, FactorAnimator callee) { switch (id) { case ANIMATOR_DISAPPEARANCE: { final float scale = .7f + .3f * factor; someView.setScaleX(scale); someView.setScaleY(scale); someView.setAlpha(MathUtils.clamp(factor)); break; } case ANIMATOR_ROTATION: { someView.setRotation(factor); break; } } } } ``` ### 4.2: `BoolAnimator` Simplified version of `FactorAnimator`. Can be used as an animated `boolean` replacement that gives `boolean` itself and its current animated representation as `float` value between `0.0` (`false`) and `1.0` (`true`). Because it was created much later than `FactorAnimator` in most places, where `FactorAnimator` is currently used, it should be replaced with `BoolAnimator`. In general, prefer using `BoolAnimator` over `FactorAnimator`, if you need to animate between just two states. ``` private final BoolAnimator isVisible = new BoolAnimator(0, (id, factor, fraction, callee) -> { someView.setAlpha(factor); someOtherView.setAlpha(1f - factor); }, AnimatorUtils.DECELERATE_INTERPOLATOR, 180L ); //... @Override public void onClick (View v) { switch (v.getId()) { case R.id.btn_toggleVisibility: isVisible.toggleValue(true); break; case R.id.btn_show: isVisible.setValue(true, true); break; case R.id.btn_hide: isVisible.setValue(false, true); break; } } ``` Whenever you'd like to set value without animation (for example, when `View` gets re-used, `onBindViewHolder` gets called and you need to update view state without animations), just pass `false` as the last argument in `setValue` or `toggleValue` methods. ### 4.3: `ListAnimator` Animator that is used to animate small lists of objects of `T` type. Can be seen in action in the list of voters' avatars in public polls. Note that in order `ListAnimator` to work properly, `T` type has to implement `equals` and `hashCode` methods. ``` class SomeObject { public final long id; public final int color; public SomeObject (long id, int color) { this.id = id; this.color = color; } @Override public boolean equals (Object obj) { return obj instanceof SomeObject && ((SomeObject) obj).id == this.id; } @Override public int hashCode () { return (int) (id ^ (id >>> 32)); } } ``` Then, whenever needed, simply pass list of items of `T` type to `ListAnimator`, and all needed animations will be handled properly: ``` private static class SomeView extends View implements ListAnimator.Callback { public SomeView (Context context) { super(context); } private final ListAnimator listAnimator = new ListAnimator<>((animator) -> { invalidate(); }); public void setObjects (@Nullable List items, boolean animated) { listAnimator.reset(items, animated); } @Override public void onDraw (@NonNull Canvas c) { final int radius = Screen.dp(12f); final int spacing = Screen.dp(4f); for (int i = listAnimator.size() - 1; i >= 0; i--) { ListAnimator.Entry item = listAnimator.getEntry(index); final float alpha = item.getVisibility(); final float x = radius + (radius * 2 + spacing) * item.getPosition(); final int color = ColorUtils.alphaColor(alpha, item.item.color); c.drawCircle(x, radius + spacing, radius, Paints.fillingPaint(color)); } } } ``` ##### `ListAnimator.Entry` methods: 1. `getPosition`: returns animated item index, i.e. when item moves it will be between `fromIndex` and `toIndex`. 2. `getVisibility`: returns animated visibility (alpha) from `0` to `1`. 3. `getIndex`: returns an actual item index in a list. Note that it can be outside of range of the last passed list in case of removed items. **Note**: `ListAnimator` implements `Iterable` interface. However, you shall not use it inside drawing methods (`onDraw`, `draw`, etc) to avoid object allocation. ### 4.4: `ReplaceAnimator` Pretty much the same as `ListAnimator`, but meant to be used for a single item. Useful for texts or buttons that should get replaced with animation. ## 5: Navigation, screens and their lifecycle Unlike most Android projects, **Telegram X** doesn't use regular `Activity` or `Fragment` navigation. Instead, it's based on its own abstract `BaseActivity` class, which uses `NavigationController`, which manages navigation between `ViewController`'s subclasses. ### 5.1: `BaseActivity` `BaseActivity` is a common class that is responsible for: 1. Creating and destroying `NavigationController`; 2. Managing keyboard open/close, window insets, etc; 3. Managing window flags; 4. Displaying and hiding passcode; 5. Displaying and hiding pop-ups; 6. Managing gestures (or, in fact, passing them to the target components). There are currently only two `BaseActivity`'s subclasses: 1. `ManageSpaceActivity`: previously used inside the `android:manageSpaceActivity` application property, but for now unused; 2. `MainActivity`: main app entry point that handles pretty much everything. It's also responsible for creating and restoring navigation stack, syncing TDLib instances, switching accounts, handling deep links, etc. ### 5.2: `NavigationController` and `ViewController` `NavigationController` is a main navigation component that responsible for navigation between `ViewController` instances. `NavigationController` and `ViewController` are designed the way animations would perform without any interruptions, frame drops, and it would be relatively easy to change gestures, and, for example, implement table/desktop layout in future. Each `ViewController` instance contains a reference to the root `BaseActivity` and an optional `Tdlib` instance. `Tdlib` instances are managed automatically, so it's possible to have screens attached to different `Tdlib` instances (accounts) at the same time. ##### Adding new screens and navigating between them Whenever you want to create a new screen, you'll need to: 1. Add a unique controller identifier to `ids.xml` with the `controller_` prefix; 2. Create a new class inside the `org.thunderdog.challegram.ui` package, inherit `ViewController` or any of its children, and override needed methods that will describe the `ViewController`; 3. From the entry point, such as `onClick`, `onTouchEvent` or wherever else, you have to create its instance, set arguments (optionally), and call `NavigationController`'s `navigateTo` method. ##### `ViewController` lifecycle When you just create a `ViewController` instance, no methods are called. After you pass it to `NavigationController`'s `navigateTo`, `setControllerAnimated` or other similar method, many things happen. 1. `usePopupMode`: if `true`, vertical navigation and gestures will be used for this screen; 2. `onPrepareToShow`: gets called every time before `ViewController` contents become visible (including backward navigation); 3. `onCreateView`: gets called just once when `ViewController`'s `get` method gets called for the first time; 4. `onFocus`: gets called whenever `ViewController` got fully visible: all corresponding navigation transitions have finished, activity resumed, passcode unlocked; 5. `onBlur`: gets called whenever `ViewController` lost "focus": navigation transition or gesture started, activity paused, passcode shown, etc; 6. `onActivityPause`, `onActivityResume`: gets called when corresponding `Activity` method got called. You most likely will never need it, as there are `onFocus` and `onBlur` methods; 7. `needAsynchronousAnimation`: when `true`, navigation transition won't start immediately after `NavigationController`'s `navigateTo` or `navigateBack` method got called. Instead, it'll wait util target `ViewController`'s `executeScheduledAnimation` method gets called, or `getAsynchronousAnimationTimeout` expires, whichever comes first. **If `getAsynchronousAnimationTimeout` returns negative or zero value, animation transition will wait forever until `executeScheduleAnimation` gets called**. Note that this scheme applies to both forward and backward animation, so, after there's no longer any need in asynchronous animation, `needAsynchronousAnimation` shall start returning `false`; 8. `destroy`: gets called whenever `ViewController` gets removed from navigation stack and won't be displayed any longer. Best place to clean all resources, views, etc. ##### Common `ViewController` types 1. `ViewController`: basic screen; 2. `TelegramViewController`: based on `ViewController`. Basic screen with built-in chat & messages search, which could be found, for example, when tapping search button on the main screen; 3. `RecyclerViewController`: based on `TelegramViewController`. Basic screen with `RecyclerView`; 4. `EditBaseController`: based on `ViewController`. Screen with done button in the bottom-right corner, useful when you want to edit something (e.g. change username, rename contact, etc); 5. `ViewPagerController`: based on `TelegramViewController`. Basic screen with `ViewPager`. Uses other `ViewController` instances to display its tabs; 6. `WebkitController`: based on `ViewController`. Basic screen with `WebView`; 7. `MapController`: based on `ViewController`. Screen used to display map with some information, such as venue, live location, etc. If you would like to add new maps implementation (e.g. OSM, Yandex, etc), you should inherit froom this class. See `MapGoogleController` for implementation based on Google Maps; 8. `SharedBaseController`: based on `ViewController`. Inherit from this class, if you want to add a completely new profile tab that doesn't look like the others. You most likely will never need it, as there is `SharedCommonController` for lists and `SharedMediaController` for grids. ##### Saving and restoring navigation stack By default, when activity gets destroyed, navigation stack is not saved. In order to keep your `ViewController` instance saved, you have to: 1. Implement `saveInstanceState` method and return `true`. **Important**: don't forget to use `keyPrefix` when storing data inside the bundle; 2. Implement `restoreInstanceState` method and return `true`. **Important**: don't forget to use `keyPrefix` when accessing data previously stored inside `saveInstanceState` method; 3. Add instance creation in the `restoreController` method inside `MainActivity`. Note that this step might be reworked later in a nicer way. To test saving and restoring navigation stack you might want to use **Don't keep activities** toggle in **Developer Options** in the system settings. ### 5.3: `BaseView`, or pseudo 3D-touch There's a special navigation type in **Telegram X**: pseudo 3D-touch. It could be found when holding chats in the chats list (when `Chat Previews` are enabled in Settings – Interface), holding photos and videos in shared media tab in profiles, and many other places. Whenever you want to implement 3D-touch, target view shall inherit from `BaseView` class. There are two modes in which `BaseView` could be used: ##### Displaying any `ViewController` `BaseView` allows displaying any desired `ViewController`: ``` BaseView view = new BaseView(context, null); view.setOnClickListener(view -> { // do anything }); view.setCustomControllerProvider(new CustomControllerProvider() { @Override public boolean needsForceTouch (BaseView v, float x, float y) { return true; // can be based on some setting } @Override public boolean onSlideOff (BaseView v, float x, float y, @Nullable ViewController openPreview) { return false; // gets called when user's finger gets moved outside of view range. // return true, if preview should be closed at this point } @Override public ViewController createForceTouchPreview (BaseView v, float x, float y) { SomeController c = new SomeController(v.getContext(), tdlib); c.setArguments(...); return c; } }); ``` On `ViewController` side just check whether `isInForceTouchMode` returns `true`, and adjust the layout at the creation step, if needed. ##### Showing chat preview, a.k.a. `MessagesController` `BaseView` also has built-in utility methods that allow opening chat previews in even more easy way: ``` // tdlib reference here controls which account will be used for the chat preview BaseView view = new BaseView(context, tdlib); view.setOnClickListener((v) -> { /* do anything */ }); view.setPreviewChatId(chatList, chatId); ``` ##### Customizing action buttons list -- TODO ``` view.setPreviewActionListProvider(new BaseView.ActionListProvider() { }); ``` ## 6: Other stuff ### 6.1: Utilities for custom views **Telegram X** heavily uses custom views with custom drawing methods to reduce layout structure complexity, easily implement animations of any kind, improve frame rate, etc. For that reason, there're many utilties built for custom drawings. -- TODO ### 6.2: Adding native methods -- TODO ### 6.3: Not so good-looking parts -- TODO ## 7: Tips and tricks ### 7.1: Use asynchronous animation when loading data from disk Whenever you navigate to the screen with items that load from local storage, or you expect that data will get loaded relatively fast (let's say, less than 250ms), instead of starting animation immediately, wait until it gets loaded. `ViewController` allows doing so in a convenient way by overriding `needAsynchronousAnimation` and calling `executeScheduledAnimation` methods (see previous section for more details). In case of other animations (such as search bar opening, etc), you have to handle things manually and call `animateTo`, `setValue` or other animation-related method once content get prepared. Sample scheme for chats list screen: 1. Create screen; 2. Calculate how many items need to be loaded to fit entire screen (with one or few extra items to enable scrolling); 3. Load items from database; If there are items available locally: 3. Update the list; 4. Call `executeScheduledAnimation`; 5. Result: user doesn't see rudimentary progress bar and other related transitions, but instead sees chats list immediately once navigation animation starts. If there are no items available locally: 3. Immediately start loading data from server. Instead of constant amount of items, load as many items as calculated at step #2 during the first request. This will help displaying the list faster, if calculated value is smaller than initially desired constant, and will avoid half-blank list, if screen size is too big (and constant value is too small, accordingly); 4. Call `executeScheduledAnimation` 5. Once data gets loaded, display content. Avoid updating layout more than once 6. Result: user sees progress bar, which is OK, because it's not possible to predict how much time the request will take for sure. Once items get loaded from server, list will display. ### 7.2: Avoid layouts during animations Unless working with heavily optimized custom `ViewGroup`, you shall avoid layouts during animations to prevent unnecessary frame drops. Instead, use `setTranslationX`, `setTranslationY`, `setAlpha`, `setScaleX`, `setScaleY`, or `invalidate` in case of custom views. ### 7.3: Avoid rudimentary object allocations 1. Never allocate any objects inside drawing methods, such as `onDraw`, `draw`, etc. Make sure methods you call there don't allocate them too; 2. Whenever possible, allocate as few objects as possible and avoid heavy work while scrolling lists (e.g. when `onBindViewHolder` gets called). There are still a lot of lowend devices that benifit from such optimizations. ### 7.4: Use single animator per one transition 1. Reuse animator instances (`FactorAnimator`, `BoolAnimator`, `ListAnimator`, etc) each time you want to animate the same item, since they handle animation cancellations and re-starts properly; 2. Think of animator and its callback as a choreographer: instead of creating multiple animator instances for each view property, use just one, and update all properties relevant to the transition at once when callback gets called. ## 8: Code style There are few rules that Telegram X tries to follow: 1. Never copy-paste public repositories, use git submodules 2. Same with libraries: never copy-paste, always add as gradle dependency 3. Only vector drawables. Viewport for icons (`viewportWidth`, `viewportHeight`) must be 24x24. Desired display size can be controlled by `width` and `height` properties and must be included in the file name suffix. 4. Double whitespace as tab symbol 5. Whitespace before the method's parameters opening brace: `public static void main () { ... }` 6. Kotlin is OK for new modules outside of `org.thunderdog` (see `me.vkryl` package), as long as it is interoperable with Java code (or it's private) ## 9: Unfinished features This section contains a list of features that have being started, but not finished, and will have notes on how and what's done and what's not. ### 9.1: Video Streaming Main part is actually done, as there's `TdlibDataSource` that can be passed to `ExoPlayer` instance, the only thing left is to change the UI logic the way `ExoPlayer` would be initialized without waiting for the file to finish downloading, like it does with music. ### 9.2: Instant View 2.0 Major part of Instant View 2.0 blocks is supported. Remaining tasks: - Tables: see `PageBlockTable` - Autosize embeds: bugfixes to avoid scroll jumping, cache height when view gets recycled, etc - Anchor support inside details block - Follow/unfollow button in channel link - View counter on the bottom - Refresh chat member count in chat link automatically - Problem with the pre block background - `Wrong layout?` button ## 10: Things to improve This is just the list of thoughts on some existing things that could be done better or just what could be done in future, which are not meat to be or not to be done. There usually just no time to work on them, as users want features, more frequent updates, and less bugs, they completely do not care about project structure and other internal stuff. Order in this list does not mean anything. ### 10.1: Full-screen `ViewController` To allow transparent headers and other redesigns. To ease up the transition to full-screen `ViewController` layout, it could be used only when some `allowRunningInFullScreen` method returns `true` (to be removed later when all screens will migrate to a new layout). This could be done relatively easy by changing the way `NavigationLayout` applies top offset to its children. ### 10.2: Dynamic `HeaderView` height and appearance Initially, `HeaderView` and its animations were designed to handle two header height: normal (like almost on all screens) and expanded (like on profile page). However, later it became pretty obvious that header should be able to handle properly any height desired by `ViewController` and be more dynamic, allowing making any changes to it without any difficulties. ### 10.3: Split project into independent libraries It's obvious that project of this size should be split into smaller projects (modules) to allow multiple developers work on separate things independently. However, as the of the moment of writing this text, there's only one project developer and currently all major modules, that in perfect world would be completely independent, intersect with each other and are not easy to separate at this time. I've started separating most common utility classes and methods to `me.vkryl` package that do not refer to any other packages (except for `org.drinkless` in `me.vkryl.td` package), but still the most part of the app is designed as it is. As I see, in perfect world **Telegram X** would be separated in a way similar to **AndroidX**: as a tree of packages with a structure similar to the roots of `me.vkryl` package. Here're some of the modules / packages that could be done in future: 1. `theme`: handles everything related to colors and themes; 2. `language`: handles everything related to languages; 3. `text`: similar to `org.thunderdog.challegram.util.text`, but without explicit references to `TdApi`, `Tdlib`, other `org.thunderdog.challegram.*` packages and other things irrelevant to `Text` that could be made abstract; 4. `leveldb`: **Telegram X**'s java layer for working with **LevelDB** (see `org.thunderdog.challegram.tool.LevelDB`); 5. `telegram`: similar to `org.thunderdog.challegram.telegram`, handling everything related to `TDLib`: instances, caching objects, etc; 6. `navigation`: all navigation-related and `ViewController` logic, that could be very useful for any other non-Telegram-related projects. ### 10.4: Move more things into contexts In the beginning, many things that currently look like contexts previously were singletons. However, there are still some things that could be changed to context. Some of them: 1. Themes. Like passing different `Tdlib` instances to `ViewController` allows creating screens with different accounts, it would be nice to be able to pass theme information as well, which would allow creating screens with different themes at the same time; 2. Audio player (`TGPlayerController`). While it may look like a context inside, it's still designed as a singleton, as it's the only place where audio playback listeners get registered and the only place it gets created is `TdlibManager`. Making it more flexible would allow creating two players at the same time, i.e. to allow playing voice messages without destroying music player: putting audio on pause while voice messages play, and resuming back once they finish playing. ### 10.5: Give ExoPlayer audio notification management Currently **Telegram X** manages audio service and notification. However, it might be a good idea to get rid of what's possible on **Telegram X** side, and give full control to **ExoPlayer**. See [Playback Notifications with ExoPlayer](https://medium.com/google-exoplayer/playback-notifications-with-exoplayer-a2f1a18cf93b). ### 10.6: Proper native object management There're some java objects in **Telegram X** that are designed in a pretty common way (example without thread safety): ``` final class Example { private int mNativeHandle; public Example () { mNativeHandle = newNativeObject(); } public void release () { if (mNativeHandle != 0) { nativeRelase(mNativeHandle); mNativeHandle = 0; } } @Override protected void finalize () throws Throwable { try { release(); } finally { super.finalize(); } } private native static long newNativeObject (); private native static void nativeRelease (long ptr); } ``` However, according to [this Google I/O talk](https://www.youtube.com/watch?v=7_caITSjk1k), such objects have to be reworked. ### 10.7: Talkback support **Telegram X** currently doesn't support accessibility features, because it heavily uses gestures and custom views that did not take them in mind from the beginning. However, at some point in future, it's obvious that they have to be implemented. ### 10.8: System integration Currently **Telegram X** almost does not use any system integration features. A lot of related features could be added, but it's important to note that any external feature must not expose user identifiers and phone numbers to the system and, especially, to other apps. When working with user identifiers, it's possible to encrypt them with some local encryption key accessible only to **Telegram X**, or store `local_user_id <-> user_id` pair in key-value storage that is private to the app too. ### 10.9: RTL support for `Text` Initially, `Text` was designed to handle `RTL` texts properly, but according to reports, it has been partially broken after one of the updates with nested entities support. ### 10.10: Built-in video player for video files Currently when you tap on a downloaded video file, it opens in system app, which might not support the codec, or be missing at all. Instead, it would be better to use existing `MediaViewController` and open video document with animation. ### 10.11: Subsampling in photo viewer It's better to load maximum photo size for the given viewport size, and display it with subsampling (refresh tile while zooming and moving), e.g. via implementation similar to [subsampling scale image view](https://github.com/davemorrissey/subsampling-scale-image-view). ### 10.12: Icon pack setting Like with Emoji Set setting, it's possible to add an ability to change icon set, see `SettingsCloudIconController`. There could be two formats: 1. Single big xml or json file with svg data for each overriden icon 2. Archive with svg files The first one is more preferable, as the second one is more dangerous, since there could be a danger of zip-bombs, if not implemented properly (given custom icon pack files would be allowed), etc. Some `custom-icon-set.tgx-icons` file could look like: ``` ... ... #FF000000 ``` or, in JSON: ``` { "icons": [ { "name": "baseline_group", "data": [ {"pathData": "..."}, {"pathData": "..."}, {"fillColor": "#FF000000"} // Optional ] } ] } ``` Or, in completely custom format: ``` @baseline_group ..some path data.. ..some path data.. #FF000000 // Optional @@ @another_icon ... @@ ``` Since `viewportWidth` and `viewportHeight` have to be `24x24`, overriden svg instruction set could be applied to all `baseline_group` display resolutions: `baseline_group_16.xml`, `baseline_group_24.xml`, `baseline_group_56.xml`, `baseline_group_96.xml`, etc. Based on these icon requirements, it would be also pretty easy to implement in-app interface to view and edit all icons and see them immediately in action. There are currently few icons that do not follow `24x24` format properly, and have to be reworked.