1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 00:02:48 +02:00

Compare commits

...

168 Commits

Author SHA1 Message Date
Valere
ee1d5faf0d Merge branch 'hotfix/fix_crash_invite_user' 2020-08-03 14:01:27 +02:00
Valere
7308c4a15c Merge branch 'hotfix/fix_crash_before_release' 2020-08-03 12:23:55 +02:00
Valere
db6045778e Update change log 2020-08-03 12:11:50 +02:00
Valere
2810923519 FIx / Crash when opening Invite User screen 2020-08-03 12:00:08 +02:00
Valere
7fddfa45e8 Merge branch 'release/1.0.3' 2020-07-31 00:02:02 +02:00
Valere
258acaf814 Prepare release 1.0.3 2020-07-31 00:01:33 +02:00
Valere
2f6cdc6529 Merge pull request #1847 from vector-im/feature/crash_store_mvrx
Fix IllegalArgumentException reported from store on HomeActivity aunch
2020-07-30 18:07:11 +02:00
Valere
1f226ca6c7 Merge pull request #1845 from vector-im/feature/admin_e2e_config
Feature/admin e2e config
2020-07-30 18:06:27 +02:00
Valere
c90f5ed370 Fix IllegalArgumentException reported from store on HomeActivity aunch 2020-07-30 17:03:11 +02:00
Valere
ccb466edbe Update change log 2020-07-30 16:57:25 +02:00
Valere
1204f6d9ce UX feedback, reduce severity of message 2020-07-30 15:25:10 +02:00
Valere
562cfce9e2 Support HS admin option to disable E2EE for DMs
Fixes #1794
2020-07-30 12:08:30 +02:00
Valere
b732ea6c69 Version ++ 2020-07-29 11:39:23 +02:00
Valere
a048f79b37 Merge branch 'release/1.0.2' 2020-07-29 10:46:54 +02:00
Valere
3214f7d3d5 Merge branch 'release/1.0.2' into develop 2020-07-29 10:46:54 +02:00
Valere
06ef665f66 prepare release 1.0.2 2020-07-29 10:33:55 +02:00
Valere
e1a07f1da6 Merge pull request #1838 from vector-im/feature/session_store_migration
Feature/session store migration
2020-07-29 10:31:14 +02:00
Valere
5c32c7388a cleaning 2020-07-29 09:51:50 +02:00
Valere
c04f22d3bf quick fix plural 2020-07-29 09:51:20 +02:00
Valere
cbf43ea7b3 Session store migration 2020-07-29 09:50:12 +02:00
Valere
9197275343 version++ 2020-07-29 09:00:29 +02:00
Valere
cc4298209b Merge branch 'release/1.0.1' 2020-07-28 18:03:37 +02:00
Valere
f26ce64914 Merge branch 'release/1.0.1' into develop 2020-07-28 18:03:36 +02:00
Valere
e3f5b7cab3 Prepare release 1.0.1 2020-07-28 18:02:51 +02:00
Valere
dde9cdd8ac Merge pull request #1821 from vector-im/feature/notification_off_alert
Check if migration disabled notif
2020-07-28 17:04:15 +02:00
Valere
0d0308d584 Merge pull request #1802 from vector-im/feature/fix_reply_tag
Feature/fix reply tag
2020-07-28 16:58:10 +02:00
Valere
a47ff99be7 Merge branch 'develop' into feature/fix_reply_tag 2020-07-28 16:58:00 +02:00
Valere
e619217687 Code quality 2020-07-28 16:56:55 +02:00
Valere
6f5b3371fe Check if migration disabled notif 2020-07-28 16:56:55 +02:00
Valere
f7b9fc3bb1 Merge pull request #1826 from vector-im/feature/banned_list
Added banned user screen
2020-07-28 16:53:58 +02:00
ganfra
700a2e9ce3 Merge pull request #1832 from vector-im/feature/fix_timeline_loading
Fix timeline non loading when there are lots of filtered events
2020-07-28 16:30:57 +02:00
ganfra
5176a3e2aa Merge branch 'develop' into feature/fix_timeline_loading 2020-07-28 16:30:34 +02:00
Valere
4631cd0e80 Merge pull request #1831 from vector-im/feature/quick_call_update
Allow call in all 2 participants rooms
2020-07-28 16:24:22 +02:00
Valere
60c6512418 Fix / item flickering 2020-07-28 16:23:21 +02:00
Valere
12bc821ef3 code review 2020-07-28 16:23:21 +02:00
Valere
c82e910c38 Added banned user screen 2020-07-28 16:23:21 +02:00
ganfra
7ae0620b20 Update CHANGES 2020-07-28 15:09:45 +02:00
Valere
de32cdb703 Merge pull request #1828 from vector-im/feature/improve_edit_role_button
Improve UI of edit role button
2020-07-28 14:46:54 +02:00
Valere
12e2a8ffc8 Allow call in all 2 participants rooms 2020-07-28 14:43:50 +02:00
ganfra
833f64fcec Fix timeline non loading when there are lots of filtered events 2020-07-28 11:49:45 +02:00
Valere
73c051d2b1 Improve UI of edit role button 2020-07-28 11:35:37 +02:00
ganfra
da06695ab7 Relation content should be checked in encrypted content and not the decrypted one 2020-07-24 18:31:01 +02:00
Onuray Sahin
984ffc4fb9 Merge pull request #1797 from vector-im/feature/fix_rebranding_assets
Fix rebranding assets on login screens
2020-07-24 15:16:02 +03:00
ganfra
d7b44ba0cf Merge branch 'develop' into feature/fix_reply_tag 2020-07-24 11:32:59 +02:00
ganfra
e90fe5cf28 Update CHANGES 2020-07-24 11:31:47 +02:00
ganfra
20c7d80bb1 Clean code 2020-07-24 11:30:27 +02:00
ganfra
25f8a9418a Merge pull request #1783 from vector-im/feature/sending_retry
Feature/sending retry
2020-07-24 10:35:24 +02:00
onurays
21b609420b ktlint fix. 2020-07-24 03:00:06 +03:00
ganfra
794b89c041 Reply: remove inner tags when replying 2020-07-23 18:23:49 +02:00
ganfra
407595e613 Change warning badge (white background) 2020-07-23 16:38:42 +02:00
ganfra
cac8099117 Local echo: use missing updateSendingInformation on RoomSummaryUpdater 2020-07-23 16:38:17 +02:00
onurays
4cc3e87d64 Fix rebranding assets on login screens. 2020-07-23 15:02:40 +03:00
ganfra
3e429490e7 Update CHANGES 2020-07-22 17:04:48 +02:00
ganfra
e23b02f2e2 Composer: set max lines to 6 so it can scroll 2020-07-22 16:02:38 +02:00
ganfra
aa5ae45a0c Add hasFailedSending in RoomSummary and a small warning icon on room list 2020-07-22 15:21:48 +02:00
ganfra
d7558902f7 Sending: limit to 3 retry before failing 2020-07-22 11:12:37 +02:00
ganfra
aa5de1896f Merge pull request #1766 from vector-im/feature/fix_annoying_issues
Fix post Element release issues
2020-07-21 16:43:26 +02:00
ganfra
e5596f6a97 Use Any instead of JsonDict so users won't loose ignored users 2020-07-21 10:43:52 +02:00
ganfra
d638306b04 Merge pull request #1703 from vector-im/feature/md_files
Riot -> Element
2020-07-21 10:08:07 +02:00
ganfra
dba9356472 Merge pull request #1714 from vector-im/feature/deactivated_account
i18n deactivated account error
2020-07-21 08:14:10 +02:00
ganfra
fb247f8bea Update CHANGES and clean code 2020-07-20 20:29:52 +02:00
ganfra
c880e2b848 Fix Requesting avatar thumbnails in Element uses wrong http "user-agent" string #1725 2020-07-20 20:24:04 +02:00
ganfra
1436477a14 Rework a bit user account data (and avoid blocking syncs) 2020-07-20 19:52:24 +02:00
ganfra
35fe4f7268 Fix modular link to EMS 2020-07-20 18:47:41 +02:00
ganfra
a488e88f93 Add reaction to RoomSummary latest previewable event 2020-07-20 16:49:05 +02:00
ganfra
0d51c160eb Should fix realm crash loop 2020-07-20 16:43:39 +02:00
ganfra
27207a27ae Fix "uploads don't work with Room v6" #1558 2020-07-17 20:33:47 +02:00
Benoit Marty
5082defddf i18n deactivated account error 2020-07-17 17:23:48 +02:00
ganfra
6e0095edb0 Feature/fix notification clearing (#1707)
Updating notification when entering a room
2020-07-17 13:31:36 +02:00
Benoit Marty
d78cd81c6f Cleanup strings.xml 2020-07-16 19:21:02 +02:00
Benoit Marty
ef93ef57f9 Replace Riot -> Element in documentation 2020-07-16 19:19:31 +02:00
Benoit Marty
a600e896a0 Update Contributing file after the release of Element 2020-07-16 19:14:20 +02:00
Benoit Marty
993c340c4f Update Authors file after the release of Element 2020-07-16 19:11:33 +02:00
Benoit Marty
87f506f23b Update Readme file after the release of Element 2020-07-16 19:10:52 +02:00
Benoit Marty
44fb53ce6b Merge pull request #1701 from vector-im/feature/riotx_remaining
RiotX -> Element
2020-07-16 18:56:44 +02:00
Benoit Marty
1d2e374526 RiotX -> Element 2020-07-16 16:01:25 +02:00
Benoit Marty
5c423d2897 RiotX -> Element 2020-07-15 20:21:58 +02:00
Benoit Marty
6d0a1ee824 SSO Login: use "element://element" url instead of "riotx://riotx" 2020-07-15 20:21:35 +02:00
Benoit Marty
978ab76644 Merge pull request #1694 from vector-im/feature/improve_splash
Improve splash screen
2020-07-15 20:15:47 +02:00
Benoit Marty
da4e3f3b9c Merge pull request #1693 from vector-im/feature/fix_1613
Feature/fix 1613
2020-07-15 16:41:19 +02:00
Benoit Marty
f6d2d05e70 Improve splash screen 2020-07-15 15:08:46 +02:00
Benoit Marty
d747d96e77 Use correct title 2020-07-15 14:49:26 +02:00
Benoit Marty
484b69165b Add rounded light background to fix dark images on dark background issue (#1613) 2020-07-15 14:34:06 +02:00
Benoit Marty
0159e6dc00 Hide the menu item, it's not implemented yet 2020-07-15 14:33:01 +02:00
Benoit Marty
8f7aae4980 Tint the icon (#1613) 2020-07-15 14:33:01 +02:00
Benoit Marty
0f9e26efc5 let instead of also 2020-07-15 14:33:01 +02:00
Benoit Marty
53f0b77fb0 Version++ 2020-07-15 14:32:15 +02:00
Benoit Marty
fca669ff98 Merge branch 'release/1.0.0' 2020-07-15 14:27:24 +02:00
Benoit Marty
e910fc60fe Merge branch 'release/1.0.0' into develop 2020-07-15 14:27:23 +02:00
Benoit Marty
1d2e62fed7 Prepare release 1.0.0 2020-07-15 14:27:08 +02:00
Benoit Marty
7651eb5c3c Revert "Temporary remove the _dev suffix added to version"
This reverts commit 7237ec7310.
2020-07-15 14:14:44 +02:00
Benoit Marty
a06484c260 Merge pull request #1691 from vector-im/rebranding
Rebranding!
2020-07-15 14:13:11 +02:00
Benoit Marty
9b49e6a817 Use local file in a dialog though (else -> crash) 2020-07-13 23:03:16 +02:00
Benoit Marty
bef0c7725b Use openUrlInChromeCustomTab because it's better and it fixes crash on API 21 (at least) 2020-07-13 22:43:19 +02:00
Benoit Marty
7237ec7310 Temporary remove the _dev suffix added to version 2020-07-13 21:50:26 +02:00
Benoit Marty
3abe5b8467 ktlint 2020-07-13 21:38:49 +02:00
Valere
0406854fa5 lighter bottom sheet separator 2020-07-13 19:25:59 +02:00
Valere
ca6a398a72 Update add room icon 2020-07-13 19:25:44 +02:00
Valere
5e81ce8e3e Update tmp lab icon for notif tab 2020-07-13 19:25:35 +02:00
Onuray Sahin
8658090736 Fix tint color of settings. 2020-07-13 20:16:01 +03:00
Onuray Sahin
614ac88567 Create group room icon changed. 2020-07-13 20:06:39 +03:00
Onuray Sahin
797dcdb48b Merge branch 'riotx_develop' into rebranding_rebase
* riotx_develop: (111 commits)
  Video calls are shown as a voice ones in the timeline (Fixes #1676)
  Fix regression: not able to create a room without IS configured (Fixes #1679)
  Fix / view attachment crash + freeze when offline
  Version++
  Prepare release 0.91.5
  Fix test compilation issue
  Fix crash after rebase
  Add TODO
  Copy Javadoc to the API class
  Move internal methods to internal task
  Latest renaming
  Rename CreateRoomParamsInternalBuilder to CreateRoomBodyBuilder for clarity
  Rename CreateRoomParamsBuilder to CreateRoomParams for clarity
  Rename internal class
  Expose other objects in the builder to create a room
  ktlint
  Display threePid invite along with the other invite (code is a bit dirty)
  Hide right arrow if threepid invite can not be revoked
  Disable fetching Msisdn, it does not work
  Revoke ThreePid invitation (#548)
  ...

# Conflicts:
#	vector/build.gradle
#	vector/src/main/java/im/vector/riotx/features/crypto/keys/KeysExporter.kt
#	vector/src/main/res/layout/bottom_sheet_logout_and_backup.xml
#	vector/src/main/res/values/strings.xml
2020-07-13 19:59:20 +03:00
Onuray Sahin
3a696ea9a1 Revert shield icons. 2020-07-13 19:21:22 +03:00
Onuray Sahin
1b19cb7449 Roll back ic_shield_black 2020-07-13 19:18:56 +03:00
Onuray Sahin
ea81a5298b ic_shield_black icon changed. 2020-07-13 18:55:30 +03:00
Onuray Sahin
ae65eb6a3e Advanced Settings icon changed. 2020-07-13 18:39:09 +03:00
Onuray Sahin
67e07a7932 Room action settings and leave icons changed. 2020-07-13 18:33:29 +03:00
Onuray Sahin
6ef15f3c1c ic_star icon changed. 2020-07-13 18:28:05 +03:00
Benoit Marty
0e3be83da2 Merge branch 'rebranding' of https://gitlab.matrix.org/new-vector/element/element-android into rebranding 2020-07-13 17:21:40 +02:00
Benoit Marty
8b5b7db241 Fix icons alignment issue 2020-07-13 17:21:33 +02:00
Onuray Sahin
1fd8a5fa91 Merge branch 'rebranding' of https://gitlab.matrix.org/new-vector/element/element-android into rebranding
* 'rebranding' of https://gitlab.matrix.org/new-vector/element/element-android:
  Icon: "Modular" -> "Element Matrix Services"
2020-07-13 18:11:45 +03:00
Onuray Sahin
34d7a86175 Update attachment and send icons. 2020-07-13 18:10:49 +03:00
Benoit Marty
21f1f89a4b Merge branch 'rebranding' of https://gitlab.matrix.org/new-vector/element/element-android into rebranding 2020-07-13 16:53:48 +02:00
Benoit Marty
159f286b2c Icon: "Modular" -> "Element Matrix Services" 2020-07-13 16:53:26 +02:00
Onuray Sahin
06454cc3e0 Sticker attachment icon changed. 2020-07-13 17:52:09 +03:00
Onuray Sahin
be27b580d6 ic_fab_add_chat icon changed. 2020-07-13 17:40:16 +03:00
Onuray Sahin
f91b6938f5 Remove stroke from shield icons. 2020-07-13 17:35:58 +03:00
Onuray Sahin
a7fea8d012 Relace eye and eye_closed icons. 2020-07-13 17:26:23 +03:00
Onuray Sahin
18bc40fb66 Design review fixes for login screen. 2020-07-13 17:01:48 +03:00
Benoit Marty
1dec4bc96b Wording: "Modular" -> "Element Matrix Services" 2020-07-13 15:49:46 +02:00
Benoit Marty
0e28214b63 Do not display the name change popup for a new installation 2020-07-13 14:23:06 +02:00
Benoit Marty
ca61751a8b Merge pull request #1678 from vector-im/feature/attachement_pager_fix
Fix / view attachment crash + freeze when offline
2020-07-13 11:33:11 +02:00
Benoit Marty
cce1c2252d Merge pull request #1681 from vector-im/feature/other_fixes
Video calls are shown as a voice ones in the timeline (Fixes #1676)
2020-07-13 11:31:38 +02:00
Benoit Marty
6a4d887941 Merge branch 'develop' into feature/other_fixes 2020-07-13 11:31:25 +02:00
Benoit Marty
4de1759321 Merge pull request #1680 from vector-im/feature/fix_create_room
Fix regression: not able to create a room without IS configured (Fixes #1679)
2020-07-13 11:30:47 +02:00
Benoit Marty
139cd051ab Video calls are shown as a voice ones in the timeline (Fixes #1676) 2020-07-13 10:57:52 +02:00
Benoit Marty
33b2abc3b9 Fix regression: not able to create a room without IS configured (Fixes #1679) 2020-07-13 10:46:51 +02:00
Valere
c63128cfbd Fix / view attachment crash + freeze when offline 2020-07-13 09:15:52 +02:00
Benoit Marty
5063188b25 Version++ 2020-07-11 23:00:16 +02:00
Benoit Marty
d09c03bff3 Merge branch 'release/0.91.5' into develop 2020-07-11 22:58:47 +02:00
Valere
bbbd45efcd Fix / missing transparency on waiting view background 2020-07-11 20:46:47 +02:00
Onuray Sahin
0bc51b2ed8 Change brand name in all strings for all languages. 2020-07-10 18:28:22 +03:00
Onuray Sahin
d3595173b4 Room notification settings icons changed. 2020-07-10 15:14:06 +03:00
Onuray Sahin
14d4b34cee Change event action icons. 2020-07-10 14:54:15 +03:00
Onuray Sahin
538149233b Change timeline call action icons. 2020-07-10 14:43:19 +03:00
Onuray Sahin
bcb203f8e0 Change settings icons. 2020-07-10 14:24:10 +03:00
Onuray Sahin
3c6937ff5a Change status bar icon. 2020-07-10 14:11:34 +03:00
Onuray Sahin
9310073c07 Remove unused splash icons. 2020-07-10 13:21:30 +03:00
Onuray Sahin
cd949e9d38 Fix bottom sheet divider colors. 2020-07-10 13:11:23 +03:00
Onuray Sahin
541e1fc4cc Change file names with the new brand. 2020-07-10 13:11:01 +03:00
Onuray Sahin
93fe00a299 Merge branch 'develop' into rebranding_rebase
* develop: (123 commits)
  Fixes #1647 share not working
  Put xmx to 2048m
  Update changelog after PR merged
  Version++
  Prepare release 0.91.04-beta
  Add the new value to the ViewEvent, because the state is maybe not up to date.
  Fix lint error, following the upgrade of the libs
  Revert to gradle build 3.5.3
  Clean code
  Fix leaving selected group
  QuickFix / crash when  starting in airplane mode
  Group: rework a bit how and when we fetch data about groups
  EventInsert: add InsertType to avoid trying to process events we shouldn't
  Upload device keys only once to the homeserver and fix crash when no network (#1629)
  Upgrade some dependencies
  Add a delay to avoid crash. Sounds like a workaround...
  Handle certificate error in case of Direct Login
  Handle JobCancellationException
  Simplify the server selection screen: remove the "Continue" button
  Re-activate Wellknown support with updated UI (#1614)
  ...

# Conflicts:
#	vector/build.gradle
#	vector/src/main/res/values/strings.xml
2020-07-10 13:05:52 +03:00
Onuray Sahin
0d9ff4bde3 Add branch name to login splash screen. 2020-07-10 12:04:48 +03:00
Onuray Sahin
c1f1620624 Disclaimer dialog is updated. 2020-07-09 15:17:17 +03:00
Onuray Sahin
f5284e8447 previously-riot url added to use in disclaimer dialog. 2020-07-09 15:15:27 +03:00
Onuray Sahin
347cf08861 Update settings urls. 2020-07-09 13:46:27 +03:00
Onuray Sahin
540317639a Change slogan. 2020-07-09 12:51:29 +03:00
Onuray Sahin
bdcd96544e Use bigger logo at loading screen. 2020-07-09 12:48:45 +03:00
Onuray Sahin
8237c949f1 Change logo in login screen. 2020-07-09 12:27:28 +03:00
Onuray Sahin
f025811025 Change splash screen icons. 2020-07-08 17:54:51 +03:00
Onuray Sahin
6d270dc5f4 Fix background of bottom sheet generic item. 2020-07-08 13:52:56 +03:00
Onuray Sahin
2afe642e8b Use 48px icon instead of 44px. 2020-07-08 13:02:02 +03:00
Onuray Sahin
680e62cb98 Update application icons. 2020-07-08 12:55:15 +03:00
Onuray Sahin
702711fc5e Fix drawer layout colors. 2020-07-08 10:58:24 +03:00
Onuray Sahin
ac7a929a1a Fix tint color of add icon. 2020-07-07 15:05:47 +03:00
Onuray Sahin
8313e45737 Use material add icon instead of png. 2020-07-07 13:28:06 +03:00
Onuray Sahin
09ca2361d7 Adjust colors and remove divider between notification settings and leave room action. 2020-07-07 13:12:24 +03:00
Onuray Sahin
9514835232 Fix bottom sheet backgrounds. 2020-07-07 12:52:28 +03:00
Onuray Sahin
e93a2d7c5d UI review fixes. 2020-07-07 10:35:38 +03:00
Onuray Sahin
b1c088a03b Update new accent color. 2020-07-06 13:08:06 +03:00
Onuray Sahin
f4057ea3fa Fix background of drawer layout. 2020-07-03 14:50:33 +03:00
Onuray Sahin
a7480c1860 Change colors according to the new color palette. 2020-07-03 13:36:30 +03:00
Onuray Sahin
332041e13b Move some of colors from attrs.xml to colors_riotx.xml 2020-07-02 19:29:45 +03:00
Onuray Sahin
74a3d7619b Handle permalink coming from the new domain. 2020-07-01 22:21:59 +03:00
Onuray Sahin
fc88892ee6 Update bottom navigation and room profile icons. 2020-07-01 22:21:06 +03:00
Onuray Sahin
7aa9f88ceb New string resources created by changing brand name. 2020-07-01 14:43:35 +03:00
Onuray Sahin
08710978c5 Change the rageshake tag. 2020-06-30 15:02:37 +03:00
Onuray Sahin
cba9109206 Rename the application. 2020-06-30 14:52:50 +03:00
Onuray Sahin
482621e86f Set versionName to 1.0.0 2020-06-30 14:34:02 +03:00
411 changed files with 3895 additions and 3296 deletions

View File

@@ -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!

View File

@@ -1,3 +1,60 @@
Changes in Element 1.0.4 (2020-08-03)
===================================================
Bugfix 🐛:
- Fix Crash when opening invite to room user screen
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)
===================================================
Features ✨:
- Re-branding: The app is now called Element. New name, new themes, new icons, etc. More details here: https://element.io/blog/welcome-to-element/ (#1691)
Bugfix 🐛:
- Video calls are shown as a voice ones in the timeline (#1676)
- Fix regression: not able to create a room without IS configured (#1679)
Changes in Riot.imX 0.91.5 (2020-07-11)
===================================================
@@ -696,7 +753,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
Changes in RiotX 0.X.0 (2020-XX-XX)
Changes in Element 1.X.X (2020-XX-XX)
===================================================
Features ✨:

View File

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

View File

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

View File

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

View File

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

View File

@@ -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'

View File

@@ -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 │
│ │ │ │
│ │ │ │

View File

@@ -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(),

View File

@@ -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"

View File

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

View File

@@ -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
)

View File

@@ -20,6 +20,7 @@ interface LegacySessionImporter {
/**
* Will eventually import a session created by the legacy app.
* @return true if a session has been imported
*/
fun process()
fun process(): Boolean
}

View File

@@ -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 {
/**

View File

@@ -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
)

View File

@@ -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"
}

View File

@@ -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
}

View File

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

View File

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

View File

@@ -59,5 +59,5 @@ data class CallInviteContent(
}
}
fun isVideo(): Boolean = offer?.sdp?.contains(Offer.SDP_VIDEO) == true
fun isVideo() = offer?.sdp?.contains(Offer.SDP_VIDEO) == true
}

View File

@@ -25,7 +25,3 @@ interface MessageContent {
val relatesTo: RelationDefaultContent?
val newContent: Content?
}
fun MessageContent?.isReply(): Boolean {
return this?.relatesTo?.inReplyTo?.eventId != null
}

View File

@@ -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 ?: ""

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}
}

View File

@@ -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>()

View File

@@ -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)
}
}
}

View File

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

View File

@@ -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) {

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}

View File

@@ -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() {

View File

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

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -53,14 +53,14 @@ internal class DefaultLegacySessionImporter @Inject constructor(
private var DELETE_PREVIOUS_DATA = true
}
override fun process() {
override fun process(): Boolean {
Timber.d("Migration: Importing legacy session")
val list = loginStorage.credentialsList
Timber.d("Migration: found ${list.size} session(s).")
val legacyConfig = list.firstOrNull() ?: return
val legacyConfig = list.firstOrNull() ?: return false
runBlocking {
Timber.d("Migration: importing a session")
@@ -97,6 +97,9 @@ internal class DefaultLegacySessionImporter @Inject constructor(
Timber.d("Migration: clear shared prefs - DEACTIVATED")
}
}
// A session has been imported
return true
}
private suspend fun importCredentials(legacyConfig: LegacyHomeServerConnectionConfig) {

View File

@@ -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()

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

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

View File

@@ -46,7 +46,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
suspend fun build(params: CreateRoomParams): CreateRoomBody {
val invite3pids = params.invite3pids
.takeIf { it.isNotEmpty() }
.let {
?.let { invites ->
// This can throw Exception if Identity server is not configured
ensureIdentityTokenTask.execute(Unit)
@@ -54,7 +54,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
?: throw IdentityServiceError.NoIdentityServerConfigured
val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
params.invite3pids.map {
invites.map {
ThreePidInviteBody(
id_server = identityServerUrlWithoutProtocol,
id_access_token = identityServerAccessToken,

View File

@@ -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) }

View File

@@ -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")

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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?) {

View File

@@ -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
)
}

View File

@@ -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)
}

View File

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

View File

@@ -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()

View File

@@ -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()

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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"
}
}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()
)

View File

@@ -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()

View File

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

View File

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

View File

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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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> {

View File

@@ -15,9 +15,9 @@ androidExtensions {
}
// Note: 2 digits max for each value
ext.versionMajor = 0
ext.versionMinor = 91
ext.versionPatch = 5
ext.versionMajor = 1
ext.versionMinor = 0
ext.versionPatch = 4
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@@ -183,7 +183,7 @@ android {
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "app_name", "Riot.imX dbg"
resValue "string", "app_name", "Element dbg"
resValue "bool", "debug_mode", "true"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
@@ -192,7 +192,7 @@ android {
}
release {
resValue "string", "app_name", "Riot.imX"
resValue "string", "app_name", "Element (Riot.im)"
resValue "bool", "debug_mode", "false"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"

View File

@@ -80,8 +80,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" />
@@ -136,6 +136,7 @@
<data android:scheme="https" />
<data android:host="riot.im" />
<data android:host="element.io" />
<data android:pathPrefix="/config/" />
</intent-filter>
</activity>

View File

@@ -28,7 +28,7 @@
</head>
<body>
<div>
<p>RiotX Android</p>
<p>Element Android</p>
<p>Third Party Licenses</p>
</div>

View File

@@ -43,6 +43,7 @@ import im.vector.riotx.core.di.VectorComponent
import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.rx.RxConfig
import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.disclaimer.doNotShowDisclaimerDialog
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.NotificationUtils
@@ -123,7 +124,11 @@ class VectorApplication :
notificationUtils.createNotificationChannels()
// It can takes time, but do we care?
legacySessionImporter.process()
val sessionImported = legacySessionImporter.process()
if (!sessionImported) {
// Do not display the name change popup
doNotShowDisclaimerDialog(this)
}
if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!

View File

@@ -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
}

View File

@@ -68,7 +68,7 @@ class ExportKeysDialog {
passwordVisible = !passwordVisible
passPhrase1EditText.showPassword(passwordVisible)
passPhrase2EditText.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
val exportDialog = builder.show()

View File

@@ -49,7 +49,7 @@ class PromptPasswordDialog {
showPassword.setOnClickListener {
passwordVisible = !passwordVisible
passwordEditText.showPassword(passwordVisible)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
showPassword.setImageResource(if (passwordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
AlertDialog.Builder(activity)

View File

@@ -21,7 +21,7 @@ import im.vector.riotx.R
/**
* Default background color is for the bottom sheets (R.attr.vctr_list_bottom_sheet_divider_color).
* To use in fragment, set color using R.attr.vctr_list_divider_color
* To use in fragment, set color using R.attr.riotx_list_divider_color
*/
@EpoxyModelClass(layout = R.layout.item_divider)
abstract class DividerItem : VectorEpoxyModel<DividerItem.Holder>() {

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -75,17 +75,22 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
holder.view.isClickable = false
}
holder.title.text = title
val tintColor = if (destructive) {
val titleTintColor = if (destructive) {
ContextCompat.getColor(holder.view.context, R.color.riotx_notice)
} else {
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_primary)
}
holder.title.setTextColor(tintColor)
val iconTintColor = if (destructive) {
ContextCompat.getColor(holder.view.context, R.color.riotx_notice)
} else {
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary)
}
holder.title.setTextColor(titleTintColor)
holder.subtitle.setTextOrHide(subtitle)
if (iconRes != 0) {
holder.icon.setImageResource(iconRes)
if (tintIcon) {
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor))
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(iconTintColor))
} else {
ImageViewCompat.setImageTintList(holder.icon, null)
}
@@ -110,7 +115,7 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
if (editableRes != 0 && editable) {
val tintColorSecondary = if (destructive) {
tintColor
titleTintColor
} else {
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary)
}

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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() }

View File

@@ -27,7 +27,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.platform.SimpleTextWatcher
fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
@DrawableRes clearIconRes: Int = R.drawable.ic_x_green) {
@DrawableRes clearIconRes: Int = R.drawable.ic_x_gray) {
addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
val clearIcon = if (s.isNotEmpty()) clearIconRes else 0

View File

@@ -102,7 +102,7 @@ fun Fragment.queryExportKeys(userId: String, requestCode: Int) {
selectTxtFileToWrite(
activity = requireActivity(),
fragment = this,
defaultFileName = "riot-megolm-export-$userId-$timestamp.txt",
defaultFileName = "element-megolm-export-$userId-$timestamp.txt",
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export),
requestCode = requestCode
)
@@ -114,7 +114,7 @@ fun Activity.queryExportKeys(userId: String, requestCode: Int) {
selectTxtFileToWrite(
activity = this,
fragment = null,
defaultFileName = "riot-megolm-export-$userId-$timestamp.txt",
defaultFileName = "element-megolm-export-$userId-$timestamp.txt",
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export),
requestCode = requestCode
)

View File

@@ -23,6 +23,8 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceViewHolder
import im.vector.riotx.R
import im.vector.riotx.features.themes.ThemeUtils
/**
* Customize PreferenceCategory class to redefine some attributes.
@@ -46,6 +48,7 @@ class VectorPreferenceCategory : PreferenceCategory {
val titleTextView = holder.itemView.findViewById<TextView>(android.R.id.title)
titleTextView?.setTypeface(null, Typeface.BOLD)
titleTextView?.setTextColor(ThemeUtils.getColor(context, R.attr.riotx_text_primary))
// "isIconSpaceReserved = false" does not work for preference category, so remove the padding
if (!isIconSpaceReserved) {

View File

@@ -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)
}
}

View File

@@ -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"
}
}
}

View File

@@ -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)

View File

@@ -63,7 +63,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
}.exhaustive
}
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
enableEncryptionIfInvitedUsersSupportIt = session.getHomeServerCapabilities().adminE2EByDefault
}
session.rx()

View File

@@ -74,7 +74,7 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
viewModel.showPasswordMode.observe(viewLifecycleOwner, Observer {
val shouldBeVisible = it ?: false
mPassphraseTextEdit.showPassword(shouldBeVisible)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
})
mPassphraseTextEdit.setOnEditorActionListener { _, actionId, _ ->

View File

@@ -141,7 +141,7 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment()
val shouldBeVisible = it ?: false
mPassphraseTextEdit.showPassword(shouldBeVisible)
mPassphraseConfirmTextEdit.showPassword(shouldBeVisible)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
})
viewModel.confirmPassphraseError.observe(viewLifecycleOwner, Observer {

View File

@@ -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)
}
}
}

View File

@@ -97,6 +97,6 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
override fun invalidate() = withState(sharedViewModel) { state ->
val shouldBeVisible = state.passphraseVisible
ssss_passphrase_enter_edittext.showPassword(shouldBeVisible)
ssss_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
ssss_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
}

View File

@@ -101,7 +101,7 @@ class BootstrapAccountPasswordFragment @Inject constructor(
if (state.step is BootstrapStep.AccountPassword) {
val isPasswordVisible = state.step.isPasswordVisible
bootstrapAccountPasswordEditText.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
}
}

View File

@@ -103,7 +103,7 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() : VectorBaseFragm
if (state.step is BootstrapStep.ConfirmPassphrase) {
val isPasswordVisible = state.step.isPasswordVisible
ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed else R.drawable.ic_eye)
}
}
}

Some files were not shown because too many files have changed in this diff Show More