Skip to content

Latest commit

 

History

History
644 lines (449 loc) · 34.4 KB

GUIDE.md

File metadata and controls

644 lines (449 loc) · 34.4 KB

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 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. 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.

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 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:

  1. _today, ordinary: today with time in %1$s. Example: seen today at 12:00 PM
  2. _yesterday, ordinary: yesterday, with time in %1$s. Example: seen yesterday at 5:12 AM
Precise form: with the specific date and time
  1. _weekday, ordinary: less than 7 days ago. Example: seen on Tue at 6:30 PM
  2. _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
  1. _days, plural: before yesterday, but less than 14 days ago. Example: joined 2 days ago
  2. _weeks, plural: more than 14 days ago. Example: joined 2 weeks ago
  3. _months, plural: more than 30 days ago. Example: joined 2 months ago
  4. _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 file and built-in themes can be found in the same folder.

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<T>

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<T>, 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<SomeObject> listAnimator = new ListAnimator<>((animator) -> {
        invalidate();
    });
    
    public void setObjects (@Nullable List<SomeObject> 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<SomeObject> 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<T>

Pretty much the same as ListAnimator<T>, 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<T>'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<T> 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<T>: basic screen;
  2. TelegramViewController<T>: based on ViewController<T>. Basic screen with built-in chat & messages search, which could be found, for example, when tapping search button on the main screen;
  3. RecyclerViewController<T>: based on TelegramViewController<T>. Basic screen with RecyclerView;
  4. EditBaseController<T>: based on ViewController<T>. 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<T>: based on TelegramViewController<T>. Basic screen with ViewPager. Uses other ViewController instances to display its tabs;
  6. WebkitController<T>: based on ViewController<T>. Basic screen with WebView;
  7. MapController<V extends View, T>: based on ViewController<MapController.Args>. 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<T extends MessageSourceProvider>: based on ViewController<SharedBaseController.Args>. 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.

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, 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.

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:

<xml>
    <icons>
        <icon name="baseline_group">
            <pathData>...</pathData>
            <pathData>...</pathData>
            <fillColor>#FF000000</fillColor> <!-- optional -->
        </icon>
        <!-- ... -->
    </icons>
</xml>

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.