mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Merge tag 'v1.0.3' into sc
1.0.3 Conflicts: vector/build.gradle vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt vector/src/main/res/layout/fragment_login_server_selection.xml vector/src/main/res/layout/fragment_login_splash.xml vector/src/main/res/layout/item_login_header.xml vector/src/main/res/values/strings.xml vector/src/main/res/values/styles_login.xml
This commit is contained in:
commit
9f7bf14a22
180 changed files with 1718 additions and 887 deletions
|
@ -28,8 +28,8 @@ Even if we try to be able to work on all the functionalities, we have more knowl
|
|||
|
||||
# Other contributors
|
||||
|
||||
First of all, we thank all contributors who use RiotX and report problems on this GitHub project or via the integrated rageshake function.
|
||||
First of all, we thank all contributors who use Element and report problems on this GitHub project or via the integrated rageshake function.
|
||||
|
||||
We do not forget all translators, for their work of translating RiotX into many languages. They are also the authors of RiotX.
|
||||
We do not forget all translators, for their work of translating Element into many languages. They are also the authors of Element.
|
||||
|
||||
Feel free to add your name below, when you contribute to the project!
|
||||
|
|
41
CHANGES.md
41
CHANGES.md
|
@ -1,3 +1,44 @@
|
|||
Changes in Element 1.0.3 (2020-07-31)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Support server admin option to disable E2EE for DMs / private rooms [users can still enable] (#1794)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Crash reported on playstore for HomeActivity launch (151 reports)
|
||||
|
||||
Changes in Element 1.0.2 (2020-07-29)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- Added Session Database migration to avoid unneeded initial syncs
|
||||
|
||||
Changes in Element 1.0.1 (2020-07-28)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- Sending events is now retried only 3 times, so we avoid blocking the sending queue too long.
|
||||
- Display warning when fail to send events in room list
|
||||
- Improve UI of edit role action in member profile
|
||||
- Moderation | New screen to display list of banned users in room settings, with unban action
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix theme issue on Room directory screen (#1613)
|
||||
- Fix notification not dismissing when entering a room
|
||||
- Fix uploads don't work with Room v6 (#1558)
|
||||
- Fix Requesting avatar thumbnails in Element uses wrong http "user-agent" string (#1725)
|
||||
- Fix 404 on EMS (#1761)
|
||||
- Fix Infinite loop at startup when migrating account from Riot (#1699)
|
||||
- Fix Element crashes in loop after initial sync (#1709)
|
||||
- Remove inner mx-reply tags before replying
|
||||
- Fix timeline items not loading when there are only filtered events
|
||||
- Fix "Voice & Video" grayed out in Settings (#1733)
|
||||
- Fix Allow VOIP call in all rooms with 2 participants (even if not DM)
|
||||
- Migration from old client does not enable notifications (#1723)
|
||||
|
||||
Other changes:
|
||||
- i18n deactivated account error
|
||||
|
||||
Changes in Element 1.0.0 (2020-07-15)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
|
||||
|
||||
Android support can be found in this [![Riot Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riot-android:matrix.org.svg?label=%23riot-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||
|
||||
Dedicated room for RiotX: [![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||
Android support can be found in this [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org) room.
|
||||
|
||||
# Specific rules for Matrix Android projects
|
||||
|
||||
|
@ -37,15 +35,13 @@ Note that if the templates are modified, the only things to do is to restart And
|
|||
|
||||
## Compilation
|
||||
|
||||
For now, the Matrix SDK and the RiotX application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||
For now, the Matrix SDK and the Element application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||
|
||||
## I want to help translating RiotX
|
||||
## I want to help translating Element
|
||||
|
||||
If you want to fix an issue with an English string, please submit a PR.
|
||||
If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
|
||||
For the moment, Strings from Riot will be used, there is no dedicated project in Weblate for RiotX.
|
||||
|
||||
## I want to submit a PR to fix an issue
|
||||
|
||||
Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it.
|
||||
|
@ -101,7 +97,7 @@ Make sure the following commands execute without any error:
|
|||
|
||||
### Tests
|
||||
|
||||
RiotX is currently supported on Android KitKat (API 19+): please test your change on an Android device (or Android emulator) running with API 19. Many issues can happen (including crashes) on older devices.
|
||||
Element is currently supported on Android Lollipop (API 21+): please test your change on an Android device (or Android emulator) running with API 21. Many issues can happen (including crashes) on older devices.
|
||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||
|
||||
You should consider adding Unit tests with your PR, and also integration tests (AndroidTest). Please refer to [this document](./docs/integration_tests.md) to install and run the integration test environment.
|
||||
|
@ -120,7 +116,7 @@ Please consider accessibility as an important point. As a minimum requirement, i
|
|||
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
||||
You can check this in the layout editor preview by selecting any RTL language (ex: Arabic).
|
||||
|
||||
Also please check that the colors are ok for all the current themes of RiotX. Please use `?attr` instead of `@color` to reference colors in the layout. You can check this in the layout editor preview by selecting all the main themes (`AppTheme.Status`, `AppTheme.Dark`, etc.).
|
||||
Also please check that the colors are ok for all the current themes of Element. Please use `?attr` instead of `@color` to reference colors in the layout. You can check this in the layout editor preview by selecting all the main themes (`AppTheme.Status`, `AppTheme.Dark`, etc.).
|
||||
|
||||
### Authors
|
||||
|
||||
|
|
26
README.md
26
README.md
|
@ -1,38 +1,32 @@
|
|||
[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
[![Weblate](https://translate.riot.im/widgets/riot-android/-/svg-badge.svg)](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||
[![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org)
|
||||
[![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org)
|
||||
[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=alert_status)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=bugs)](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
|
||||
# RiotX Android
|
||||
# Element Android
|
||||
|
||||
RiotX is an Android Matrix Client currently in beta but in active development.
|
||||
Element Android is an Android Matrix Client provided by [Element](https://element.io/).
|
||||
|
||||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience.
|
||||
|
||||
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
||||
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app)
|
||||
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app)
|
||||
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
# New Android SDK
|
||||
|
||||
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the SDK is stable enough.
|
||||
|
||||
Element is based on a new Android SDK fully written in Kotlin (like Element). In order to make the early development as fast as possible, Element and the new SDK currently share the same git repository. We will make separate repos once the SDK is stable enough.
|
||||
|
||||
# Roadmap
|
||||
|
||||
The current target is to release an application out of beta with the same level of features (and even more) as Riot.
|
||||
The roadmap has 3 phases:
|
||||
|
||||
- [phase 0](https://github.com/vector-im/riotX-android/labels/phase0): Prototyping / Project setup
|
||||
- [phase 1](https://github.com/vector-im/riotX-android/labels/phase1): Beta release to the Play Store
|
||||
- [phase 2](https://github.com/vector-im/riotX-android/labels/phase2): Out of beta
|
||||
|
||||
The version 1.0.0 of Element still misses some features which was previously included in Riot-Android.
|
||||
The team will work to add them on a regular basis.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects!
|
||||
|
||||
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#riotx:matrix.org).
|
||||
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#element-android:matrix.org).
|
||||
|
|
|
@ -14,7 +14,7 @@ Difference though (list not exhaustive):
|
|||
- Only API v2 is supported (see https://matrix.org/docs/spec/identity_service/latest)
|
||||
- Homeserver has to be up to date to support binding (Versions.isLoginAndRegistrationSupportedBySdk() has to return true)
|
||||
- The SDK managed the session and client secret when binding ThreePid. Those data are not exposed to the client.
|
||||
- The SDK supports incremental sendAttempt (this is not used by RiotX)
|
||||
- The SDK supports incremental sendAttempt (this is not used by Element)
|
||||
- The "Continue" button is now under the information, and not as the same place that the checkbox
|
||||
- The app can cancel a binding. Current data are erased from DB.
|
||||
- The API (IdentityService) is improved.
|
||||
|
@ -22,7 +22,7 @@ Difference though (list not exhaustive):
|
|||
|
||||
Missing features (list not exhaustive):
|
||||
- Invite by 3Pid (will be in a dedicated PR)
|
||||
- Add email or phone to account (not P1, can be done on Riot-Web)
|
||||
- Add email or phone to account (not P1, can be done on Element-Web)
|
||||
- List email and phone of the account (could be done in a dedicated PR)
|
||||
- Search contact (not P1)
|
||||
- Logout from identity server when user sign out or deactivate his account.
|
||||
|
@ -55,7 +55,7 @@ The list can be found here: https://matrix.org/blog/2019/09/27/privacy-improveme
|
|||
|
||||
- Default identity server URL, from Wellknown data is proposed to the user.
|
||||
- Identity server can be set
|
||||
- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) RiotX should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
|
||||
- Identity server can be changed on another user's device, so when the change is detected (thanks to account data sync) Element should properly disconnect from a previous identity server (I think it was not the case in Riot-Android, where we keep the token forever)
|
||||
- Registration to the identity server is managed with an openId token
|
||||
- Terms of service can be accepted when configuring the identity server.
|
||||
- Terms of service can be accepted after, if they change.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
This document aims to describe how RiotX android displays notifications to the end user. It also clarifies notifications and background settings in the app.
|
||||
This document aims to describe how Element android displays notifications to the end user. It also clarifies notifications and background settings in the app.
|
||||
|
||||
# Table of Contents
|
||||
1. [Prerequisites Knowledge](#prerequisites-knowledge)
|
||||
|
@ -9,7 +9,7 @@ This document aims to describe how RiotX android displays notifications to the e
|
|||
* [How does the Home Server knows when to notify a client?](#how-does-the-home-server-knows-when-to-notify-a-client)
|
||||
* [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation)
|
||||
* [Background processing limitations](#background-processing-limitations)
|
||||
2. [RiotX Notification implementations](#riotx-notification-implementations)
|
||||
2. [Element Notification implementations](#element-notification-implementations)
|
||||
* [Requirements](#requirements)
|
||||
* [Foreground sync mode (Gplay & F-Droid)](#foreground-sync-mode-gplay-f-droid)
|
||||
* [Push (FCM) received in background](#push-fcm-received-in-background)
|
||||
|
@ -50,7 +50,7 @@ By default, this is 0, so the server will return immediately even if the respons
|
|||
|
||||
**delay** is a client preference. When the server responds to a sync request, the client waits for `delay`before calling a new sync.
|
||||
|
||||
When the RiotX Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0.
|
||||
When the Element Android app is open (i.e in foreground state), the default timeout is 30 seconds, and delay is 0.
|
||||
|
||||
## How does a mobile app receives push notification
|
||||
|
||||
|
@ -86,7 +86,7 @@ This need some disambiguation, because it is the source of common confusion:
|
|||
In order to send a push to a mobile, App developers need to have a server that will use the FCM APIs, and these APIs requires authentication!
|
||||
This server is called a **Push Gateway** in the matrix world
|
||||
|
||||
That means that RiotX Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client.
|
||||
That means that Element Android, a matrix client created by New Vector, is using a **Push Gateway** with the needed credentials (FCM API secret Key) in order to send push to the New Vector client.
|
||||
|
||||
If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app.
|
||||
|
||||
|
@ -132,7 +132,7 @@ A Home Server can be configured with default rules (for Direct messages, group m
|
|||
|
||||
There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based).
|
||||
|
||||
Notifications have 2 'levels' (`highlighted = true/false sound = default/custom`). In RiotX these notifications level are reflected as Noisy/Silent.
|
||||
Notifications have 2 'levels' (`highlighted = true/false sound = default/custom`). In Element these notifications level are reflected as Noisy/Silent.
|
||||
|
||||
**What about encrypted messages?**
|
||||
|
||||
|
@ -158,7 +158,7 @@ In a nutshell, apps can't do much in background now.
|
|||
|
||||
If the devices is not plugged and stays IDLE for a certain amount of time, radio (mobile connectivity) and CPU can/will be turned off.
|
||||
|
||||
For an application like RiotX, where users can receive important information at anytime, the best option is to rely on a push system (Google's Firebase Message a.k.a FCM). FCM high priority push can wake up the device and whitelist an application to perform background task (for a limited but unspecified amount of time).
|
||||
For an application like Element, where users can receive important information at anytime, the best option is to rely on a push system (Google's Firebase Message a.k.a FCM). FCM high priority push can wake up the device and whitelist an application to perform background task (for a limited but unspecified amount of time).
|
||||
|
||||
Notice that this is still evolving, and in future versions application that has been 'background restricted' by users won't be able to wake up even when a high priority push is received. Also high priority notifications could be rate limited (not defined anywhere)
|
||||
|
||||
|
@ -167,41 +167,41 @@ The documentation on this subject is vague, and as per our experiments not alway
|
|||
|
||||
It is getting more and more complex to have reliable notifications when FCM is not used.
|
||||
|
||||
# RiotX Notification implementations
|
||||
# Element Notification implementations
|
||||
|
||||
## Requirements
|
||||
|
||||
RiotX Android must work with and without FCM.
|
||||
* The RiotX android app published on F-Droid do not rely on FCM (all related dependencies are not present)
|
||||
* The RiotX android app published on google play rely on FCM, with a fallback mode when FCM registration has failed (e.g outdated or missing Google Play Services)
|
||||
Element Android must work with and without FCM.
|
||||
* The Element android app published on F-Droid do not rely on FCM (all related dependencies are not present)
|
||||
* The Element android app published on google play rely on FCM, with a fallback mode when FCM registration has failed (e.g outdated or missing Google Play Services)
|
||||
|
||||
## Foreground sync mode (Gplay & F-Droid)
|
||||
|
||||
When in foreground, RiotX performs sync continuously with a timeout value set to 10 seconds (see HttpPooling).
|
||||
When in foreground, Element performs sync continuously with a timeout value set to 10 seconds (see HttpPooling).
|
||||
|
||||
As this mode does not need to live beyond the scope of the application, and as per Google recommendation, RiotX uses the internal app resources (Thread and Timers) to perform the syncs.
|
||||
As this mode does not need to live beyond the scope of the application, and as per Google recommendation, Element uses the internal app resources (Thread and Timers) to perform the syncs.
|
||||
|
||||
This mode is turned on when the app enters foreground, and off when enters background.
|
||||
|
||||
In background, and depending on wether push is available or not, RiotX will use different methods to perform the syncs (Workers / Alarms / Service)
|
||||
In background, and depending on wether push is available or not, Element will use different methods to perform the syncs (Workers / Alarms / Service)
|
||||
|
||||
## Push (FCM) received in background
|
||||
|
||||
In order to enable Push, RiotX must first get a push token from the firebase SDK, then register a pusher with this token on the HomeServer.
|
||||
In order to enable Push, Element must first get a push token from the firebase SDK, then register a pusher with this token on the HomeServer.
|
||||
|
||||
When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for RiotX, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org.
|
||||
When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for Element, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org.
|
||||
|
||||
This sygnal instance is configured with the required FCM API authentication token, and will then use the FCM API in order to notify the user's device running riotX.
|
||||
This sygnal instance is configured with the required FCM API authentication token, and will then use the FCM API in order to notify the user's device running Element.
|
||||
|
||||
```
|
||||
Homeserver ----> Sygnal (configured for RiotX) ----> FCM ----> RiotX
|
||||
Homeserver ----> Sygnal (configured for Element) ----> FCM ----> Element
|
||||
```
|
||||
|
||||
The push gateway is configured to only send `(eventId,roomId)` in the push payload (for better [privacy](#push-vs-privacy-and-mitigation)).
|
||||
|
||||
RiotX needs then to synchronise with the user's HomeServer, in order to resolve the event and create a notification.
|
||||
Element needs then to synchronise with the user's HomeServer, in order to resolve the event and create a notification.
|
||||
|
||||
As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), RiotX will then use the WorkManager API in order to trigger a background sync.
|
||||
As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), Element will then use the WorkManager API in order to trigger a background sync.
|
||||
|
||||
**Google recommendations:**
|
||||
> We recommend using FCM messages in combination with the WorkManager 1 or JobScheduler API
|
||||
|
@ -209,7 +209,7 @@ As per [Google recommendation](https://android-developers.googleblog.com/2018/09
|
|||
> Avoid background services. One common pitfall is using a background service to fetch data in the FCM message handler, since background service will be stopped by the system per recent changes to Google Play Policy
|
||||
|
||||
```
|
||||
Homeserver ----> Sygnal ----> FCM ----> RiotX
|
||||
Homeserver ----> Sygnal ----> FCM ----> Element
|
||||
(Sync) ----> Homeserver
|
||||
<----
|
||||
Display notification
|
||||
|
@ -217,24 +217,24 @@ Homeserver ----> Sygnal ----> FCM ----> RiotX
|
|||
|
||||
**Possible outcomes**
|
||||
|
||||
Upon reception of the FCM push, RiotX will perform a sync call to the Home Server, during this process it is possible that:
|
||||
Upon reception of the FCM push, Element will perform a sync call to the Home Server, during this process it is possible that:
|
||||
* Happy path, the sync is performed, the message resolved and displayed in the notification drawer
|
||||
* The notified message is not in the sync. Can happen if a lot of things did happen since the push (`gappy sync`)
|
||||
* The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally)
|
||||
* The sync takes too long and the process is killed before completion, or network is not reliable and the sync fails.
|
||||
|
||||
RiotX implements several strategies in these cases (TODO document)
|
||||
Element implements several strategies in these cases (TODO document)
|
||||
|
||||
## FCM Fallback mode
|
||||
|
||||
It is possible that RiotX is not able to get a FCM push token.
|
||||
It is possible that Element is not able to get a FCM push token.
|
||||
Common errors (amoung several others) that can cause that:
|
||||
* Google Play Services is outdated
|
||||
* Google Play Service fails in someways with FCM servers (infamous `SERVICE_NOT_AVAILABLE`)
|
||||
|
||||
If RiotX is able to detect one of this cases, it will notifies it to the users and when possible help him fix it via a dedicated troubleshoot screen.
|
||||
If Element is able to detect one of this cases, it will notifies it to the users and when possible help him fix it via a dedicated troubleshoot screen.
|
||||
|
||||
Meanwhile, in order to offer a minimal service, and as per Google's recommendation for background activities, RiotX will launch periodic background sync in order to stays in sync with servers.
|
||||
Meanwhile, in order to offer a minimal service, and as per Google's recommendation for background activities, Element will launch periodic background sync in order to stays in sync with servers.
|
||||
|
||||
The fallback mode is impacted by all the battery life saving mechanism implemented by android. Meaning that if the app is not used for a certain amount of time (`App-Standby`), or the device stays still and unplugged (`Light Doze`) , the sync will become less frequent.
|
||||
|
||||
|
@ -248,7 +248,7 @@ The fallback mode is supposed to be a temporary state waiting for the user to fi
|
|||
|
||||
## F-Droid background Mode
|
||||
|
||||
The F-Droid RiotX flavor has no dependencies to FCM, therefore cannot relies on Push.
|
||||
The F-Droid Element flavor has no dependencies to FCM, therefore cannot relies on Push.
|
||||
|
||||
Also Google's recommended background processing method cannot be applied. This is because all of these methods are affected by IDLE modes, and will result on the user not being notified at all when the app is in a Doze mode (only in maintenance windows that could happens only after hours).
|
||||
|
||||
|
@ -262,7 +262,7 @@ F-Droid version will schedule alarms that will then trigger a Broadcast Receiver
|
|||
|
||||
Depending on the system status (or device make), it is still possible that the app is not given enough time to launch the service, or that the radio is still turned off thus preventing the sync to success (that's why Alarms are not recommended for network related tasks).
|
||||
|
||||
That is why on RiotX F-Droid, the broadcast receiver will acquire a temporary WAKE_LOCK for several seconds (thus securing cpu/network), and launch the service in foreground. The service performs the sync.
|
||||
That is why on Element F-Droid, the broadcast receiver will acquire a temporary WAKE_LOCK for several seconds (thus securing cpu/network), and launch the service in foreground. The service performs the sync.
|
||||
|
||||
Note that foreground services require to put a notification informing the user that the app is doing something even if not launched).
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ It's worth noting that the response from the homeserver contains the userId of A
|
|||
|
||||
### Login with Msisdn
|
||||
|
||||
Not supported yet in RiotX
|
||||
Not supported yet in Element
|
||||
|
||||
### Login with SSO
|
||||
|
||||
|
@ -155,9 +155,9 @@ Not supported yet in RiotX
|
|||
|
||||
In this case, the user can click on "Sign in with SSO" and the native web browser, or a ChromeCustomTab if the device supports it, will be launched on the page
|
||||
|
||||
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=riotx%3A%2F%2Friotx
|
||||
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=element%3A%2F%element
|
||||
|
||||
The parameter `redirectUrl` is set to `riotx://riotx`.
|
||||
The parameter `redirectUrl` is set to `element://element`.
|
||||
|
||||
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
|
||||
|
||||
|
@ -167,9 +167,9 @@ During the process, user may be asked to validate an email by clicking on a link
|
|||
|
||||
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
|
||||
|
||||
> riotx://riotx?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
|
||||
> element://element?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
|
||||
|
||||
This navigation is intercepted by RiotX by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
||||
This navigation is intercepted by Element by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
||||
|
||||
> curl -X POST --data $'{"type":"m.login.token","token":"MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy"}' 'https://homeserver.with.sso/_matrix/client/r0/login'
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ Useful links:
|
|||
│ │ │ │ │ mx event │ │ │ │
|
||||
│ │ │ └────────────────────┘ │ │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
│ Riot.im │ │ │ │ │ Riot.im │
|
||||
│ Element │ │ │ │ │ Element │
|
||||
┌──│ App │ │ │ │ │ App │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
|
@ -103,7 +103,7 @@ Useful links:
|
|||
│ │ ┌────┐ │ │ └────────────────────┘ │ │ │
|
||||
│ │ │ 3 │ │ ┌────────────────────┐ │ │ │ │
|
||||
│ │──────┴────┴───────┼──────────────────┼─▶│ m.call.candidates │ │ │ │
|
||||
│ Riot.im │ │ │ mx event │ │ │ │ Riot.im │
|
||||
│ Element │ │ │ mx event │ │ │ │ Element │
|
||||
│ App │ │ │ └────────────────────┘ │ │ App │
|
||||
│ │ │ │ │ │ │
|
||||
│ │ │ │ │ │ │
|
||||
|
@ -195,9 +195,9 @@ Useful links:
|
|||
│ │ │ m.call.invite │───┼────────────────────────────┬────┬───▶│ │
|
||||
┌─────────────────┐ │ │ mx event │ │ │ │ 1 │ │ │
|
||||
│ │ │ │ └────────────────────┘ │ └────┘ │ │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ Riot.im │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ Element │
|
||||
│ │ │ │ │ m.call.candidates │ │ │ App │
|
||||
│ Riot.im │ │ │ mx event │ │ │ │ │
|
||||
│ Element │ │ │ mx event │ │ │ │ │
|
||||
│ App │ │ │ └────────────────────┘ │ │ │
|
||||
│ │ │ ┌────────────────────┐◀──┼─────────────────┼───┬────┬───────────┤ │
|
||||
│ │◀──────────────────┼──────────────────┼──│ m.call.answer │ │ │ 4 │ └──┬──────────────┘
|
||||
|
@ -275,7 +275,7 @@ Useful links:
|
|||
│ │ │ │ └────────────────────┘ │ │ │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ │
|
||||
│ │ │ │ │ m.call.candidates │ │ │ │
|
||||
│ Riot.im │ │ │ mx event │ │ │ │ Riot.im │
|
||||
│ Element │ │ │ mx event │ │ │ │ Element │
|
||||
│ App │ │ │ └────────────────────┘ │ ┌────┐ │ App │
|
||||
│ │ │ ┌────────────────────┐ │ │ │ 3 │ │ │
|
||||
│ │◀──────────────────┼┐ │ │ m.call.answer │ │ ┌───────┴────┴────────│ │
|
||||
|
@ -370,7 +370,7 @@ Useful links:
|
|||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ Riot.im │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Riot.im │
|
||||
│ Element │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Element │
|
||||
│ App │ │ App │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
|
|
|
@ -42,8 +42,7 @@ import im.vector.matrix.android.api.util.toOptional
|
|||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function3
|
||||
|
@ -179,7 +178,7 @@ class RxSession(private val session: Session) {
|
|||
}
|
||||
|
||||
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
|
||||
return Observable.combineLatest<List<UserAccountData>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
||||
return Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
||||
liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
|
||||
liveCrossSigningInfo(session.myUserId),
|
||||
liveCrossSigningPrivateKeys(),
|
||||
|
|
|
@ -116,6 +116,7 @@ dependencies {
|
|||
def markwon_version = '3.1.0'
|
||||
def daggerVersion = '2.25.4'
|
||||
def work_version = '2.3.3'
|
||||
def retrofit_version = '2.6.2'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
|
@ -128,8 +129,9 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'
|
||||
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
|
||||
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
|
||||
implementation "com.squareup.retrofit2:converter-scalars:$retrofit_version"
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
|
|
|
@ -35,7 +35,7 @@ import im.vector.matrix.android.common.TestMatrixCallback
|
|||
import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||
import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
@ -53,5 +53,15 @@ data class WellKnown(
|
|||
val identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: JsonDict? = null
|
||||
val integrations: JsonDict? = null,
|
||||
|
||||
@Json(name = "im.vector.riot.e2ee")
|
||||
val e2eAdminSetting: E2EWellKnownConfig? = null
|
||||
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class E2EWellKnownConfig(
|
||||
@Json(name = "default")
|
||||
val e2eDefault: Boolean = true
|
||||
)
|
||||
|
|
|
@ -21,7 +21,6 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
|
||||
interface AccountDataService {
|
||||
/**
|
||||
|
|
|
@ -14,14 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
package im.vector.matrix.android.api.session.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.AllowedWidgetsContent
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
|
||||
/**
|
||||
* This is a simplified Event with just a type and a content.
|
||||
* Currently used types are defined in [UserAccountDataTypes].
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataAllowedWidgets(
|
||||
@Json(name = "type") override val type: String = TYPE_ALLOWED_WIDGETS,
|
||||
@Json(name = "content") val content: AllowedWidgetsContent
|
||||
) : UserAccountData()
|
||||
data class UserAccountDataEvent(
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "content") val content: Content
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.api.session.accountdata
|
||||
|
||||
object UserAccountDataTypes {
|
||||
|
||||
const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list"
|
||||
const val TYPE_DIRECT_MESSAGES = "m.direct"
|
||||
const val TYPE_BREADCRUMBS = "im.vector.setting.breadcrumbs" // Was previously "im.vector.riot.breadcrumb_rooms"
|
||||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||
const val TYPE_WIDGETS = "m.widgets"
|
||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||
const val TYPE_INTEGRATION_PROVISIONING = "im.vector.setting.integration_provisioning"
|
||||
const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
|
||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
||||
}
|
|
@ -21,9 +21,11 @@ import com.squareup.moshi.JsonClass
|
|||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
|
@ -240,6 +242,18 @@ fun Event.isFileMessage(): Boolean {
|
|||
return getClearType() == EventType.MESSAGE
|
||||
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
|
||||
MessageType.MSGTYPE_FILE -> true
|
||||
else -> false
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.getRelationContent(): RelationDefaultContent? {
|
||||
return if (isEncrypted()) {
|
||||
content.toModel<EncryptedEventContent>()?.relatesTo
|
||||
} else {
|
||||
content.toModel<MessageContent>()?.relatesTo
|
||||
}
|
||||
}
|
||||
|
||||
fun Event.isReply(): Boolean {
|
||||
return getRelationContent()?.inReplyTo?.eventId != null
|
||||
}
|
||||
|
|
|
@ -32,7 +32,12 @@ data class HomeServerCapabilities(
|
|||
/**
|
||||
* Default identity server url, provided in Wellknown
|
||||
*/
|
||||
val defaultIdentityServerUrl: String? = null
|
||||
val defaultIdentityServerUrl: String? = null,
|
||||
/**
|
||||
* Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms
|
||||
* (as it was before) for various environments where this is desired.
|
||||
*/
|
||||
val adminE2EByDefault: Boolean = true
|
||||
) {
|
||||
companion object {
|
||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||
|
|
|
@ -53,7 +53,8 @@ data class RoomSummary constructor(
|
|||
val typingUsers: List<SenderInfo>,
|
||||
val inviterId: String? = null,
|
||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
|
||||
val hasFailedSending: Boolean = false
|
||||
) {
|
||||
|
||||
val isVersioned: Boolean
|
||||
|
@ -66,7 +67,7 @@ data class RoomSummary constructor(
|
|||
get() = tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE }
|
||||
|
||||
val canStartCall: Boolean
|
||||
get() = isDirect && joinedMembersCount == 2
|
||||
get() = joinedMembersCount == 2
|
||||
|
||||
companion object {
|
||||
const val NOT_IN_BREADCRUMBS = -1
|
||||
|
|
|
@ -25,7 +25,3 @@ interface MessageContent {
|
|||
val relatesTo: RelationDefaultContent?
|
||||
val newContent: Content?
|
||||
}
|
||||
|
||||
fun MessageContent?.isReply(): Boolean {
|
||||
return this?.relatesTo?.inReplyTo?.eventId != null
|
||||
}
|
||||
|
|
|
@ -20,15 +20,16 @@ import im.vector.matrix.android.BuildConfig
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||
import im.vector.matrix.android.api.session.events.model.getRelationContent
|
||||
import im.vector.matrix.android.api.session.events.model.isReply
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.api.session.room.sender.SenderInfo
|
||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
|
||||
/**
|
||||
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
|
||||
|
@ -88,11 +89,18 @@ data class TimelineEvent(
|
|||
*/
|
||||
fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
|
||||
|
||||
/**
|
||||
* Get the relation content if any
|
||||
*/
|
||||
fun TimelineEvent.getRelationContent(): RelationDefaultContent? {
|
||||
return root.getRelationContent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the eventId which was edited by this event if any
|
||||
*/
|
||||
fun TimelineEvent.getEditedEventId(): String? {
|
||||
return root.getClearContent().toModel<MessageContent>()?.relatesTo?.takeIf { it.type == RelationType.REPLACE }?.eventId
|
||||
return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,11 +129,16 @@ fun TimelineEvent.getLastMessageBody(): String? {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it's a reply
|
||||
*/
|
||||
fun TimelineEvent.isReply(): Boolean {
|
||||
return root.isReply()
|
||||
}
|
||||
|
||||
fun TimelineEvent.getTextEditableContent(): String? {
|
||||
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null
|
||||
val isReply = originalContent.isReply() || root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId != null
|
||||
val lastContent = getLastMessageContent()
|
||||
return if (isReply) {
|
||||
return if (isReply()) {
|
||||
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
||||
} else {
|
||||
lastContent?.body ?: ""
|
||||
|
|
|
@ -19,7 +19,7 @@ import im.vector.matrix.android.api.session.crypto.CryptoService
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
|
||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.awaitCallback
|
||||
import javax.inject.Inject
|
||||
|
@ -35,7 +35,7 @@ internal interface EncryptEventTask : Task<EncryptEventTask.Params, Event> {
|
|||
|
||||
internal class DefaultEncryptEventTask @Inject constructor(
|
||||
// private val crypto: CryptoService
|
||||
private val localEchoUpdater: LocalEchoUpdater
|
||||
private val localEchoRepository: LocalEchoRepository
|
||||
) : EncryptEventTask {
|
||||
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
||||
if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
|
||||
|
@ -44,7 +44,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||
|
||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||
params.keepKeys?.forEach {
|
||||
|
|
|
@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
|
||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository
|
||||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
@ -34,7 +34,7 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
|||
}
|
||||
|
||||
internal class DefaultSendEventTask @Inject constructor(
|
||||
private val localEchoUpdater: LocalEchoUpdater,
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val encryptEventTask: DefaultEncryptEventTask,
|
||||
private val roomAPI: RoomAPI,
|
||||
private val eventBus: EventBus) : SendEventTask {
|
||||
|
@ -44,7 +44,7 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||
val localId = event.eventId!!
|
||||
|
||||
try {
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
||||
localEchoRepository.updateSendState(localId, SendState.SENDING)
|
||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||
apiCall = roomAPI.send(
|
||||
localId,
|
||||
|
@ -53,10 +53,10 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||
eventType = event.type
|
||||
)
|
||||
}
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
||||
localEchoRepository.updateSendState(localId, SendState.SENT)
|
||||
return executeRequest.eventId
|
||||
} catch (e: Throwable) {
|
||||
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
||||
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
|
||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoRepository
|
||||
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
@ -34,7 +34,7 @@ internal interface SendVerificationMessageTask : Task<SendVerificationMessageTas
|
|||
}
|
||||
|
||||
internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||
private val localEchoUpdater: LocalEchoUpdater,
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val encryptEventTask: DefaultEncryptEventTask,
|
||||
private val roomAPI: RoomAPI,
|
||||
private val eventBus: EventBus) : SendVerificationMessageTask {
|
||||
|
@ -44,7 +44,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
val localId = event.eventId!!
|
||||
|
||||
try {
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
||||
localEchoRepository.updateSendState(localId, SendState.SENDING)
|
||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||
apiCall = roomAPI.send(
|
||||
localId,
|
||||
|
@ -53,10 +53,10 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||
eventType = event.type
|
||||
)
|
||||
}
|
||||
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
||||
localEchoRepository.updateSendState(localId, SendState.SENT)
|
||||
return executeRequest.eventId
|
||||
} catch (e: Throwable) {
|
||||
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
||||
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResu
|
|||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventInsertEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventInsertEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.EventInsertLiveProcessor
|
||||
|
@ -46,17 +47,25 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||
if (!results.isLoaded || results.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val idsToDeleteAfterProcess = ArrayList<String>()
|
||||
val filteredEvents = ArrayList<EventInsertEntity>(results.size)
|
||||
Timber.v("EventInsertEntity updated with ${results.size} results in db")
|
||||
val filteredEvents = results.mapNotNull {
|
||||
results.forEach {
|
||||
if (shouldProcess(it)) {
|
||||
results.realm.copyFromRealm(it)
|
||||
} else {
|
||||
null
|
||||
// don't use copy from realm over there
|
||||
val copiedEvent = EventInsertEntity(
|
||||
eventId = it.eventId,
|
||||
eventType = it.eventType
|
||||
).apply {
|
||||
insertType = it.insertType
|
||||
}
|
||||
filteredEvents.add(copiedEvent)
|
||||
}
|
||||
idsToDeleteAfterProcess.add(it.eventId)
|
||||
}
|
||||
Timber.v("There are ${filteredEvents.size} events to process")
|
||||
observerScope.launch {
|
||||
awaitTransaction(realmConfiguration) { realm ->
|
||||
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
|
||||
filteredEvents.forEach { eventInsert ->
|
||||
val eventId = eventInsert.eventId
|
||||
val event = EventEntity.where(realm, eventId).findFirst()
|
||||
|
@ -72,7 +81,10 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
|
|||
it.process(realm, domainEvent)
|
||||
}
|
||||
}
|
||||
realm.delete(EventInsertEntity::class.java)
|
||||
realm.where(EventInsertEntity::class.java)
|
||||
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import io.realm.RealmObject
|
|||
import io.realm.RealmResults
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
@ -39,7 +40,7 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val r
|
|||
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
|
||||
}
|
||||
|
||||
protected val observerScope = CoroutineScope(SupervisorJob())
|
||||
protected val observerScope = CoroutineScope(SupervisorJob() + BACKGROUND_HANDLER.asCoroutineDispatcher())
|
||||
protected abstract val query: Monarchy.Query<T>
|
||||
private val isStarted = AtomicBoolean(false)
|
||||
private val backgroundRealm = AtomicReference<Realm>()
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.database
|
||||
|
||||
import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
Timber.d("Step 0 -> 1")
|
||||
// Add hasFailedSending in RoomSummary and a small warning icon on room list
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.HAS_FAILED_SENDING, Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo2(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,8 +42,13 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||
@SessionFilesDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
val migration: RealmSessionStoreMigration,
|
||||
context: Context) {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 2L
|
||||
}
|
||||
|
||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||
|
||||
fun create(): RealmConfiguration {
|
||||
|
@ -67,7 +72,8 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
|||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.modules(SessionRealmModule())
|
||||
.deleteRealmIfMigrationNeeded()
|
||||
.schemaVersion(SESSION_STORE_SCHEMA_VERSION)
|
||||
.migration(migration)
|
||||
.build()
|
||||
|
||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.database.mapper
|
|||
import com.squareup.moshi.Moshi
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AccountDataMapper @Inject constructor(moshi: Moshi) {
|
||||
|
|
|
@ -29,7 +29,8 @@ internal object HomeServerCapabilitiesMapper {
|
|||
canChangePassword = entity.canChangePassword,
|
||||
maxUploadFileSize = entity.maxUploadFileSize,
|
||||
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
|
||||
adminE2EByDefault = entity.adminE2EByDefault
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
|||
encryptionEventTs = roomSummaryEntity.encryptionEventTs,
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||
inviterId = roomSummaryEntity.inviterId
|
||||
inviterId = roomSummaryEntity.inviterId,
|
||||
hasFailedSending = roomSummaryEntity.hasFailedSending
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ internal open class HomeServerCapabilitiesEntity(
|
|||
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||
var lastVersionIdentityServerSupported: Boolean = false,
|
||||
var defaultIdentityServerUrl: String? = null,
|
||||
var adminE2EByDefault: Boolean = true,
|
||||
var lastUpdatedTimestamp: Long = 0L
|
||||
) : RealmObject() {
|
||||
|
||||
|
|
|
@ -51,7 +51,8 @@ internal open class RoomSummaryEntity(
|
|||
var isEncrypted: Boolean = false,
|
||||
var encryptionEventTs: Long? = 0,
|
||||
var roomEncryptionTrustLevelStr: String? = null,
|
||||
var inviterId: String? = null
|
||||
var inviterId: String? = null,
|
||||
var hasFailedSending: Boolean = false
|
||||
) : RealmObject() {
|
||||
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
|
|
@ -93,9 +93,12 @@ internal fun TimelineEventEntity.Companion.findAllInRoomWithSendStates(realm: Re
|
|||
roomId: String,
|
||||
sendStates: List<SendState>)
|
||||
: RealmResults<TimelineEventEntity> {
|
||||
val sendStatesStr = sendStates.map { it.name }.toTypedArray()
|
||||
return realm.where<TimelineEventEntity>()
|
||||
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||
.`in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr)
|
||||
return whereRoomId(realm, roomId)
|
||||
.filterSendStates(sendStates)
|
||||
.findAll()
|
||||
}
|
||||
|
||||
internal fun RealmQuery<TimelineEventEntity>.filterSendStates(sendStates: List<SendState>): RealmQuery<TimelineEventEntity> {
|
||||
val sendStatesStr = sendStates.map { it.name }.toTypedArray()
|
||||
return `in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr)
|
||||
}
|
||||
|
|
|
@ -34,24 +34,12 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVideoConte
|
|||
import im.vector.matrix.android.internal.network.parsing.ForceToBooleanJsonAdapter
|
||||
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
|
||||
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules
|
||||
|
||||
object MoshiProvider {
|
||||
|
||||
private val moshi: Moshi = Moshi.Builder()
|
||||
.add(UriMoshiAdapter())
|
||||
.add(ForceToBooleanJsonAdapter())
|
||||
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java)
|
||||
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
|
||||
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
|
||||
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
|
||||
.registerSubtype(UserAccountDataBreadcrumbs::class.java, UserAccountData.TYPE_BREADCRUMBS)
|
||||
)
|
||||
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)
|
||||
.registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT)
|
||||
.registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE)
|
||||
|
|
|
@ -24,6 +24,7 @@ import okhttp3.OkHttpClient
|
|||
import okhttp3.Request
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.converter.scalars.ScalarsConverterFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
||||
|
@ -48,6 +49,7 @@ internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
|||
return okHttpClient.get().newCall(request)
|
||||
}
|
||||
})
|
||||
.addConverterFactory(ScalarsConverterFactory.create())
|
||||
.addConverterFactory(UnitConverterFactory)
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.build()
|
||||
|
|
|
@ -29,7 +29,7 @@ internal class UserAgentInterceptor @Inject constructor(private val userAgentHol
|
|||
userAgentHolder.userAgent
|
||||
.takeIf { it.isNotBlank() }
|
||||
?.let {
|
||||
newRequestBuilder.addHeader(HttpHeaders.UserAgent, it)
|
||||
newRequestBuilder.header(HttpHeaders.UserAgent, it)
|
||||
}
|
||||
request = newRequestBuilder.build()
|
||||
return chain.proceed(request)
|
||||
|
|
|
@ -108,13 +108,15 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
|
||||
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
||||
|
||||
homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true
|
||||
// We are also checking for integration manager configurations
|
||||
val config = configExtractor.extract(getWellknownResult.wellKnown)
|
||||
if (config != null) {
|
||||
Timber.v("Extracted integration config : $config")
|
||||
realm.insertOrUpdate(config)
|
||||
}
|
||||
} else {
|
||||
homeServerCapabilitiesEntity.adminE2EByDefault = true
|
||||
}
|
||||
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
|||
import im.vector.matrix.android.internal.session.profile.BindThreePidsTask
|
||||
import im.vector.matrix.android.internal.session.profile.UnbindThreePidsTask
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
|
@ -95,7 +95,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
// Observe the account data change
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_IDENTITY_SERVER)
|
||||
.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_IDENTITY_SERVER)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
notifyIdentityServerUrlChange(it.getOrNull()?.content?.toModel<IdentityServerContent>()?.baseUrl)
|
||||
}
|
||||
|
|
|
@ -34,8 +34,8 @@ import im.vector.matrix.android.internal.di.SessionDatabase
|
|||
import im.vector.matrix.android.internal.extensions.observeNotNull
|
||||
import im.vector.matrix.android.internal.session.SessionLifecycleObserver
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.WidgetFactory
|
||||
|
@ -87,7 +87,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
lifecycleRegistry.currentState = Lifecycle.State.STARTED
|
||||
observeWellknownConfig()
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val allowedWidgetsContent = it.getOrNull()?.content?.toModel<AllowedWidgetsContent>()
|
||||
if (allowedWidgetsContent != null) {
|
||||
|
@ -95,7 +95,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
|
||||
.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_INTEGRATION_PROVISIONING)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationProvisioningContent = it.getOrNull()?.content?.toModel<IntegrationProvisioningContent>()
|
||||
if (integrationProvisioningContent != null) {
|
||||
|
@ -103,7 +103,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
}
|
||||
}
|
||||
accountDataDataSource
|
||||
.getLiveAccountDataEvent(UserAccountData.TYPE_WIDGETS)
|
||||
.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS)
|
||||
.observeNotNull(lifecycleOwner) {
|
||||
val integrationManagerContent = it.getOrNull()?.asIntegrationManagerWidgetContent()
|
||||
val config = integrationManagerContent?.extractIntegrationManagerConfig()
|
||||
|
@ -132,7 +132,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
* Returns false if the user as disabled integration manager feature
|
||||
*/
|
||||
fun isIntegrationEnabled(): Boolean {
|
||||
val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_INTEGRATION_PROVISIONING)
|
||||
val integrationProvisioningData = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_INTEGRATION_PROVISIONING)
|
||||
val integrationProvisioningContent = integrationProvisioningData?.content?.toModel<IntegrationProvisioningContent>()
|
||||
return integrationProvisioningContent?.enabled ?: false
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
}
|
||||
|
||||
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val allowedWidget = mapOf(stateEventId to allowed)
|
||||
|
@ -173,13 +173,13 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
}
|
||||
|
||||
fun isWidgetAllowed(stateEventId: String): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.widgets?.get(stateEventId) ?: false
|
||||
}
|
||||
|
||||
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
val newContent = if (currentContent == null) {
|
||||
val nativeAllowedWidgets = mapOf(widgetType to mapOf(domain to allowed))
|
||||
|
@ -203,7 +203,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
|
|||
}
|
||||
|
||||
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean {
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ALLOWED_WIDGETS)
|
||||
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
|
||||
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
|
||||
return currentContent?.native?.get(widgetType)?.get(domain) ?: false
|
||||
}
|
||||
|
|
|
@ -129,6 +129,21 @@ internal interface RoomAPI {
|
|||
@Body content: Content?
|
||||
): Call<SendResponse>
|
||||
|
||||
/**
|
||||
* Send an event to a room.
|
||||
*
|
||||
* @param txId the transaction Id
|
||||
* @param roomId the room id
|
||||
* @param eventType the event type
|
||||
* @param content the event content as string
|
||||
*/
|
||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}")
|
||||
fun send(@Path("txId") txId: String,
|
||||
@Path("roomId") roomId: String,
|
||||
@Path("eventType") eventType: String,
|
||||
@Body content: String?
|
||||
): Call<SendResponse>
|
||||
|
||||
/**
|
||||
* Get the context surrounding an event.
|
||||
*
|
||||
|
|
|
@ -128,6 +128,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
|
||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
|
||||
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
||||
return sendEvent(localEcho.root)
|
||||
}
|
||||
return null
|
||||
|
@ -282,12 +283,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
return SendEventWorker.Params(sessionId, event)
|
||||
.let { WorkerParamsFactory.toData(it) }
|
||||
.let { timelineSendEventWorkCommon.createWork<SendEventWorker>(it, startChain) }
|
||||
}
|
||||
|
||||
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
||||
return localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason)
|
||||
.also { createLocalEcho(it) }
|
||||
|
|
|
@ -52,7 +52,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var crypto: CryptoService
|
||||
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.v("Start Encrypt work")
|
||||
|
@ -74,7 +74,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
if (localEvent.eventId == null) {
|
||||
return Result.success()
|
||||
}
|
||||
localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
||||
|
||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||
params.keepKeys?.forEach {
|
||||
|
@ -116,7 +116,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||
claimedEd25519Key = crypto.getMyDevice().fingerprint()
|
||||
)
|
||||
localEchoUpdater.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
|
||||
localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
|
||||
}
|
||||
|
||||
val nextWorkerParams = SendEventWorker.Params(params.sessionId, encryptedEvent)
|
||||
|
@ -126,7 +126,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
|
||||
else -> SendState.UNDELIVERED
|
||||
}
|
||||
localEchoUpdater.updateSendState(localEvent.eventId, sendState)
|
||||
localEchoRepository.updateSendState(localEvent.eventId, sendState)
|
||||
// always return success, or the chain will be stuck for ever!
|
||||
val nextWorkerParams = SendEventWorker.Params(params.sessionId, localEvent, error?.localizedMessage
|
||||
?: "Error")
|
||||
|
|
|
@ -30,7 +30,6 @@ import im.vector.matrix.android.api.session.events.model.LocalEcho
|
|||
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.AudioInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.FileInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.ImageInfo
|
||||
|
@ -50,13 +49,13 @@ import im.vector.matrix.android.api.session.room.model.message.OPTION_TYPE_POLL
|
|||
import im.vector.matrix.android.api.session.room.model.message.OptionItem
|
||||
import im.vector.matrix.android.api.session.room.model.message.ThumbnailInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.VideoInfo
|
||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||
import im.vector.matrix.android.api.session.room.model.relation.ReactionContent
|
||||
import im.vector.matrix.android.api.session.room.model.relation.ReactionInfo
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
|
||||
import im.vector.matrix.android.api.session.room.model.relation.ReplyToContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.isReply
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
|
||||
import im.vector.matrix.android.internal.session.room.send.pills.TextPillsUtils
|
||||
|
@ -173,12 +172,13 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
val userLink = originalEvent.root.senderId?.let { PermalinkFactory.createPermalink(it) }
|
||||
?: ""
|
||||
|
||||
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.root.getClearContent().toModel())
|
||||
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply())
|
||||
val replyFormatted = REPLY_PATTERN.format(
|
||||
permalink,
|
||||
userLink,
|
||||
originalEvent.senderInfo.disambiguatedDisplayName,
|
||||
body.takeFormatted(),
|
||||
// Remove inner mx_reply tags if any
|
||||
body.takeFormatted().replace(MX_REPLY_REGEX, ""),
|
||||
createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted()
|
||||
)
|
||||
//
|
||||
|
@ -367,12 +367,13 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
val userId = eventReplied.root.senderId ?: return null
|
||||
val userLink = PermalinkFactory.createPermalink(userId) ?: return null
|
||||
|
||||
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.root.getClearContent().toModel())
|
||||
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply())
|
||||
val replyFormatted = REPLY_PATTERN.format(
|
||||
permalink,
|
||||
userLink,
|
||||
userId,
|
||||
body.takeFormatted(),
|
||||
// Remove inner mx_reply tags if any
|
||||
body.takeFormatted().replace(MX_REPLY_REGEX, ""),
|
||||
createTextContent(replyText, autoMarkdown).takeFormatted()
|
||||
)
|
||||
//
|
||||
|
@ -412,10 +413,10 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
|
||||
/**
|
||||
* Returns a TextContent used for the fallback event representation in a reply message.
|
||||
* We also pass the original content, because in case of an edit of a reply the last content is not
|
||||
* In case of an edit of a reply the last content is not
|
||||
* himself a reply, but it will contain the fallbacks, so we have to trim them.
|
||||
*/
|
||||
private fun bodyForReply(content: MessageContent?, originalContent: MessageContent?): TextContent {
|
||||
private fun bodyForReply(content: MessageContent?, isReply: Boolean): TextContent {
|
||||
when (content?.msgType) {
|
||||
MessageType.MSGTYPE_EMOTE,
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
|
@ -424,7 +425,6 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
if (content is MessageContentWithFormattedBody) {
|
||||
formattedText = content.matrixFormattedBody
|
||||
}
|
||||
val isReply = content.isReply() || originalContent.isReply()
|
||||
return if (isReply) {
|
||||
TextContent(content.body, formattedText).removeInReplyFallbacks()
|
||||
} else {
|
||||
|
@ -485,5 +485,8 @@ internal class LocalEchoEventFactory @Inject constructor(
|
|||
// </mx-reply>
|
||||
// No whitespace because currently breaks temporary formatted text to Span
|
||||
const val REPLY_PATTERN = """<mx-reply><blockquote><a href="%s">In reply to</a> <a href="%s">%s</a><br />%s</blockquote></mx-reply>%s"""
|
||||
|
||||
// This is used to replace inner mx-reply tags
|
||||
val MX_REPLY_REGEX = "<mx-reply>.*</mx-reply>".toRegex()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.matrix.android.internal.session.room.send
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
|
@ -24,7 +25,9 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
|||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.helper.nextId
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||
|
@ -36,8 +39,8 @@ import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
|||
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.room.summary.RoomSummaryUpdater
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
|
||||
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||
import io.realm.Realm
|
||||
|
@ -79,7 +82,33 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
realm.insert(eventInsertEntity)
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync
|
||||
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
|
||||
roomSummaryUpdater.update(realm, roomId)
|
||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSendState(eventId: String, sendState: SendState) {
|
||||
Timber.v("Update local state of $eventId to ${sendState.name}")
|
||||
monarchy.writeAsync { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||
// If already synced, do not put as sent
|
||||
} else {
|
||||
sendingEventEntity.sendState = sendState
|
||||
}
|
||||
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
|
||||
monarchy.writeAsync { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
sendingEventEntity.type = EventType.ENCRYPTED
|
||||
sendingEventEntity.content = ContentMapper.map(encryptedContent)
|
||||
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,16 +116,18 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
monarchy.awaitTransaction { realm ->
|
||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
||||
EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.deleteFromRealm()
|
||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearSendingQueue(roomId: String) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
RoomEntity.where(realm, roomId).findFirst()?.let { room ->
|
||||
room.sendingTimelineEvents.forEach {
|
||||
it.root?.sendState = SendState.UNDELIVERED
|
||||
}
|
||||
}
|
||||
TimelineEventEntity
|
||||
.findAllInRoomWithSendStates(realm, roomId, SendState.IS_SENDING_STATES)
|
||||
.forEach {
|
||||
it.root?.sendState = SendState.UNSENT
|
||||
}
|
||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,6 +137,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
timelineEvents.forEach {
|
||||
it.root?.sendState = sendState
|
||||
}
|
||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.room.send
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class LocalEchoUpdater @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
|
||||
|
||||
fun updateSendState(eventId: String, sendState: SendState) {
|
||||
Timber.v("Update local state of $eventId to ${sendState.name}")
|
||||
monarchy.writeAsync { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||
// If already synced, do not put as sent
|
||||
} else {
|
||||
sendingEventEntity.sendState = sendState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
|
||||
monarchy.writeAsync { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
sendingEventEntity.type = EventType.ENCRYPTED
|
||||
sendingEventEntity.content = ContentMapper.map(encryptedContent)
|
||||
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||
|
||||
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
||||
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
|
||||
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.v("Start dispatch sending multiple event work")
|
||||
|
@ -67,7 +67,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||
|
||||
if (params.lastFailureMessage != null) {
|
||||
params.events.forEach { event ->
|
||||
event.eventId?.let { localEchoUpdater.updateSendState(it, SendState.UNDELIVERED) }
|
||||
event.eventId?.let { localEchoRepository.updateSendState(it, SendState.UNDELIVERED) }
|
||||
}
|
||||
// Transmit the error if needed?
|
||||
return Result.success(inputData)
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.squareup.moshi.JsonClass
|
|||
import im.vector.matrix.android.api.failure.shouldBeRetried
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||
import im.vector.matrix.android.internal.worker.SessionWorkerParams
|
||||
|
@ -32,6 +33,8 @@ import org.greenrobot.eventbus.EventBus
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val MAX_NUMBER_OF_RETRY_BEFORE_FAILING = 3
|
||||
|
||||
/**
|
||||
* Possible previous worker: [EncryptEventWorker] or first worker
|
||||
* Possible next worker : None
|
||||
|
@ -43,11 +46,26 @@ internal class SendEventWorker(context: Context,
|
|||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val event: Event,
|
||||
// TODO remove after some time, it's used for compat
|
||||
val event: Event? = null,
|
||||
val eventId: String? = null,
|
||||
val roomId: String? = null,
|
||||
val type: String? = null,
|
||||
val contentStr: String? = null,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
) : SessionWorkerParams {
|
||||
|
||||
@Inject lateinit var localEchoUpdater: LocalEchoUpdater
|
||||
constructor(sessionId: String, event: Event, lastFailureMessage: String? = null) : this(
|
||||
sessionId = sessionId,
|
||||
eventId = event.eventId,
|
||||
roomId = event.roomId,
|
||||
type = event.type,
|
||||
contentStr = ContentMapper.map(event.content),
|
||||
lastFailureMessage = lastFailureMessage
|
||||
)
|
||||
}
|
||||
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var roomAPI: RoomAPI
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
|
||||
|
@ -58,42 +76,39 @@ internal class SendEventWorker(context: Context,
|
|||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
val event = params.event
|
||||
if (event.eventId == null) {
|
||||
if (params.eventId == null || params.roomId == null || params.type == null) {
|
||||
// compat with old params, make it fail if any
|
||||
if (params.event?.eventId != null) {
|
||||
localEchoRepository.updateSendState(params.event.eventId, SendState.UNDELIVERED)
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED)
|
||||
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
return try {
|
||||
sendEvent(event)
|
||||
sendEvent(params.eventId, params.roomId, params.type, params.contentStr)
|
||||
Result.success()
|
||||
} catch (exception: Throwable) {
|
||||
if (exception.shouldBeRetried()) {
|
||||
Result.retry()
|
||||
// It does start from 0, we want it to stop if it fails the third time
|
||||
val currentAttemptCount = runAttemptCount + 1
|
||||
if (currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING || !exception.shouldBeRetried()) {
|
||||
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||
return Result.success()
|
||||
} else {
|
||||
localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED)
|
||||
// always return success, or the chain will be stuck for ever!
|
||||
Result.success()
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendEvent(event: Event) {
|
||||
localEchoUpdater.updateSendState(event.eventId!!, SendState.SENDING)
|
||||
private suspend fun sendEvent(eventId: String, roomId: String, type: String, contentStr: String?) {
|
||||
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
||||
executeRequest<SendResponse>(eventBus) {
|
||||
apiCall = roomAPI.send(
|
||||
event.eventId,
|
||||
event.roomId!!,
|
||||
event.type,
|
||||
event.content
|
||||
)
|
||||
apiCall = roomAPI.send(eventId, roomId, type, contentStr)
|
||||
}
|
||||
localEchoUpdater.updateSendState(event.eventId, SendState.SENT)
|
||||
localEchoRepository.updateSendState(eventId, SendState.SENT)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
|||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomNameContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.SessionToCryptoRoomMembersUpdate
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
|
@ -34,6 +35,7 @@ import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
|
||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.getOrNull
|
||||
import im.vector.matrix.android.internal.database.query.isEventRead
|
||||
|
@ -75,6 +77,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
EventType.STATE_ROOM_ENCRYPTION,
|
||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
EventType.STICKER,
|
||||
EventType.REACTION,
|
||||
EventType.STATE_ROOM_CREATE
|
||||
)
|
||||
}
|
||||
|
@ -144,6 +147,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
} else if (roomSummaryEntity.membership != Membership.INVITE) {
|
||||
roomSummaryEntity.inviterId = null
|
||||
}
|
||||
roomSummaryEntity.updateHasFailedSending()
|
||||
|
||||
if (latestPreviewableEvent?.root?.type == EventType.ENCRYPTED && latestPreviewableEvent.root?.decryptionResultJson == null) {
|
||||
Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
|
||||
|
@ -166,6 +170,17 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun RoomSummaryEntity.updateHasFailedSending() {
|
||||
hasFailedSending = TimelineEventEntity.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES).isNotEmpty()
|
||||
}
|
||||
|
||||
fun updateSendingInformation(realm: Realm, roomId: String) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
roomSummaryEntity.updateHasFailedSending()
|
||||
roomSummaryEntity.latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
|
||||
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
|
||||
}
|
||||
|
||||
fun updateShieldTrust(realm: Realm,
|
||||
roomId: String,
|
||||
trust: RoomEncryptionTrustLevel?) {
|
||||
|
|
|
@ -169,7 +169,7 @@ internal class DefaultTimeline(
|
|||
filteredEvents = nonFilteredEvents.where()
|
||||
.filterEventsWithSettings()
|
||||
.findAll()
|
||||
filteredEvents.addChangeListener(eventsChangeListener)
|
||||
nonFilteredEvents.addChangeListener(eventsChangeListener)
|
||||
handleInitialLoad()
|
||||
if (settings.shouldHandleHiddenReadReceipts()) {
|
||||
hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this)
|
||||
|
@ -342,21 +342,18 @@ internal class DefaultTimeline(
|
|||
|
||||
private fun updateLoadingStates(results: RealmResults<TimelineEventEntity>) {
|
||||
val lastCacheEvent = results.lastOrNull()
|
||||
val lastBuiltEvent = builtEvents.lastOrNull()
|
||||
val firstCacheEvent = results.firstOrNull()
|
||||
val firstBuiltEvent = builtEvents.firstOrNull()
|
||||
val chunkEntity = getLiveChunk()
|
||||
|
||||
updateState(Timeline.Direction.FORWARDS) {
|
||||
it.copy(
|
||||
hasMoreInCache = firstBuiltEvent != null && firstBuiltEvent.displayIndex < firstCacheEvent?.displayIndex ?: Int.MIN_VALUE,
|
||||
hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId),
|
||||
hasReachedEnd = chunkEntity?.isLastForward ?: false
|
||||
)
|
||||
}
|
||||
|
||||
updateState(Timeline.Direction.BACKWARDS) {
|
||||
it.copy(
|
||||
hasMoreInCache = lastBuiltEvent == null || lastBuiltEvent.displayIndex > lastCacheEvent?.displayIndex ?: Int.MAX_VALUE,
|
||||
hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId),
|
||||
hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE
|
||||
)
|
||||
}
|
||||
|
|
|
@ -116,11 +116,11 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
|
|||
tokenStore.saveToken(realm, syncResponse.nextBatch)
|
||||
}
|
||||
// Everything else we need to do outside the transaction
|
||||
syncResponse.rooms?.also {
|
||||
syncResponse.rooms?.let {
|
||||
checkPushRules(it, isInitialSync)
|
||||
userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite)
|
||||
}
|
||||
syncResponse.groups?.also {
|
||||
syncResponse.groups?.let {
|
||||
scheduleGroupDataFetchingIfNeeded(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,14 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.sync
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.pushrules.RuleScope
|
||||
import im.vector.matrix.android.api.pushrules.RuleSetKey
|
||||
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
@ -37,16 +40,13 @@ import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFie
|
|||
import im.vector.matrix.android.internal.database.query.getDirectRooms
|
||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.DirectMessagesContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IgnoredUsersContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
|
@ -60,25 +60,18 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
@SessionDatabase private val monarchy: Monarchy,
|
||||
@UserId private val userId: String,
|
||||
private val directChatsHelper: DirectChatsHelper,
|
||||
private val moshi: Moshi,
|
||||
private val updateUserAccountDataTask: UpdateUserAccountDataTask) {
|
||||
|
||||
fun handle(realm: Realm, accountData: UserAccountDataSync?) {
|
||||
accountData?.list?.forEach {
|
||||
accountData?.list?.forEach { event ->
|
||||
// Generic handling, just save in base
|
||||
handleGenericAccountData(realm, it.type, it.content)
|
||||
|
||||
// Didn't want to break too much thing, so i re-serialize to jsonString before reparsing
|
||||
// TODO would be better to have a mapper?
|
||||
val toJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJson(it)
|
||||
val model = toJson?.let { json ->
|
||||
MoshiProvider.providesMoshi().adapter(UserAccountData::class.java).fromJson(json)
|
||||
}
|
||||
// Specific parsing
|
||||
when (model) {
|
||||
is UserAccountDataDirectMessages -> handleDirectChatRooms(realm, model)
|
||||
is UserAccountDataPushRules -> handlePushRules(realm, model)
|
||||
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(realm, model)
|
||||
is UserAccountDataBreadcrumbs -> handleBreadcrumbs(realm, model)
|
||||
handleGenericAccountData(realm, event.type, event.content)
|
||||
when (event.type) {
|
||||
UserAccountDataTypes.TYPE_DIRECT_MESSAGES -> handleDirectChatRooms(realm, event)
|
||||
UserAccountDataTypes.TYPE_PUSH_RULES -> handlePushRules(realm, event)
|
||||
UserAccountDataTypes.TYPE_IGNORED_USER_LIST -> handleIgnoredUsers(realm, event)
|
||||
UserAccountDataTypes.TYPE_BREADCRUMBS -> handleBreadcrumbs(realm, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,8 +109,8 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePushRules(realm: Realm, userAccountDataPushRules: UserAccountDataPushRules) {
|
||||
val pushRules = userAccountDataPushRules.content
|
||||
private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) {
|
||||
val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return
|
||||
realm.where(PushRulesEntity::class.java)
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
|
@ -158,13 +151,14 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
realm.insertOrUpdate(underrides)
|
||||
}
|
||||
|
||||
private fun handleDirectChatRooms(realm: Realm, directMessages: UserAccountDataDirectMessages) {
|
||||
private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) {
|
||||
val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
|
||||
oldDirectRooms.forEach {
|
||||
it.isDirect = false
|
||||
it.directUserId = null
|
||||
}
|
||||
directMessages.content.forEach {
|
||||
val content = event.content.toModel<DirectMessagesContent>() ?: return
|
||||
content.forEach {
|
||||
val userId = it.key
|
||||
it.value.forEach { roomId ->
|
||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
|
@ -177,8 +171,8 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleIgnoredUsers(realm: Realm, userAccountDataIgnoredUsers: UserAccountDataIgnoredUsers) {
|
||||
val userIds = userAccountDataIgnoredUsers.content.ignoredUsers.keys
|
||||
private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
|
||||
val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
|
||||
realm.where(IgnoredUserEntity::class.java)
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
|
@ -187,8 +181,8 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
// TODO If not initial sync, we should execute a init sync
|
||||
}
|
||||
|
||||
private fun handleBreadcrumbs(realm: Realm, userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) {
|
||||
val recentRoomIds = userAccountDataBreadcrumbs.content.recentRoomIds
|
||||
private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
|
||||
val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return
|
||||
val entity = BreadcrumbsEntity.getOrCreate(realm)
|
||||
|
||||
// And save the new received list
|
||||
|
|
|
@ -19,12 +19,6 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataAcceptedTerms(
|
||||
@Json(name = "type") override val type: String = TYPE_ACCEPTED_TERMS,
|
||||
@Json(name = "content") val content: AcceptedTermsContent
|
||||
) : UserAccountData()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class AcceptedTermsContent(
|
||||
@Json(name = "accepted") val acceptedTerms: List<String> = emptyList()
|
|
@ -19,12 +19,6 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataBreadcrumbs(
|
||||
@Json(name = "type") override val type: String = TYPE_BREADCRUMBS,
|
||||
@Json(name = "content") val content: BreadcrumbsContent
|
||||
) : UserAccountData()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class BreadcrumbsContent(
|
||||
@Json(name = "recent_rooms") val recentRoomIds: List<String> = emptyList()
|
|
@ -16,12 +16,4 @@
|
|||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UserAccountDataEvent(
|
||||
@Json(name = "type") override val type: String,
|
||||
@Json(name = "content") val content: JsonDict
|
||||
) : UserAccountData()
|
||||
typealias DirectMessagesContent = Map<String, List<String>>
|
|
@ -19,12 +19,6 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataIdentityServer(
|
||||
@Json(name = "type") override val type: String = TYPE_IDENTITY_SERVER,
|
||||
@Json(name = "content") val content: IdentityServerContent? = null
|
||||
) : UserAccountData()
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class IdentityServerContent(
|
||||
@Json(name = "base_url") val baseUrl: String? = null
|
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.api.util.emptyJsonDict
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
@ -26,7 +25,7 @@ internal data class IgnoredUsersContent(
|
|||
/**
|
||||
* Required. The map of users to ignore. UserId -> empty object for future enhancement
|
||||
*/
|
||||
@Json(name = "ignored_users") val ignoredUsers: Map<String, JsonDict>
|
||||
@Json(name = "ignored_users") val ignoredUsers: Map<String, Any>
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataContent
|
||||
|
||||
abstract class UserAccountData : AccountDataContent {
|
||||
|
||||
@Json(name = "type") abstract val type: String
|
||||
|
||||
companion object {
|
||||
const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list"
|
||||
const val TYPE_DIRECT_MESSAGES = "m.direct"
|
||||
const val TYPE_BREADCRUMBS = "im.vector.setting.breadcrumbs" // Was previously "im.vector.riot.breadcrumb_rooms"
|
||||
const val TYPE_PREVIEW_URLS = "org.matrix.preview_urls"
|
||||
const val TYPE_WIDGETS = "m.widgets"
|
||||
const val TYPE_PUSH_RULES = "m.push_rules"
|
||||
const val TYPE_INTEGRATION_PROVISIONING = "im.vector.setting.integration_provisioning"
|
||||
const val TYPE_ALLOWED_WIDGETS = "im.vector.setting.allowed_widgets"
|
||||
const val TYPE_IDENTITY_SERVER = "m.identity_server"
|
||||
const val TYPE_ACCEPTED_TERMS = "m.accepted_terms"
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataDirectMessages(
|
||||
@Json(name = "type") override val type: String = TYPE_DIRECT_MESSAGES,
|
||||
@Json(name = "content") val content: Map<String, List<String>>
|
||||
) : UserAccountData()
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataIgnoredUsers(
|
||||
@Json(name = "type") override val type: String = TYPE_IGNORED_USER_LIST,
|
||||
@Json(name = "content") val content: IgnoredUsersContent
|
||||
) : UserAccountData()
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataPushRules(
|
||||
@Json(name = "type") override val type: String = TYPE_PUSH_RULES,
|
||||
@Json(name = "content") val content: GetPushRulesResponse
|
||||
) : UserAccountData()
|
|
@ -18,9 +18,9 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataSync(
|
||||
@Json(name = "events") val list: List<Event> = emptyList()
|
||||
@Json(name = "events") val list: List<UserAccountDataEvent> = emptyList()
|
||||
)
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/*
|
||||
"m.widgets":{
|
||||
"stickerpicker_@rxl881:matrix.org_1514573757015":{
|
||||
"content":{
|
||||
"creatorUserId":"@rxl881:matrix.org",
|
||||
"data":{
|
||||
"..."
|
||||
},
|
||||
"id":"stickerpicker_@rxl881:matrix.org_1514573757015",
|
||||
"name":"Stickerpicker",
|
||||
"type":"m.stickerpicker",
|
||||
"url":"https://...",
|
||||
"waitForIframeLoad":true
|
||||
},
|
||||
"sender":"@rxl881:matrix.org"
|
||||
"state_key":"stickerpicker_@rxl881:matrix.org_1514573757015",
|
||||
"type":"m.widget"
|
||||
},
|
||||
{
|
||||
"..."
|
||||
}
|
||||
}
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataWidgets(
|
||||
@Json(name = "type") override val type: String = TYPE_WIDGETS,
|
||||
@Json(name = "content") val content: Map<String, Event>
|
||||
) : UserAccountData()
|
|
@ -30,7 +30,7 @@ import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
|||
import im.vector.matrix.android.internal.session.identity.IdentityRegisterTask
|
||||
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
|
@ -109,7 +109,7 @@ internal class DefaultTermsService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getAlreadyAcceptedTermUrlsFromAccountData(): Set<String> {
|
||||
return accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_ACCEPTED_TERMS)
|
||||
return accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ACCEPTED_TERMS)
|
||||
?.content
|
||||
?.toModel<AcceptedTermsContent>()
|
||||
?.acceptedTerms
|
||||
|
|
|
@ -25,7 +25,7 @@ import im.vector.matrix.android.internal.database.mapper.AccountDataMapper
|
|||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
|
|
@ -25,7 +25,7 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import javax.inject.Inject
|
||||
|
|
|
@ -22,7 +22,7 @@ import im.vector.matrix.android.internal.di.SessionDatabase
|
|||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IgnoredUsersContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
@ -64,7 +64,7 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
|
|||
val body = IgnoredUsersContent.createWithUserIds(list)
|
||||
|
||||
executeRequest<Unit>(eventBus) {
|
||||
apiCall = accountDataApi.setAccountData(userId, UserAccountData.TYPE_IGNORED_USER_LIST, body)
|
||||
apiCall = accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body)
|
||||
}
|
||||
|
||||
// Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update)
|
||||
|
|
|
@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.session.integrationmanager.IntegrationP
|
|||
import im.vector.matrix.android.internal.session.sync.model.accountdata.AcceptedTermsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.IdentityServerContent
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
@ -35,7 +35,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
fun getData(): Any
|
||||
}
|
||||
|
||||
data class IdentityParams(override val type: String = UserAccountData.TYPE_IDENTITY_SERVER,
|
||||
data class IdentityParams(override val type: String = UserAccountDataTypes.TYPE_IDENTITY_SERVER,
|
||||
private val identityContent: IdentityServerContent
|
||||
) : Params {
|
||||
|
||||
|
@ -44,7 +44,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class AcceptedTermsParams(override val type: String = UserAccountData.TYPE_ACCEPTED_TERMS,
|
||||
data class AcceptedTermsParams(override val type: String = UserAccountDataTypes.TYPE_ACCEPTED_TERMS,
|
||||
private val acceptedTermsContent: AcceptedTermsContent
|
||||
) : Params {
|
||||
|
||||
|
@ -54,7 +54,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
|
||||
// TODO Use [UserAccountDataDirectMessages] class?
|
||||
data class DirectChatParams(override val type: String = UserAccountData.TYPE_DIRECT_MESSAGES,
|
||||
data class DirectChatParams(override val type: String = UserAccountDataTypes.TYPE_DIRECT_MESSAGES,
|
||||
private val directMessages: Map<String, List<String>>
|
||||
) : Params {
|
||||
|
||||
|
@ -63,7 +63,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class BreadcrumbsParams(override val type: String = UserAccountData.TYPE_BREADCRUMBS,
|
||||
data class BreadcrumbsParams(override val type: String = UserAccountDataTypes.TYPE_BREADCRUMBS,
|
||||
private val breadcrumbsContent: BreadcrumbsContent
|
||||
) : Params {
|
||||
|
||||
|
@ -72,7 +72,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class AllowedWidgets(override val type: String = UserAccountData.TYPE_ALLOWED_WIDGETS,
|
||||
data class AllowedWidgets(override val type: String = UserAccountDataTypes.TYPE_ALLOWED_WIDGETS,
|
||||
private val allowedWidgetsContent: AllowedWidgetsContent) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
|
@ -80,7 +80,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
|
|||
}
|
||||
}
|
||||
|
||||
data class IntegrationProvisioning(override val type: String = UserAccountData.TYPE_INTEGRATION_PROVISIONING,
|
||||
data class IntegrationProvisioning(override val type: String = UserAccountDataTypes.TYPE_INTEGRATION_PROVISIONING,
|
||||
private val integrationProvisioningContent: IntegrationProvisioningContent) : Params {
|
||||
|
||||
override fun getData(): Any {
|
||||
|
|
|
@ -23,6 +23,8 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.Transformations
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataTypes
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -38,8 +40,6 @@ import im.vector.matrix.android.internal.session.SessionLifecycleObserver
|
|||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationManager
|
||||
import im.vector.matrix.android.internal.session.room.state.StateEventDataSource
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataDataSource
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.WidgetFactory
|
||||
import im.vector.matrix.android.internal.session.widgets.helper.extractWidgetSequence
|
||||
|
@ -136,7 +136,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): LiveData<List<Widget>> {
|
||||
val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountData.TYPE_WIDGETS)
|
||||
val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS)
|
||||
return Transformations.map(widgetsAccountData) {
|
||||
it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes) ?: emptyList()
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
widgetTypes: Set<String>? = null,
|
||||
excludedTypes: Set<String>? = null
|
||||
): List<Widget> {
|
||||
val widgetsAccountData = accountDataDataSource.getAccountDataEvent(UserAccountData.TYPE_WIDGETS) ?: return emptyList()
|
||||
val widgetsAccountData = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList()
|
||||
return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.session.widgets.helper
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.accountdata.UserAccountDataEvent
|
||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||
|
||||
internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> {
|
||||
|
|
|
@ -17,7 +17,7 @@ androidExtensions {
|
|||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 1
|
||||
ext.versionMinor = 0
|
||||
ext.versionPatch = 0
|
||||
ext.versionPatch = 3
|
||||
ext.scVersion = 10
|
||||
|
||||
static def getGitTimestamp() {
|
||||
|
|
|
@ -79,8 +79,8 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="riotx" />
|
||||
<data android:host="riotx" />
|
||||
<data android:scheme="element" />
|
||||
<data android:host="element" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".features.media.ImageMediaViewerActivity" />
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<p>RiotX Android</p>
|
||||
<p>Element Android</p>
|
||||
<p>Third Party Licenses</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
|||
import im.vector.riotx.features.roommemberprofile.devices.DeviceListFragment
|
||||
import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionFragment
|
||||
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
||||
import im.vector.riotx.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
||||
import im.vector.riotx.features.roomprofile.uploads.RoomUploadsFragment
|
||||
|
@ -534,4 +535,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(ContactsBookFragment::class)
|
||||
fun bindPhoneBookFragment(fragment: ContactsBookFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomBannedMemberListFragment::class)
|
||||
fun bindRoomBannedMemberListFragment(fragment: RoomBannedMemberListFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package im.vector.riotx.core.epoxy
|
||||
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
|
@ -26,14 +28,16 @@ import im.vector.riotx.core.extensions.setTextOrHide
|
|||
abstract class LoadingItem : VectorEpoxyModel<LoadingItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute var loadingText: String? = null
|
||||
@EpoxyAttribute var showLoader: Boolean = true
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
holder.progressBar.isVisible = showLoader
|
||||
holder.textView.setTextOrHide(loadingText)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val textView by bind<TextView>(R.id.loadingText)
|
||||
val progressBar by bind<ProgressBar>(R.id.loadingProgress)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.epoxy.profiles
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.features.crypto.util.toImageRes
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
|
||||
abstract class BaseProfileMatrixItem<T : ProfileMatrixItem.Holder> : VectorEpoxyModel<T>() {
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var editable: Boolean = true
|
||||
@EpoxyAttribute
|
||||
var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: T) {
|
||||
super.bind(holder)
|
||||
val bestName = matrixItem.getBestName()
|
||||
val matrixId = matrixItem.id
|
||||
.takeIf { it != bestName }
|
||||
// Special case for ThreePid fake matrix item
|
||||
.takeIf { it != "@" }
|
||||
holder.view.setOnClickListener(clickListener?.takeIf { editable })
|
||||
holder.titleView.text = bestName
|
||||
holder.subtitleView.setTextOrHide(matrixId)
|
||||
holder.editableView.isVisible = editable
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes())
|
||||
}
|
||||
}
|
|
@ -20,43 +20,14 @@ package im.vector.riotx.core.epoxy.profiles
|
|||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.features.crypto.util.toImageRes
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
|
||||
abstract class ProfileMatrixItem : VectorEpoxyModel<ProfileMatrixItem.Holder>() {
|
||||
abstract class ProfileMatrixItem : BaseProfileMatrixItem<ProfileMatrixItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var editable: Boolean = true
|
||||
@EpoxyAttribute var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
|
||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
val bestName = matrixItem.getBestName()
|
||||
val matrixId = matrixItem.id
|
||||
.takeIf { it != bestName }
|
||||
// Special case for ThreePid fake matrix item
|
||||
.takeIf { it != "@" }
|
||||
holder.view.setOnClickListener(clickListener?.takeIf { editable })
|
||||
holder.titleView.text = bestName
|
||||
holder.subtitleView.setTextOrHide(matrixId)
|
||||
holder.editableView.isVisible = editable
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes())
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
open class Holder : VectorEpoxyHolder() {
|
||||
val titleView by bind<TextView>(R.id.matrixItemTitle)
|
||||
val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
|
||||
val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.epoxy.profiles
|
||||
|
||||
import android.widget.ProgressBar
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.riotx.R
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item_progress)
|
||||
abstract class ProfileMatrixItemWithProgress : BaseProfileMatrixItem<ProfileMatrixItemWithProgress.Holder>() {
|
||||
|
||||
@EpoxyAttribute var inProgress: Boolean = true
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.progress.isVisible = inProgress
|
||||
}
|
||||
|
||||
class Holder : ProfileMatrixItem.Holder() {
|
||||
val progress by bind<ProgressBar>(R.id.matrixItemProgress)
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
stringProvider.getString(R.string.login_error_unknown_host)
|
||||
is SSLPeerUnverifiedException ->
|
||||
stringProvider.getString(R.string.login_error_ssl_peer_unverified)
|
||||
is SSLException ->
|
||||
is SSLException ->
|
||||
stringProvider.getString(R.string.login_error_ssl_other)
|
||||
else ->
|
||||
stringProvider.getString(R.string.error_no_network)
|
||||
|
@ -84,6 +84,9 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
|
||||
stringProvider.getString(R.string.login_reset_password_error_not_found)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
|
||||
stringProvider.getString(R.string.auth_invalid_login_deactivated_account)
|
||||
}
|
||||
else -> {
|
||||
throwable.error.message.takeIf { it.isNotEmpty() }
|
||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||
|
|
|
@ -16,11 +16,18 @@
|
|||
|
||||
package im.vector.riotx.core.preference
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.ArgbEvaluator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import androidx.preference.SwitchPreference
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
|
||||
/**
|
||||
* Switch preference with title on multiline (only used in XML)
|
||||
|
@ -41,10 +48,49 @@ class VectorSwitchPreference : SwitchPreference {
|
|||
isIconSpaceReserved = true
|
||||
}
|
||||
|
||||
var isHighlighted = false
|
||||
set(value) {
|
||||
field = value
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
var currentHighlightAnimator: Animator? = null
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
// display the title in multi-line to avoid ellipsis.
|
||||
holder.itemView.findViewById<TextView>(android.R.id.title)?.isSingleLine = false
|
||||
|
||||
// cancel existing animation (find a way to resume if happens during anim?)
|
||||
currentHighlightAnimator?.cancel()
|
||||
|
||||
val itemView = holder.itemView
|
||||
if (isHighlighted) {
|
||||
val colorFrom = Color.TRANSPARENT
|
||||
val colorTo = ThemeUtils.getColor(itemView.context, R.attr.colorControlHighlight)
|
||||
currentHighlightAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo).apply {
|
||||
duration = 250 // milliseconds
|
||||
addUpdateListener { animator ->
|
||||
itemView.setBackgroundColor(animator.animatedValue as Int)
|
||||
}
|
||||
doOnEnd {
|
||||
currentHighlightAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorTo, colorFrom).apply {
|
||||
duration = 250 // milliseconds
|
||||
addUpdateListener { animator ->
|
||||
itemView.setBackgroundColor(animator.animatedValue as Int)
|
||||
}
|
||||
doOnEnd {
|
||||
isHighlighted = false
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
startDelay = 200
|
||||
start()
|
||||
}
|
||||
} else {
|
||||
itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
super.onBindViewHolder(holder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class AppNameProvider @Inject constructor(private val context: Context) {
|
|||
return appName
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## AppNameProvider() : failed")
|
||||
return "RiotXAndroid"
|
||||
return "ElementAndroid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ import im.vector.riotx.core.services.CallService
|
|||
connection.connectionCapabilities = Connection.CAPABILITY_MUTE
|
||||
connection.audioModeIsVoip = true
|
||||
connection.setAddress(Uri.fromParts("tel", "+905000000000", null), TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.setCallerDisplayName("RiotX Caller", TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.setCallerDisplayName("Element Caller", TelecomManager.PRESENTATION_ALLOWED)
|
||||
connection.statusHints = StatusHints("Testing Hint...", null, null)
|
||||
|
||||
bindService(Intent(applicationContext, CallService::class.java), CallServiceConnection(connection), 0)
|
||||
|
|
|
@ -38,6 +38,14 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
|||
fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
setState {
|
||||
copy(
|
||||
hsAdminHasDisabledE2E = !session.getHomeServerCapabilities().adminE2EByDefault
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<CreateDirectRoomViewModel, CreateDirectRoomViewState> {
|
||||
|
||||
@JvmStatic
|
||||
|
@ -63,7 +71,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
|||
}.exhaustive
|
||||
}
|
||||
setDirectMessage()
|
||||
enableEncryptionIfInvitedUsersSupportIt = true
|
||||
enableEncryptionIfInvitedUsersSupportIt = session.getHomeServerCapabilities().adminE2EByDefault
|
||||
}
|
||||
|
||||
session.rx()
|
||||
|
|
|
@ -21,5 +21,6 @@ import com.airbnb.mvrx.MvRxState
|
|||
import com.airbnb.mvrx.Uninitialized
|
||||
|
||||
data class CreateDirectRoomViewState(
|
||||
val createAndInviteState: Async<String> = Uninitialized
|
||||
val createAndInviteState: Async<String> = Uninitialized,
|
||||
val hsAdminHasDisabledE2E: Boolean = false
|
||||
) : MvRxState
|
||||
|
|
|
@ -282,8 +282,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? {
|
||||
val activity: SharedSecureStorageActivity = viewModelContext.activity()
|
||||
val args: SharedSecureStorageActivity.Args? = activity.intent.getParcelableExtra(MvRx.KEY_ARG)
|
||||
return args?.let { activity.viewModelFactory.create(state, it) }
|
||||
val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(MvRx.KEY_ARG) ?: error("Missing args")
|
||||
return activity.viewModelFactory.create(state, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,13 @@ import im.vector.riotx.core.pushers.PushersManager
|
|||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.features.disclaimer.showDisclaimerDialog
|
||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.popup.DefaultVectorAlert
|
||||
import im.vector.riotx.features.popup.PopupAlertManager
|
||||
import im.vector.riotx.features.popup.VerificationVectorAlert
|
||||
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import im.vector.riotx.features.workers.signout.ServerBackupStatusViewModel
|
||||
import im.vector.riotx.features.workers.signout.ServerBackupStatusViewState
|
||||
import im.vector.riotx.push.fcm.FcmHelper
|
||||
|
@ -70,7 +73,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
|
|||
@Inject lateinit var viewModelFactory: HomeActivityViewModel.Factory
|
||||
|
||||
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
|
||||
@Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory
|
||||
@Inject lateinit var serverBackupviewModelFactory: ServerBackupStatusViewModel.Factory
|
||||
|
||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
||||
|
@ -135,6 +138,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
|
|||
when (it) {
|
||||
is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
|
||||
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
|
||||
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
|
||||
}.exhaustive
|
||||
}
|
||||
homeActivityViewModel.subscribe(this) { renderState(it) }
|
||||
|
@ -194,6 +198,42 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
|
|||
}
|
||||
}
|
||||
|
||||
private fun handlePromptToEnablePush() {
|
||||
popupAlertManager.postVectorAlert(
|
||||
DefaultVectorAlert(
|
||||
uid = "enablePush",
|
||||
title = getString(R.string.alert_push_are_disabled_title),
|
||||
description = getString(R.string.alert_push_are_disabled_description),
|
||||
iconId = R.drawable.ic_room_actions_notifications_mutes,
|
||||
shouldBeDisplayedIn = {
|
||||
it is HomeActivity
|
||||
}
|
||||
).apply {
|
||||
colorInt = ThemeUtils.getColor(this@HomeActivity, R.attr.vctr_notice_secondary)
|
||||
contentAction = Runnable {
|
||||
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
|
||||
// action(it)
|
||||
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
|
||||
it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
dismissedAction = Runnable {
|
||||
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
|
||||
}
|
||||
addButton(getString(R.string.dismiss), Runnable {
|
||||
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
|
||||
}, true)
|
||||
addButton(getString(R.string.settings), Runnable {
|
||||
(weakCurrentActivity?.get() as? VectorBaseActivity)?.let {
|
||||
// action(it)
|
||||
homeActivityViewModel.handle(HomeActivityViewActions.PushPromptHasBeenReviewed)
|
||||
it.navigator.openSettings(it, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_NOTIFICATIONS)
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun promptSecurityEvent(userItem: MatrixItem.UserItem?, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) {
|
||||
popupAlertManager.postVectorAlert(
|
||||
VerificationVectorAlert(
|
||||
|
|
|
@ -14,14 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync.model.accountdata
|
||||
package im.vector.riotx.features.home
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.session.integrationmanager.IntegrationProvisioningContent
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class UserAccountDataIntegrationProvisioning(
|
||||
@Json(name = "type") override val type: String = TYPE_INTEGRATION_PROVISIONING,
|
||||
@Json(name = "content") val content: IntegrationProvisioningContent
|
||||
) : UserAccountData()
|
||||
sealed class HomeActivityViewActions : VectorViewModelAction {
|
||||
object PushPromptHasBeenReviewed : HomeActivityViewActions()
|
||||
}
|
|
@ -22,4 +22,5 @@ import im.vector.riotx.core.platform.VectorViewEvents
|
|||
sealed class HomeActivityViewEvents : VectorViewEvents {
|
||||
data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents()
|
||||
data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents()
|
||||
object PromptToEnableSessionPush : HomeActivityViewEvents()
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.home
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
|
@ -23,24 +24,32 @@ import com.squareup.inject.assisted.Assisted
|
|||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||
import im.vector.matrix.android.api.pushrules.RuleIds
|
||||
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
||||
import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||
import im.vector.matrix.rx.asObservable
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.login.ReAuthHelper
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class HomeActivityViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: HomeActivityViewState,
|
||||
@Assisted private val args: HomeActivityArgs,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val reAuthHelper: ReAuthHelper
|
||||
) : VectorViewModel<HomeActivityViewState, EmptyAction, HomeActivityViewEvents>(initialState) {
|
||||
private val reAuthHelper: ReAuthHelper,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -53,7 +62,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
override fun create(viewModelContext: ViewModelContext, state: HomeActivityViewState): HomeActivityViewModel? {
|
||||
val activity: HomeActivity = viewModelContext.activity()
|
||||
val args: HomeActivityArgs? = activity.intent.getParcelableExtra(MvRx.KEY_ARG)
|
||||
return args?.let { activity.viewModelFactory.create(state, it) }
|
||||
return activity.viewModelFactory.create(state, args ?: HomeActivityArgs(clearNotification = false, accountCreation = false))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +71,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
init {
|
||||
observeInitialSync()
|
||||
mayBeInitializeCrossSigning()
|
||||
checkSessionPushIsOn()
|
||||
}
|
||||
|
||||
private fun observeInitialSync() {
|
||||
|
@ -115,6 +125,41 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After migration from riot to element some users reported that their
|
||||
* push setting for the session was set to off
|
||||
* In order to mitigate this, we want to display a popup once to the user
|
||||
* giving him the option to review this setting
|
||||
*/
|
||||
private fun checkSessionPushIsOn() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// Don't do that if it's a login or a register (pass in memory)
|
||||
if (reAuthHelper.data != null) return@launch
|
||||
// Check if disabled for this device
|
||||
if (!vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
// Check if set at account level
|
||||
val mRuleMaster = activeSessionHolder.getSafeActiveSession()
|
||||
?.getPushRules()
|
||||
?.getAllRules()
|
||||
?.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
|
||||
if (mRuleMaster?.enabled == false) {
|
||||
// So push are enabled at account level but not for this session
|
||||
// Let's check that there are some rooms?
|
||||
val knownRooms = activeSessionHolder.getSafeActiveSession()?.getRoomSummaries(roomSummaryQueryParams {
|
||||
memberships = Membership.activeMemberships()
|
||||
})?.size ?: 0
|
||||
|
||||
// Prompt once to the user
|
||||
if (knownRooms > 1 && !vectorPreferences.didAskUserToEnableSessionPush()) {
|
||||
// delay a bit
|
||||
delay(1500)
|
||||
_viewEvents.post(HomeActivityViewEvents.PromptToEnableSessionPush)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeBootstrapCrossSigning() {
|
||||
// In case of account creation, it is already done before
|
||||
if (args.accountCreation) return
|
||||
|
@ -167,7 +212,11 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
// NA
|
||||
override fun handle(action: HomeActivityViewActions) {
|
||||
when (action) {
|
||||
HomeActivityViewActions.PushPromptHasBeenReviewed -> {
|
||||
vectorPreferences.setDidAskUserToEnableSessionPush()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
|
|
@ -415,11 +415,11 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
R.id.clear_message_queue ->
|
||||
// For now always disable when not in developer mode, worker cancellation is not working properly
|
||||
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
||||
R.id.resend_all -> timeline.failedToDeliverEventCount() > 0
|
||||
R.id.clear_all -> timeline.failedToDeliverEventCount() > 0
|
||||
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||
R.id.open_matrix_apps -> session.integrationManagerService().isIntegrationEnabled()
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> room.canStartCall() && webRtcPeerConnectionManager.currentCall == null
|
||||
R.id.video_call -> state.asyncRoomSummary()?.canStartCall == true && webRtcPeerConnectionManager.currentCall == null
|
||||
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
||||
R.id.show_room_info -> true
|
||||
R.id.show_participants -> true
|
||||
|
|
|
@ -74,7 +74,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
||||
fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View)
|
||||
fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
|
||||
// fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
|
||||
|
||||
// fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent)
|
||||
// fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
|
||||
fun onEditedDecorationClicked(informationData: MessageInformationData)
|
||||
|
||||
|
@ -107,7 +108,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
fun onUrlLongClicked(url: String): Boolean
|
||||
}
|
||||
|
||||
private var showingForwardLoader = false
|
||||
// Map eventId to adapter position
|
||||
private val adapterPositionMapping = HashMap<String, Int>()
|
||||
private val modelCache = arrayListOf<CacheItemData?>()
|
||||
|
@ -233,7 +233,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
|
||||
override fun buildModels() {
|
||||
val timestamp = System.currentTimeMillis()
|
||||
showingForwardLoader = LoadingItem_()
|
||||
|
||||
val showingForwardLoader = LoadingItem_()
|
||||
.id("forward_loading_item_$timestamp")
|
||||
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
|
||||
.addWhenLoading(Timeline.Direction.FORWARDS)
|
||||
|
@ -242,12 +243,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
add(timelineModels)
|
||||
|
||||
// Avoid displaying two loaders if there is no elements between them
|
||||
if (!showingForwardLoader || timelineModels.isNotEmpty()) {
|
||||
LoadingItem_()
|
||||
.id("backward_loading_item_$timestamp")
|
||||
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS)
|
||||
.addWhenLoading(Timeline.Direction.BACKWARDS)
|
||||
}
|
||||
val showBackwardsLoader = !showingForwardLoader || timelineModels.isNotEmpty()
|
||||
// We can hide the loader but still add the item to controller so it can trigger backwards pagination
|
||||
LoadingItem_()
|
||||
.id("backward_loading_item_$timestamp")
|
||||
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS)
|
||||
.showLoader(showBackwardsLoader)
|
||||
.addWhenLoading(Timeline.Direction.BACKWARDS)
|
||||
}
|
||||
|
||||
// Timeline.LISTENER ***************************************************************************
|
||||
|
|
|
@ -69,7 +69,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
|||
id("send_state")
|
||||
showProgress(false)
|
||||
text(stringProvider.getString(R.string.unable_to_send_message))
|
||||
drawableStart(R.drawable.ic_warning_small)
|
||||
drawableStart(R.drawable.ic_warning_badge)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,9 +30,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||
import im.vector.matrix.android.api.session.events.model.isReply
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
|
@ -113,7 +111,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
|||
}
|
||||
|
||||
if (event.eventId == it.eventId) {
|
||||
originalIsReply = it.getClearContent().toModel<MessageContent>().isReply()
|
||||
originalIsReply = it.isReply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -354,16 +354,22 @@ class MessageItemFactory @Inject constructor(
|
|||
when (codeVisitor.codeKind) {
|
||||
CodeVisitor.Kind.BLOCK -> {
|
||||
val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody)
|
||||
buildCodeBlockItem(codeFormattedBlock, informationData, highlight, callback, attributes)
|
||||
if (codeFormattedBlock == null) {
|
||||
buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
|
||||
} else {
|
||||
buildCodeBlockItem(codeFormattedBlock, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
CodeVisitor.Kind.INLINE -> {
|
||||
val codeFormatted = htmlRenderer.get().render(localFormattedBody)
|
||||
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
|
||||
if (codeFormatted == null) {
|
||||
buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
|
||||
} else {
|
||||
buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
CodeVisitor.Kind.NONE -> {
|
||||
val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
|
||||
val formattedBody = htmlRenderer.get().render(compressed)
|
||||
buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
||||
buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -371,6 +377,16 @@ class MessageItemFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildFormattedTextItem(messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||
val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
|
||||
val formattedBody = htmlRenderer.get().render(compressed)
|
||||
return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
||||
}
|
||||
|
||||
private fun buildMessageTextItem(body: CharSequence,
|
||||
isFormatted: Boolean,
|
||||
informationData: MessageInformationData,
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
package im.vector.riotx.features.home.room.detail.timeline.format
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||
import im.vector.matrix.android.api.session.room.model.relation.ReactionContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.isReply
|
||||
import im.vector.riotx.EmojiCompatWrapper
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
|
@ -31,6 +34,7 @@ import javax.inject.Inject
|
|||
class DisplayableEventFormatter @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val emojiCompatWrapper: EmojiCompatWrapper,
|
||||
private val noticeEventFormatter: NoticeEventFormatter
|
||||
) {
|
||||
|
||||
|
@ -47,10 +51,16 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
||||
|
||||
when (timelineEvent.root.getClearType()) {
|
||||
EventType.STICKER -> {
|
||||
EventType.STICKER -> {
|
||||
return simpleFormat(senderName, stringProvider.getString(R.string.send_a_sticker), appendAuthor)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
EventType.REACTION -> {
|
||||
timelineEvent.root.getClearContent().toModel<ReactionContent>()?.relatesTo?.let {
|
||||
val emojiSpanned = emojiCompatWrapper.safeEmojiSpanify(it.key)
|
||||
return simpleFormat(senderName, emojiSpanned, appendAuthor)
|
||||
}
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
||||
when (messageContent.msgType) {
|
||||
MessageType.MSGTYPE_VERIFICATION_REQUEST -> {
|
||||
|
@ -69,13 +79,13 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
return simpleFormat(senderName, stringProvider.getString(R.string.sent_a_file), appendAuthor)
|
||||
}
|
||||
MessageType.MSGTYPE_TEXT -> {
|
||||
if (messageContent.isReply()) {
|
||||
return if (timelineEvent.isReply()) {
|
||||
// Skip reply prefix, and show important
|
||||
// TODO add a reply image span ?
|
||||
return simpleFormat(senderName, timelineEvent.getTextEditableContent()
|
||||
simpleFormat(senderName, timelineEvent.getTextEditableContent()
|
||||
?: messageContent.body, appendAuthor)
|
||||
} else {
|
||||
return simpleFormat(senderName, messageContent.body, appendAuthor)
|
||||
simpleFormat(senderName, messageContent.body, appendAuthor)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
@ -84,7 +94,7 @@ class DisplayableEventFormatter @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
return span {
|
||||
text = noticeEventFormatter.format(timelineEvent) ?: ""
|
||||
textStyle = "italic"
|
||||
|
|
|
@ -53,6 +53,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
|||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||
@EpoxyAttribute var hasDraft: Boolean = false
|
||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||
@EpoxyAttribute var hasFailedSending: Boolean = false
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
|
||||
@EpoxyAttribute var showSelected: Boolean = false
|
||||
|
@ -73,6 +74,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
|||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null
|
||||
holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes())
|
||||
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
|
||||
renderSelection(holder, showSelected)
|
||||
holder.typingView.setTextOrHide(typingMessage)
|
||||
holder.lastEventView.isInvisible = holder.typingView.isVisible
|
||||
|
@ -107,6 +109,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
|
|||
val avatarCheckedImageView by bind<ImageView>(R.id.roomAvatarCheckedImageView)
|
||||
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||
val roomAvatarDecorationImageView by bind<ImageView>(R.id.roomAvatarDecorationImageView)
|
||||
val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView)
|
||||
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
|||
.lastFormattedEvent(latestFormattedEvent)
|
||||
.showHighlighted(showHighlighted)
|
||||
.showSelected(showSelected)
|
||||
.hasFailedSending(roomSummary.hasFailedSending)
|
||||
.unreadNotificationCount(unreadCount)
|
||||
.hasUnreadMessage(roomSummary.hasUnreadMessages)
|
||||
.hasDraft(roomSummary.userDrafts.isNotEmpty())
|
||||
|
|
|
@ -25,6 +25,7 @@ import io.noties.markwon.Markwon
|
|||
import io.noties.markwon.html.HtmlPlugin
|
||||
import io.noties.markwon.html.TagHandlerNoOp
|
||||
import org.commonmark.node.Node
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -41,11 +42,21 @@ class EventHtmlRenderer @Inject constructor(context: Context,
|
|||
}
|
||||
|
||||
fun render(text: String): CharSequence {
|
||||
return markwon.toMarkdown(text)
|
||||
return try {
|
||||
markwon.toMarkdown(text)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.v("Fail to render $text to html")
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
fun render(node: Node): CharSequence {
|
||||
return markwon.render(node)
|
||||
fun render(node: Node): CharSequence? {
|
||||
return try {
|
||||
markwon.render(node)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.v("Fail to render $node to html")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.login
|
||||
|
||||
const val MODULAR_LINK = "https://modular.im/services/matrix-hosting-riot" +
|
||||
"?utm_source=riot-x-android" +
|
||||
const val EMS_LINK = "https://element.io/matrix-services" +
|
||||
"?utm_source=element-android" +
|
||||
"&utm_medium=native" +
|
||||
"&utm_campaign=riot-x-android-authentication"
|
||||
"&utm_campaign=element-android-authentication"
|
||||
|
|
|
@ -19,12 +19,10 @@ package im.vector.riotx.features.login
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
|
@ -73,14 +71,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
get() = supportFragmentManager.findFragmentById(R.id.loginFragmentContainer)
|
||||
|
||||
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
|
||||
// Find the loginLogo on the current Fragment, this should not return null
|
||||
(topFragment?.view as? ViewGroup)
|
||||
// Find findViewById does not work, I do not know why
|
||||
// findViewById<View?>(R.id.loginLogo)
|
||||
?.children
|
||||
?.firstOrNull { it.id == R.id.loginLogo }
|
||||
?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
// TODO
|
||||
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||
}
|
||||
|
||||
|
@ -145,7 +135,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerSelectionFragment::class.java,
|
||||
option = { ft ->
|
||||
findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
// TODO Disabled because it provokes a flickering
|
||||
|
@ -231,7 +220,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) {
|
||||
when (loginViewEvents.serverType) {
|
||||
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
|
||||
ServerType.Modular,
|
||||
ServerType.EMS,
|
||||
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerUrlFormFragment::class.java,
|
||||
option = commonOption)
|
||||
|
|
|
@ -155,7 +155,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
|||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
||||
}
|
||||
ServerType.Modular -> {
|
||||
ServerType.EMS -> {
|
||||
loginServerIcon.isVisible = true
|
||||
loginServerIcon.setImageResource(R.drawable.ic_logo_element_matrix_services)
|
||||
loginTitle.text = getString(resId, "Element Matrix Services")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue