mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-09 08:38:43 +01:00
Squashed commit of the following:
commit a588989d13aa9f3e6b17a6178b9cbde5b030a914 Merge: 56067300e0 5ee3eefe96 Author: Benoit Marty <benoitm@matrix.org> Date: Fri Jan 6 18:45:18 2023 +0100 Merge pull request #7875 from vector-im/feature/bma/releaseScript3 Release script update commit 56067300e0d9d67910b58915f3f262009738f2d6 Merge: baa46634b5 330a9be787 Author: Benoit Marty <benoitm@matrix.org> Date: Fri Jan 6 18:44:55 2023 +0100 Merge pull request #7905 from RiotTranslateBot/weblate-element-android-element-app Translations update from Weblate commit baa46634b5fb33f4d9131cf66e448bec28a75429 Merge: 93021a6028 0d2fb8e3d0 Author: Benoit Marty <benoitm@matrix.org> Date: Fri Jan 6 18:44:37 2023 +0100 Merge pull request #7885 from vector-im/feature/bma/fixLint Fix lint false positive commit 93021a6028f5103a568e61446d9d0859c14fff6f Merge: f856142cdc e9d1de8fba Author: Benoit Marty <benoitm@matrix.org> Date: Fri Jan 6 18:43:53 2023 +0100 Merge pull request #7724 from vector-im/feature/bma/launchWhenResumed Observe ViewEvents only when resumed commit e9d1de8fbac8d93f6a98aa3cc706f6ad16ea8f10 Author: Benoit Marty <benoit@matrix.org> Date: Fri Jan 6 17:36:40 2023 +0100 Fix compilation issue after rebase. commit 330a9be7877d2f3b704fb94f307958e4853e532a Merge: f856142cdc 4f2550ae92 Author: Weblate <noreply@weblate.org> Date: Fri Jan 6 16:33:47 2023 +0000 Merge branch 'origin/develop' into Weblate. commit 4f2550ae923b1be83c41c501a2283f37d3bfe4f7 Author: Linerly <linerly@protonmail.com> Date: Wed Jan 4 22:47:24 2023 +0000 Translated using Weblate (Indonesian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ commit 5734a270d8b1630f43543747174336191fe2bdb5 Author: waclaw66 <waclaw66@seznam.cz> Date: Wed Jan 4 16:04:26 2023 +0000 Translated using Weblate (Czech) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ commit 0882e1bf81b17f4afb84cb2dcf11c4bae38d44c9 Author: Jeff Huang <s8321414@gmail.com> Date: Thu Jan 5 02:10:13 2023 +0000 Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ commit 53db9885252338dd8aecc7ce88f1832ff00401fd Author: Christian Paul <info@jaller.de> Date: Thu Jan 5 16:06:48 2023 +0000 Translated using Weblate (Esperanto) Currently translated at 2.2% (2 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/eo/ commit ff9cf8fd2f0e3edcfb0d55bddf86dad31938cdb3 Author: Danial Behzadi <dani.behzi@ubuntu.com> Date: Wed Jan 4 17:43:34 2023 +0000 Translated using Weblate (Persian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ commit 8a5aad1ba0a3ee66e40b10f05fafa22641cf63dc Author: Priit Jõerüüt <riot@joeruut.com> Date: Thu Jan 5 07:22:25 2023 +0000 Translated using Weblate (Estonian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ commit 2903a644f2e258f6509e7dfa126bca5bcfed9c37 Author: Ihor Hordiichuk <igor_ck@outlook.com> Date: Wed Jan 4 19:24:53 2023 +0000 Translated using Weblate (Ukrainian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ commit 96363fb789f82acbb24d1bb55f1c3bad505431c5 Author: Jozef Gaal <preklady@mayday.sk> Date: Wed Jan 4 16:18:18 2023 +0000 Translated using Weblate (Slovak) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ commit 271b828be09c638cbc43a30708be8404493ac255 Author: Szimszon <github@oregpreshaz.eu> Date: Wed Jan 4 18:55:35 2023 +0000 Translated using Weblate (Hungarian) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ commit bd21f032d4a53d0292f4c580c237cf573ba74361 Author: Glandos <bugs-github@antipoul.fr> Date: Thu Jan 5 08:42:07 2023 +0000 Translated using Weblate (French) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ commit 860df019025ac652ef265814afa8eec759ce099d Author: Vri <element@vrifox.cc> Date: Wed Jan 4 16:20:15 2023 +0000 Translated using Weblate (German) Currently translated at 100.0% (89 of 89 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ commit ed84212c78d176c06760c9be65a097d11781e7ed Author: Besnik Bleta <besnik@programeshqip.org> Date: Wed Jan 4 17:01:03 2023 +0000 Translated using Weblate (Albanian) Currently translated at 99.3% (2558 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ commit f790921785b91b1d46fa5a48199047abf9420ac5 Author: Mateus Rodrigues Costa <mateusrodcosta@gmail.com> Date: Wed Jan 4 21:12:32 2023 +0000 Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2576 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ commit 3098ec140d384a7705d384444c30e7d8a44b81df Author: overtinkering <overtinker@yandex.com> Date: Thu Jan 5 18:02:33 2023 +0000 Translated using Weblate (Spanish) Currently translated at 90.7% (2338 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ commit 725722d3f29b42e033925491c8e6ca1d492aaf0e Author: Christian Paul <info@jaller.de> Date: Thu Jan 5 16:14:10 2023 +0000 Translated using Weblate (Esperanto) Currently translated at 76.0% (1960 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/eo/ commit ea924642ce0e4eca0537f5f752e63c27d5fe5e3e Author: Christian Paul <info@jaller.de> Date: Thu Jan 5 16:51:43 2023 +0000 Translated using Weblate (Danish) Currently translated at 10.2% (264 of 2576 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/da/ commit f856142cdc4edfbbdc7047a81a41ef3ea9cedea4 Merge: b7076a13dc 85cfa433d9 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Jan 6 16:07:44 2023 +0100 Merge pull request #7886 from vector-im/feature/mna/past-polls-ui [Poll] Render past polls list of a room (PSG-1029) commit b7076a13dcab7a961079ef47bbd866c42d805397 Merge: 41bcdd7232 dbf3b76331 Author: Benoit Marty <benoitm@matrix.org> Date: Fri Jan 6 15:16:16 2023 +0100 Merge pull request #7879 from vector-im/feature/bma/still_investigating Reduce number of crypto database transactions when handling the sync response commit 7b1724f6dd1aa3651fc7a36890565cf9d213aa27 Author: Benoit Marty <benoit@matrix.org> Date: Fri Jan 6 15:13:01 2023 +0100 changelog commit 9768430d5c19cc78a7d21c7dce375bb39890d26b Author: Benoit Marty <benoit@matrix.org> Date: Mon Dec 19 18:32:07 2022 +0100 Fix test compilation issue commit 71bd4f457a8093683c6b9d046352e032c92d21eb Author: Benoit Marty <benoit@matrix.org> Date: Wed Dec 7 17:48:25 2022 +0100 Ensure posted events from the ViewModel are consumed (once) by the UI Inspired from https://github.com/Kotlin/kotlinx.coroutines/issues/3002 commit 9c79d234440310bf41e4964c78dc48e8bbb89c15 Author: Benoit Marty <benoitm@matrix.org> Date: Fri Dec 16 21:02:33 2022 +0100 Ensure event are not sent if the lifecycle state is not RESUMED commit 0dd1abb9262a9ecf28ac85c99958e238d1459a95 Author: Benoit Marty <benoit@matrix.org> Date: Tue Dec 6 13:02:02 2022 +0100 Rename method commit 41bcdd723239ff8df487debdacab3d042bf09743 Merge: b8da53b3bb 7fc9705f3a Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Jan 6 14:18:20 2023 +0100 Merge pull request #7867 from vector-im/feature/mna/active-polls-ui [Poll] Render active polls list of a room (PSG-908) commit 85cfa433d9e15362c0b907d19e253c01cff665c5 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Jan 6 14:13:58 2023 +0100 Using ordinal of enum to render tabs commit b8da53b3bb4a7e62e0fa20aeaab425c4782742ce Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Jan 6 11:56:11 2023 +0000 Bump checker from 3.27.0 to 3.29.0 (#7903) Bumps [checker](https://github.com/typetools/checker-framework) from 3.27.0 to 3.29.0. - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.27.0...checker-framework-3.29.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit e88e874697a504896f1de2a36597d54607ffdb80 Merge: 2e95d4f97c c3ad7faa2c Author: Benoit Marty <benoitm@matrix.org> Date: Fri Jan 6 10:35:14 2023 +0100 Merge pull request #7865 from vector-im/dependabot/gradle/org.owasp-dependency-check-gradle-7.4.3 Bump dependency-check-gradle from 7.4.1 to 7.4.3 commit 2e95d4f97cf2bea6dddcb1de330394229f023d40 Merge: f1bd9b2cf3 87e661e3b5 Author: Florian Renaud <Florian14@users.noreply.github.com> Date: Fri Jan 6 09:10:00 2023 +0100 Merge pull request #7899 from vector-im/bugfix/fre/buffering_on_last_chunk [Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number commit 9b5fda2689531063847707804100be2576990afa Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:45:35 2023 +0100 Fix after rebase commit a5d076a28a2c82483cdaea9114f1e0cc0cc561da Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed Jan 4 10:49:07 2023 +0100 Adding total votes status for ended poll items commit 05363dc8ca26609ff41f746869a720ccb7da6135 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed Jan 4 10:30:57 2023 +0100 Adding winner option views for ended poll items commit 1cc26449f3f9145abe79e105a9635aa1f4152dde Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed Jan 4 10:12:34 2023 +0100 Renaming some ui fields commit 3deae1101c317375dbf1f14ddbcde8e40fa5a6c9 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue Jan 3 17:32:41 2023 +0100 Adding extra data for ended poll commit cf82486efa20613b7130399928c094eb031a790b Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 17:38:55 2022 +0100 Adding mocked data for ended polls commit 740591cd38a01d8bf469cac3d78cf162e4a71c89 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 17:31:23 2022 +0100 Updating unit tests commit cb45056c1a311ec652d72a198c089abb66f78669 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 17:28:57 2022 +0100 Mutualizing list fragments and add ended polls tab commit 0b535910d649d5c9dbe3777f15971aabb4cc973c Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 15:50:32 2022 +0100 Adding changelog entry commit 7fc9705f3a92d392b0ce8855ecd79e6abb32cc40 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 16:37:06 2023 +0100 Adding importantForAccessibility attribute to icon commit 2dab6ed052912e6296b643d15b30c69ce0df7516 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:27:11 2023 +0100 Fix horizontal margin of tabs commit ff9e78be42c500fd3a0985cadb9db67be8c54df3 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:20:20 2023 +0100 Use classical for loop instead of forEach commit d60403545c4f323f7067bef06776660cdcf4b4d3 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Jan 5 15:09:41 2023 +0100 Renaming of filter enum commit 87e661e3b5d895990648f5cc0e71e60d08d10982 Author: Florian Renaud <florianr@element.io> Date: Thu Jan 5 14:36:22 2023 +0100 Add changelog file commit 0d2fb8e3d089bce9c47f3ace8b170be6c661ffe5 Author: Benoit Marty <benoit@matrix.org> Date: Wed Jan 4 10:17:35 2023 +0100 Lint: fix KotlinNullnessAnnotation warning commit dbf3b763311ea0faae4df39a178ec584f5537f95 Author: Benoit Marty <benoitm@matrix.org> Date: Thu Jan 5 11:54:19 2023 +0100 Update doc. commit 27d32188bfd4af638af0a43b8044ce1a2a8701d7 Author: Benoit Marty <benoit@matrix.org> Date: Thu Jan 5 11:04:20 2023 +0100 Aggregate data outside of the RealmCryptoStore. commit 682bb8bde09fb8f083a7442091b91a0214fe2cf9 Author: Florian Renaud <florianr@element.io> Date: Wed Jan 4 14:06:58 2023 +0100 VB - Stop listening if we reach the last received chunk and there is no last sequence number commit 30940cb9370587b50fe2b5f05652032eaf0c1062 Author: Benoit Marty <benoit@matrix.org> Date: Thu Jan 5 09:53:12 2023 +0100 Rename `UserCrossSigningKeys` to `UserIdentity` commit 7e26c4b6f2533fde4db32d9fc986481f4e22bd4d Author: Benoit Marty <benoit@matrix.org> Date: Thu Jan 5 09:48:25 2023 +0100 Rename fun commit 354554e8435bbd0934066c130f63adb47df5e0ee Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 16:45:28 2022 +0100 Ignore missing ContentDescription commit e82c7afdae45860ee54aae8bf665305b723c9d4d Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 15:48:14 2022 +0100 Replace usage of colorAccent commit 6c0c5e506408d242ce1792ddd833c41836e573c3 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 15:12:12 2022 +0100 Rename poll item layout to be more generic commit bd9c53a96c96aa870d3edcb8db44a52f6afb2d42 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 14:57:37 2022 +0100 Show message when list is empty commit e0b77936c1e4325387a1e06e4d4fef34bd7acfdd Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 14:27:11 2022 +0100 Changing the date format commit bc985aa1ef310e5fe45cbaefc4b4cab71fa71dc8 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 14:19:50 2022 +0100 Adding unit tests for ViewModel commit 71b7edc6f2c095f476fda596cdc6cbaf2b3ca159 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 12:12:57 2022 +0100 Adding debug log commit bf67d2529f84899f1118f050d01b54cc0bd312a1 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 12:08:55 2022 +0100 Allow access of poll history only in debug variant commit 8de86e74807339f5a02abcea6fd5c1452ffc68c9 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 11:59:48 2022 +0100 Render mocked data get from use case commit 77d3b7da04b0e8e2cd9ff6ab44392565675eef11 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 11:40:41 2022 +0100 Fix missing id in Epoxy model commit f20513eb16602a7b05d02231ca784215f0a28551 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 10:56:44 2022 +0100 Render the active polls list on fragment commit 7b63f891c33ffbb28d42491ac4ac48b89aef0a4e Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 10:42:42 2022 +0100 Epoxy controller to render active poll list commit 9f97579f9dc4c928fd8f95449fde3a10b8f77040 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri Dec 30 10:07:50 2022 +0100 Epoxy model for active poll commit 10133bd20ffe12d059049d906079f3e74e57990f Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 17:46:07 2022 +0100 Setup tab layout when landing on the room polls screen commit 7436c2e1f5a60ed8507c9df53b49bec014182e07 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 16:41:42 2022 +0100 Navigate to new empty screen commit cba960fbd782c91ab7de7e84c3635d3374ce3de2 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 16:05:52 2022 +0100 Adding new entry "Poll history" into room profile screen commit e903dac22480525c97ba7ccc90cde41eeb811491 Author: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu Dec 29 15:40:59 2022 +0100 Adding changelog entry commit 437b93cc18751777e92f7a249dd67c93460382e0 Author: Benoit Marty <benoitm@matrix.org> Date: Wed Jan 4 11:35:04 2023 +0100 Add some doc commit 06f3c1101042500973c1999f8b9ae0f97a42332e Author: Benoit Marty <benoit@matrix.org> Date: Tue Jan 3 16:43:09 2023 +0100 Changelog commit 02e7157206404ea07c2544ac6e590c30ae201f98 Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 16:16:17 2023 +0100 Introduce CryptoCrossSigningKeys container commit 4c4ef0d73eca19e846c553456d9f9e500276328c Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 15:57:39 2023 +0100 Batch insertion of user data after downloading keys. commit f26178fc211efb9f0fa7fe56267401fd2a2a494a Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 15:21:03 2023 +0100 Avoid useless transaction commit a386a4762c13ac13922382d7ee119de10b67dd67 Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 15:18:32 2023 +0100 Crypto store: Log realm transactions and the duration commit c1a8bf828b9b5569a2e6498c7eec99a1d2c8597b Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 15:15:15 2023 +0100 Batch insertion of `shouldEncryptForInvitedMembers` commit 6f384c799f46fe63c55b71a3590a8b0349861bbd Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 15:02:45 2023 +0100 Batch insertion of `shouldShareHistory` commit 0e504e90145f021a7a1867184cc1a373dc5a5c2d Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 11:55:41 2023 +0100 Format commit 837590104d1a9c7cd06ae2b6908eb7c1eebfbd89 Author: Benoit Marty <benoitm@matrix.org> Date: Tue Jan 3 11:55:32 2023 +0100 Avoid launching coroutine for nothing. commit 56986c3a77b77b19ea9be782543719e0bdffccf5 Author: Benoit Marty <benoit@matrix.org> Date: Mon Jan 2 21:15:08 2023 +0100 Add a way to get the access token from the advances settings. commit 5ee3eefe964a4dc77f2cae24dafe3a1088fa2332 Author: Benoit Marty <benoit@matrix.org> Date: Mon Jan 2 16:55:25 2023 +0100 Pull branch sooner to ensure release version is correctly guessed commit c3ad7faa2c951d232471a52d68c870a2bb347400 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Dec 29 23:02:43 2022 +0000 Bump dependency-check-gradle from 7.4.1 to 7.4.3 Bumps dependency-check-gradle from 7.4.1 to 7.4.3. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> commit b3d578d6b831972c4aa7e6dd3a7429347a0e5bd0 Author: Benoit Marty <benoit@matrix.org> Date: Thu Dec 15 12:44:40 2022 +0100 Release script: Improve creation of the release on GitHub. commit 5e1d3e6c8df68ac05b46802679bd0508914a32c4 Author: Benoit Marty <benoitm@matrix.org> Date: Thu Dec 15 12:09:27 2022 +0100 Escape %
This commit is contained in:
parent
26d71e214a
commit
4d6bbbbe89
@ -29,7 +29,7 @@ buildscript {
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
||||
classpath "com.likethesalad.android:stem-plugin:2.2.3"
|
||||
classpath 'org.owasp:dependency-check-gradle:7.4.1'
|
||||
classpath 'org.owasp:dependency-check-gradle:7.4.3'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
|
||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
|
||||
|
1
changelog.d/7724.bugfix
Normal file
1
changelog.d/7724.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Observe ViewEvents only when resumed and ensure ViewEvents are not lost.
|
2
changelog.d/7864.wip
Normal file
2
changelog.d/7864.wip
Normal file
@ -0,0 +1,2 @@
|
||||
[Poll] Render active polls list of a room
|
||||
[Poll] Render past polls list of a room
|
1
changelog.d/7879.bugfix
Normal file
1
changelog.d/7879.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Reduce number of crypto database transactions when handling the sync response
|
1
changelog.d/7899.bugfix
Normal file
1
changelog.d/7899.bugfix
Normal file
@ -0,0 +1 @@
|
||||
[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number
|
2
fastlane/metadata/android/cs-CZ/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení.
|
||||
Úplný seznam změn: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/cs-CZ/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavní změny v této verzi: Vlákna jsou nyní povolena ve výchozím nastavení.
|
||||
Úplný seznam změn: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/de-DE/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert.
|
||||
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/de-DE/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Die wichtigsten Änderungen in dieser Version: Threads sind nun standardmäßig aktiviert.
|
||||
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases
|
@ -1 +1 @@
|
||||
Sekura kaj sencentrigita vokado kaj babilado. Tenu viajn datumojn sekuraj.
|
||||
Grupa mesaĝisto - ĉifrita mesaĝado, grupa babilejo kaj videovokoj
|
||||
|
@ -1 +1 @@
|
||||
Element (antaŭe Riot.im)
|
||||
Element - Sekura Tujmesaĝilo
|
||||
|
2
fastlane/metadata/android/et/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/et/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Põhilised muutused selles versioonis: jutulõngad on vaikimisi kasutusel.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: رشتهها اکنون به صورت پیشگزیده به کار افتادهاند.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fa/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
تغییرات عمده در این نگارش: رشتهها اکنون به صورت پیشگزیده به کار افتادهاند.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : Fils de discussion activés par défaut.
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/fr-FR/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : Fils de discussion activés par défaut.
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/hu-HU/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Legnagyobb változtatás ebben a verzióban: Új üzenetszálak alapból bekapcsolva!
|
||||
Teljes változási napló: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/hu-HU/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Legnagyobb változtatás ebben a verzióban: Az üzenetszálak alapból bekapcsolva!
|
||||
Teljes változási napló: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/id/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan.
|
||||
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/id/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Perubahan utama dalam versi ini: Utasan sekarang diaktifkan secara bawaan.
|
||||
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sk/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté.
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/sk/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavné zmeny v tejto verzii: Vlákna sú teraz predvolene zapnuté.
|
||||
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/uk/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Основні зміни в цій версії: Гілки відтепер типово ввімкнено.
|
||||
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/uk/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Основні зміни в цій версії: Гілки відтепер типово ввімкнено.
|
||||
Перелік усіх змін: https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/zh-TW/changelogs/40105160.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40105160.txt
Normal file
@ -0,0 +1,2 @@
|
||||
此版本中的主要變動:討論串現在預設啟用。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
2
fastlane/metadata/android/zh-TW/changelogs/40105180.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40105180.txt
Normal file
@ -0,0 +1,2 @@
|
||||
此版本中的主要變動:討論串現在預設啟用。
|
||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases
|
@ -39,7 +39,6 @@
|
||||
<string name="medium_phone_number">Telefonnummer</string>
|
||||
<string name="room_displayname_room_invite">Invitation til rum</string>
|
||||
<string name="room_displayname_two_members">%1$s og %2$s</string>
|
||||
|
||||
<string name="room_displayname_empty_room">Tomt rum</string>
|
||||
<string name="light_theme">Lyst Tema</string>
|
||||
<string name="dark_theme">Mørkt Tema</string>
|
||||
@ -82,7 +81,6 @@
|
||||
<string name="matrix_only_filter">Kun Matrix kontakter</string>
|
||||
<string name="no_result_placeholder">Ingen resultater</string>
|
||||
<string name="rooms_header">Rum</string>
|
||||
|
||||
<string name="send_bug_report_include_logs">Send logfiler</string>
|
||||
<string name="send_bug_report_include_crash_logs">Send crashlogfiler</string>
|
||||
<string name="send_bug_report_include_screenshot">Send screenshot</string>
|
||||
@ -110,10 +108,8 @@
|
||||
<string name="auth_invalid_email">Dette ligner ikke en gyldig emailadresse</string>
|
||||
<string name="auth_email_already_defined">Den emailadresse er allerede i brug.</string>
|
||||
<string name="auth_forgot_password">Glemt adgangskode?</string>
|
||||
|
||||
<string name="auth_recaptcha_message">Denne Home Server vil gerne være sikker på du ikke er en robot</string>
|
||||
<string name="auth_reset_password_error_unauthorized">Kunne ikke verificere emailadresse: vær sikker på du klikkede på linket i emailen</string>
|
||||
|
||||
<string name="login_error_invalid_home_server">Skriv gyldig URL</string>
|
||||
<string name="login_error_bad_json">Fejlformet JSON</string>
|
||||
<string name="login_error_not_json">Indeholdt ikke gyldig JSON</string>
|
||||
@ -134,15 +130,10 @@
|
||||
<string name="call_in_progress">Opkald I Gang</string>
|
||||
<string name="call_error_user_not_responding">Den anden side tog den ikke.</string>
|
||||
<string name="permissions_rationale_popup_title">Information</string>
|
||||
|
||||
|
||||
<string name="permissions_rationale_msg_record_audio">${app_name} skal bruge tilladelse til at bruge din mikrofon for at lave lydopkald.</string>
|
||||
|
||||
<string name="permissions_rationale_msg_camera_and_audio">${app_name} skal bruge tilladelse til at bruge dit kamera og din mikrofon for at lave videoopkald.
|
||||
|
||||
Giv venligst tilladelse ved næste pop-up for at lave opkaldet.</string>
|
||||
|
||||
|
||||
<string name="yes">JA</string>
|
||||
<string name="no">NEJ</string>
|
||||
<string name="_continue">Fortsæt</string>
|
||||
@ -150,7 +141,6 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet.</string>
|
||||
<string name="action_join">Forbind</string>
|
||||
<string name="action_reject">Afvis</string>
|
||||
<string name="room_jump_to_first_unread">Spring til første ulæste besked.</string>
|
||||
|
||||
<string name="room_participants_leave_prompt_title">Forlad rum</string>
|
||||
<string name="room_participants_leave_prompt_msg">Er du sikker på at du vil forlade rummet?</string>
|
||||
<string name="room_participants_header_direct_chats">DIREKTE CHATS</string>
|
||||
@ -163,7 +153,7 @@ Giv venligst tilladelse ved næste pop-up for at lave opkaldet.</string>
|
||||
<string name="room_participants_power_level_prompt">Du vil ikke kunne omgøre denne ændring da du forfremmer brugeren til at have samme magt niveau som dig selv.
|
||||
Er du sikker?</string>
|
||||
<string name="room_one_user_is_typing">%s skriver…</string>
|
||||
<string name="room_two_users_are_typing">"%1$s & %2$s skriver…"</string>
|
||||
<string name="room_two_users_are_typing">%1$s & %2$s skriver…</string>
|
||||
<string name="room_many_users_are_typing">%1$s, %2$s og andre skriver…</string>
|
||||
<string name="room_do_not_have_permission_to_post">Du har ikke tilladelse til at skrive i dette rum</string>
|
||||
<string name="ssl_trust">Stol på</string>
|
||||
@ -186,7 +176,6 @@ Er du sikker?</string>
|
||||
<item quantity="other">%d medlemsændringer</item>
|
||||
</plurals>
|
||||
<string name="list_members">Medlemsoversigt</string>
|
||||
|
||||
<plurals name="room_title_members">
|
||||
<item quantity="one">1 medlem</item>
|
||||
<item quantity="other">%d medlemmer</item>
|
||||
@ -199,8 +188,6 @@ Er du sikker?</string>
|
||||
<string name="search_hint">Søg</string>
|
||||
<string name="search_members_hint">Filtrer medlemmer i rum</string>
|
||||
<string name="search_no_results">Ingen resultater</string>
|
||||
|
||||
|
||||
<string name="room_settings_all_messages">Alle meddelelser</string>
|
||||
<string name="room_settings_add_homescreen_shortcut">Opret genvej på startskærm</string>
|
||||
<string name="settings_profile_picture">Profilbillede</string>
|
||||
@ -291,4 +278,4 @@ Er du sikker?</string>
|
||||
<string name="notice_room_created">%1$s oprettede rummet</string>
|
||||
<string name="notice_room_invite_no_invitee_by_you">Din invitation</string>
|
||||
<string name="link_this_email_settings_link">Forbind denne email med din konto</string>
|
||||
</resources>
|
||||
</resources>
|
@ -718,8 +718,8 @@
|
||||
<string name="settings_discovery_please_enter_server">Bonvolu enigi la URL-on de identiga servilo</string>
|
||||
<string name="settings_discovery_bad_identity_server">Ne povis konektiĝi al identiga servilo</string>
|
||||
<string name="settings_discovery_enter_identity_server">Enigu URL-on de identiga servilo</string>
|
||||
<string name="settings_discovery_confirm_mail_not_clicked">Ni sendis al vi konfirman retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon</string>
|
||||
<string name="settings_discovery_confirm_mail">Ni sendis al vi konfirman retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon</string>
|
||||
<string name="settings_discovery_confirm_mail_not_clicked">Ni sendis retleteron al %s, bonvolu unue kontroli vian retpoŝton kaj klaki la konfirman ligilon</string>
|
||||
<string name="settings_discovery_confirm_mail">Ni sendis retleteron al %s; kontrolu vian retpoŝton kaj klaku la konfirman ligilon</string>
|
||||
<string name="settings_discovery_msisdn_title">Troveblaj telefonnumeroj</string>
|
||||
<string name="settings_discovery_disconnect_identity_server_info">Malkonekto de via identiga servilo signifas, ke vi ne estos trovebla de aliaj uzantoj kaj ne povos inviti aliulojn per retpoŝtadreso aŭ telefono.</string>
|
||||
<string name="settings_discovery_no_msisdn">Elektebloj pri trovado aperos post aldono de telefonnumero.</string>
|
||||
@ -2201,4 +2201,4 @@
|
||||
<string name="call_ringing">Sonorante…</string>
|
||||
<string name="spaces">Aroj</string>
|
||||
<string name="initial_sync_request_reason_unignored_users">- Iom uzantoj reatentita</string>
|
||||
</resources>
|
||||
</resources>
|
@ -50,7 +50,7 @@
|
||||
<string name="notice_room_invite_with_reason">%1$s ha invitado a %2$s. Razón: %3$s</string>
|
||||
<string name="notice_room_invite_you_with_reason">%1$s te ha invitado. Razón: %2$s</string>
|
||||
<string name="notice_room_join_with_reason">%1$s se ha unido. Razón: %2$s</string>
|
||||
<string name="notice_room_leave_with_reason">%1$s se ha ido. Razón: %2$s</string>
|
||||
<string name="notice_room_leave_with_reason">%1$s dejó la sala. Razón: %2$s</string>
|
||||
<string name="notice_room_reject_with_reason">%1$s ha rechadazo la invitación. Razón: %2$s</string>
|
||||
<string name="notice_room_remove_with_reason">%1$s expulsó a %2$s. Razón: %3$s</string>
|
||||
<string name="notice_room_ban_with_reason">%1$s ha baneado a %2$s. Razón: %3$s</string>
|
||||
@ -81,17 +81,17 @@
|
||||
<string name="notice_room_guest_access_can_join">%1$s ha permitido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_room_guest_access_forbidden">%1$s ha impedido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_end_to_end_ok">%1$s ha activado el cifrado Extremo-a-Extremo.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm">%1$s ha activado el cifrado Extremo-a-Extremo (algoritmo no reconocido %2$s).</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm">%1$s ha activado el cifrado extremo-a-extremo (algoritmo no reconocido %2$s).</string>
|
||||
<string name="notice_room_invite_no_invitee_by_you">Tu invitación</string>
|
||||
<string name="notice_room_created">%1$s creó la sala</string>
|
||||
<string name="notice_room_created_by_you">Creaste la sala</string>
|
||||
<string name="notice_room_invite_by_you">Invitaste a %1$s</string>
|
||||
<string name="notice_room_join_by_you">Te uniste a la Sala</string>
|
||||
<string name="notice_room_leave_by_you">Dejaste la Sala</string>
|
||||
<string name="notice_room_leave_by_you">Dejaste la sala</string>
|
||||
<string name="notice_room_reject_by_you">Rechazaste la invitación</string>
|
||||
<string name="notice_room_remove_by_you">Tu pateaste a %1$s</string>
|
||||
<string name="notice_room_unban_by_you">Tu desbanaste a %1$s</string>
|
||||
<string name="notice_room_ban_by_you">Usted prohibió a %1$s</string>
|
||||
<string name="notice_room_ban_by_you">Excluiste a %1$s</string>
|
||||
<string name="notice_room_withdraw_by_you">Retiró la invitación de %1$s\'s</string>
|
||||
<string name="notice_avatar_url_changed_by_you">Cambiaste tu avatar</string>
|
||||
<string name="notice_display_name_set_by_you">Establece su nombre de visualización en %1$s</string>
|
||||
@ -152,10 +152,10 @@
|
||||
<string name="notice_room_aliases_added_and_removed_by_you">Agregaste %1$s y quitaste %2$s como direcciones para esta sala.</string>
|
||||
<string name="notice_room_canonical_alias_set_by_you">Estableciste la dirección principal de esta sala en %1$s.</string>
|
||||
<string name="notice_room_canonical_alias_unset_by_you">Quitaste la dirección principal de esta sala.</string>
|
||||
<string name="notice_room_guest_access_can_join_by_you">Ha permitido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_room_guest_access_forbidden_by_you">Ha impedido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_room_guest_access_can_join_by_you">Has permitido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_room_guest_access_forbidden_by_you">Has impedido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_end_to_end_ok_by_you">Has activado el cifrado Extremo-a-Extremo.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Has activado el cifrado Extremo-a-Extremo (algoritmo %1$s no reconocido).</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Has activado el cifrado extremo-a-extremo (algoritmo %1$s no reconocido).</string>
|
||||
<string name="notice_direct_room_guest_access_forbidden_by_you">Has impedido que invitados se unan a la sala.</string>
|
||||
<string name="notice_direct_room_guest_access_can_join_by_you">Has permitido a invitados unirse aquí.</string>
|
||||
<string name="notice_direct_room_leave_with_reason_by_you">Te has ido. Razón: %1$s</string>
|
||||
@ -163,7 +163,7 @@
|
||||
<string name="notice_direct_room_third_party_invite_by_you">Has invitado a %1$s</string>
|
||||
<string name="notice_direct_room_update_by_you">Has actualizado aquí.</string>
|
||||
<string name="notice_made_future_direct_room_visibility_by_you">Has hecho futuros mensajes visibles a %1$s</string>
|
||||
<string name="notice_direct_room_leave_by_you">Te saliste de la sala</string>
|
||||
<string name="notice_direct_room_leave_by_you">Has dejado la sala</string>
|
||||
<string name="notice_direct_room_join_by_you">Te uniste</string>
|
||||
<string name="notice_direct_room_created_by_you">Creaste la conversación</string>
|
||||
<string name="notice_direct_room_guest_access_forbidden">%1$s ha impedido que invitados se unan a la sala.</string>
|
||||
@ -255,7 +255,7 @@
|
||||
<string name="bottom_action_rooms">Salas y Grupos</string>
|
||||
<string name="home_filter_placeholder_home">Filtrar salas</string>
|
||||
<string name="invitations_header">Invitaciones</string>
|
||||
<string name="low_priority_header">Prioridad baja</string>
|
||||
<string name="low_priority_header">Baja prioridad</string>
|
||||
<string name="direct_chats_header">Conversaciones</string>
|
||||
<string name="matrix_only_filter">Solo contactos de Matrix</string>
|
||||
<string name="no_result_placeholder">No hay resultados</string>
|
||||
@ -429,7 +429,7 @@
|
||||
<string name="encryption_import_import">Importar</string>
|
||||
<string name="encryption_never_send_to_unverified_devices_title">Cifrar solo a sesiones verificadas</string>
|
||||
<string name="encryption_never_send_to_unverified_devices_summary">Nunca enviar mensajes cifrados a sesiones sin verificar desde esta sesión.</string>
|
||||
<string name="encryption_information_not_verified">SIN Verificar</string>
|
||||
<string name="encryption_information_not_verified">Sin Verificar</string>
|
||||
<string name="encryption_information_verified">Verificado</string>
|
||||
<string name="encryption_information_verify">Verificar</string>
|
||||
<string name="encryption_information_verify_device_warning">Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación:</string>
|
||||
@ -819,7 +819,7 @@
|
||||
<string name="keys_backup_settings_valid_signature_from_unverified_device">La copia de seguridad tiene una firma valida de la sesión no verificada %s</string>
|
||||
<string name="keys_backup_settings_invalid_signature_from_verified_device">La copia de seguridad tiene una firma inválida de la sesión verificada %s</string>
|
||||
<string name="keys_backup_settings_invalid_signature_from_unverified_device">La copia de seguridad tiene una firma inválida de la sesión no verificada %s</string>
|
||||
<string name="keys_backup_settings_untrusted_backup">Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora.</string>
|
||||
<string name="keys_backup_settings_untrusted_backup">Para usar la copia de seguridad de la clave en esta sesión introduce tu contraseña o tu clave de recuperación ahora.</string>
|
||||
<string name="keys_backup_settings_delete_confirm_message">¿Deseas borrar tus claves de cifrado guardadas en el servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados.</string>
|
||||
<string name="settings_play_shutter_sound">Reproducir sonido de cámara</string>
|
||||
<string name="encryption_information_unknown_ip">ip desconocida</string>
|
||||
@ -1182,7 +1182,7 @@
|
||||
<string name="verification_request_other_cancelled">%s cancelada</string>
|
||||
<string name="verification_request_you_cancelled">Cancelado por usted</string>
|
||||
<string name="verification_request_other_accepted">%s aceptada</string>
|
||||
<string name="verification_request_you_accepted">Aceptado por usted</string>
|
||||
<string name="verification_request_you_accepted">Aceptaste</string>
|
||||
<string name="verification_sent">Verificacion enviada</string>
|
||||
<string name="verification_request">Solicitud de verificación</string>
|
||||
<string name="verification_verify_device">Verifica esta Sesion</string>
|
||||
@ -1239,7 +1239,7 @@
|
||||
<string name="verification_profile_warning">Precaucion</string>
|
||||
<string name="room_member_profile_failed_to_get_devices">Error al obtener sesiones</string>
|
||||
<string name="room_member_profile_sessions_section_title">Sesiones</string>
|
||||
<string name="trusted">Confirmado</string>
|
||||
<string name="trusted">Confiable</string>
|
||||
<string name="not_trusted">No es confiable</string>
|
||||
<string name="initialize_cross_signing">Inicializar Firmas Cruzadas</string>
|
||||
<string name="reset_cross_signing">Restablecer claves</string>
|
||||
@ -1255,7 +1255,7 @@
|
||||
<string name="delete_event_dialog_reason_hint">Razón para redactar</string>
|
||||
<string name="login_default_session_public_name">${app_name} Android</string>
|
||||
<string name="refresh">Refrescar</string>
|
||||
<string name="new_session">Nuevo inicio de sesión detectado . ¿Fue usted\?</string>
|
||||
<string name="new_session">Nuevo inicio de sesión detectado . ¿Has sido tú\?</string>
|
||||
<string name="verify_new_session_was_not_me">Este no era yo</string>
|
||||
<string name="verify_new_session_compromized">Su cuenta puede estar comprometida</string>
|
||||
<string name="verification_cancelled">Verificación cancelada</string>
|
||||
@ -1263,7 +1263,7 @@
|
||||
<string name="message_key">Clave de mensaje</string>
|
||||
<string name="bootstrap_finish_title">¡Listo!</string>
|
||||
<string name="encryption_enabled">Cifrado habilitado</string>
|
||||
<string name="room_created_summary_item_by_you">Sala creada y configurada por usted.</string>
|
||||
<string name="room_created_summary_item_by_you">Creaste y configuraste la sala.</string>
|
||||
<string name="qr_code_scanned_verif_waiting">Esperando por %s…</string>
|
||||
<string name="settings_notification_configuration">Ajuste de Notificaciones</string>
|
||||
<string name="room_message_placeholder">Mensaje…</string>
|
||||
@ -1279,7 +1279,7 @@
|
||||
<string name="settings_security_pin_code_summary">Si decea resetear su PIN, toque Olvidé PIN para cerrar sesión y restablecer.</string>
|
||||
<string name="settings_phone_numbers">Numeros telefonicos</string>
|
||||
<string name="settings_emails_and_phone_numbers_title">Correos y numeros telefonicos</string>
|
||||
<string name="settings_emails_and_phone_numbers_summary">Administre el correo y numero telefonico de su cuenta</string>
|
||||
<string name="settings_emails_and_phone_numbers_summary">Administra las direcciones de correo y/o números telefónicos relacionados a tu cuenta de Matrix</string>
|
||||
<string name="settings_show_redacted">Mostrar mensajes eliminados</string>
|
||||
<string name="settings_show_redacted_summary">Indicar marca de mensaje eliminado</string>
|
||||
<string name="uploads_files_title">ARCHIVOS</string>
|
||||
@ -1357,7 +1357,7 @@
|
||||
<string name="room_join_rules_invite_by_you">Hiciste la sala solo por invitación.</string>
|
||||
<string name="login_server_matrix_org_text">Únase gratis a millones de personas en el mayor servidor público</string>
|
||||
<string name="login_signin_sso">Continuar con SSO</string>
|
||||
<string name="login_server_url_form_modular_hint">Dirección de servicios de Element Matrix</string>
|
||||
<string name="login_server_url_form_modular_hint">Dirección de Element Matrix Services</string>
|
||||
<string name="login_server_url_form_common_notice">Ingrese la dirección del servidor que desea utilizar</string>
|
||||
<string name="login_reset_password_notice">Se enviará un correo electrónico de verificación a su bandeja de entrada para confirmar la configuración de su nueva contraseña.</string>
|
||||
<string name="login_reset_password_submit">Siguiente</string>
|
||||
@ -1407,7 +1407,7 @@
|
||||
<string name="login_signup_cancel_confirmation_title">Advertencia</string>
|
||||
<string name="login_signup_cancel_confirmation_content">Tu cuenta aún no está creada. ¿Detener el proceso de registro\?</string>
|
||||
<string name="login_a11y_choose_matrix_org">Seleccione matrix.org</string>
|
||||
<string name="login_a11y_choose_modular">Seleccionar servicios de matriz de elementos</string>
|
||||
<string name="login_a11y_choose_modular">Seleccionar Element Matrix Services</string>
|
||||
<string name="login_a11y_choose_other">Seleccione un servidor doméstico personalizado</string>
|
||||
<string name="login_a11y_captcha_container">Realiza el desafío de captcha</string>
|
||||
<string name="login_terms_title">Acepta los términos para continuar</string>
|
||||
@ -1453,7 +1453,7 @@
|
||||
\nVuelva a iniciar sesión para acceder a los datos y mensajes de su cuenta.</string>
|
||||
<string name="soft_logout_clear_data_dialog_e2e_warning_content">Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de cifrado.</string>
|
||||
<string name="soft_logout_sso_not_same_user_error">La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no está suportado por ${app_name}.
|
||||
\nPrimero borre los datos, luego inicie sesión nuevamente con otra cuenta.</string>
|
||||
\nPrimero borra los datos, luego inicia sesión nuevamente con otra cuenta.</string>
|
||||
<string name="permalink_malformed">Su enlace matrix.to estaba mal formado</string>
|
||||
<string name="settings_developer_mode_summary">El modo desarrollador activa funciones ocultas y también puede hacer que la aplicación sea menos estable. ¡Solo para desarrolladores!</string>
|
||||
<string name="verification_conclusion_compromised">Uno de los siguientes puede verse comprometido:
|
||||
@ -1462,9 +1462,9 @@
|
||||
\n- El servidor privado al que está conectado el usuario que estás verificando
|
||||
\n- Su conexión a internet o la de otros usuarios
|
||||
\n- Su dispositivo o el de otros usuarios</string>
|
||||
<string name="room_profile_encrypted_subtitle">Los mensajes de esta sala están cifrados Extremo-a-Extremo.
|
||||
<string name="room_profile_encrypted_subtitle">Los mensajes de esta sala están cifrados de extremo-a-extremo.
|
||||
\n
|
||||
\nSus mensajes están protegidos y sólo usted y el destinatario tienen las claves únicas para descifrarlos.</string>
|
||||
\nTus mensajes están protegidos y sólo tu y el destinatario tienen las claves únicas para descifrarlos.</string>
|
||||
<string name="verify_cannot_cross_sign">Esta sesión no puede compartir esta verificación con sus otras sesiones.
|
||||
\nLa verificación se guardará localmente y se compartirá en una versión futura de la aplicación.</string>
|
||||
<string name="command_description_rainbow_emote">Envía el emote dado coloreado como un arcoíris</string>
|
||||
@ -1474,7 +1474,7 @@
|
||||
<string name="verification_emoji_notice">Verifica si los mismos emojis aparecen en el mismo orden en ambos usuarios.</string>
|
||||
<string name="verification_code_notice">Compare el código con el que se muestra en la pantalla del otro usuario.</string>
|
||||
<string name="verification_conclusion_ok_notice">Los mensajes con este usuario están cifrados Extremo-a-Extremo y no pueden ser leídos por terceros.</string>
|
||||
<string name="verification_conclusion_ok_self_notice">Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes cifrados y otros usuarios lo verán como de confianza.</string>
|
||||
<string name="verification_conclusion_ok_self_notice">Tu nueva sesión acaba de verificarse y ahora tiene acceso a tus mensajes cifrados y otros usuarios la verán como de confianza.</string>
|
||||
<string name="encryption_information_dg_xsigning_complete">La firma cruzada está habilitada
|
||||
\n Claves privadas en el dispositivo.</string>
|
||||
<string name="encryption_information_dg_xsigning_trusted">La firma cruzada está habilitada
|
||||
@ -1484,8 +1484,8 @@
|
||||
\nLas claves no son de confianza</string>
|
||||
<string name="settings_hs_admin_e2e_disabled">El administrador de su servidor ha desactivado el cifrado Extremo-a-Extremo de forma predeterminada en salas privadas y mensajes directos.</string>
|
||||
<string name="settings_failed_to_get_crypto_device_info">No hay información criptográfica disponible</string>
|
||||
<string name="settings_active_sessions_verified_device_desc">Esta sesión es confiable para mensajería segura porque usted la verificó:</string>
|
||||
<string name="settings_active_sessions_unverified_device_desc">Verifique esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no inició sesión en esta sesión, su cuenta puede verse comprometida:</string>
|
||||
<string name="settings_active_sessions_verified_device_desc">Esta sesión es confiable para mensajería segura porque la verificaste:</string>
|
||||
<string name="settings_active_sessions_unverified_device_desc">Verifica esta sesión para marcarla como confiable y otorgarle acceso a mensajes cifrados. Si no iniciaste sesión en esta sesión, su cuenta puede haber sido comprometida:</string>
|
||||
<plurals name="settings_active_sessions_count">
|
||||
<item quantity="one">%d sesión activa</item>
|
||||
<item quantity="other">%d sesiones activas</item>
|
||||
@ -1510,8 +1510,8 @@
|
||||
<string name="settings_key_requests">Solicitudes clave</string>
|
||||
<string name="e2e_use_keybackup">Desbloquear el historial de mensajes cifrados</string>
|
||||
<string name="verify_new_session_notice">Utilice esta sesión para verificar su nuevo, otorgándole acceso a mensajes cifrados.</string>
|
||||
<string name="verify_cancel_self_verification_from_untrusted">Si cancela, no podrá leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en él</string>
|
||||
<string name="verify_cancel_self_verification_from_trusted">Si cancela, no podrá leer mensajes cifrados en su nuevo dispositivo y otros usuarios no confiarán en él</string>
|
||||
<string name="verify_cancel_self_verification_from_untrusted">Si cancelas, no podrás leer mensajes cifrados en este dispositivo y otros usuarios no confiarán en este</string>
|
||||
<string name="verify_cancel_self_verification_from_trusted">Si cancelas, no podrás leer mensajes cifrados en tu nuevo dispositivo y otros usuarios no confiarán en este</string>
|
||||
<string name="verify_cancel_other">No verificarás %1$s (%2$s) si cancelas ahora. Comience de nuevo en su perfil de usuario.</string>
|
||||
<string name="verify_not_me_self_verification">Uno de los siguientes puede verse comprometido:
|
||||
\n
|
||||
@ -1524,7 +1524,7 @@
|
||||
<string name="verify_cancelled_notice">Se canceló la verificación. Puede iniciar la verificación de nuevo.</string>
|
||||
<string name="enter_account_password">Ingrese su %s para continuar.</string>
|
||||
<string name="bootstrap_dont_reuse_pwd">No use la contraseña de su cuenta.</string>
|
||||
<string name="bootstrap_info_text_2">Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor.</string>
|
||||
<string name="bootstrap_info_text_2">Ingresa una frase de seguridad que solo tú conozcas, que se usa para proteger secretos en tu servidor.</string>
|
||||
<string name="bootstrap_loading_text">Esto puede tardar varios segundos, tenga paciencia.</string>
|
||||
<string name="bootstrap_loading_title">Configurando la recuperación.</string>
|
||||
<string name="keep_it_safe">Manténlo seguro</string>
|
||||
@ -1561,7 +1561,7 @@
|
||||
<string name="auth_invalid_login_param_space_in_password">Nombre de usuario y / o contraseña incorrectos. La contraseña ingresada comienza o termina con espacios, verifíquela.</string>
|
||||
<string name="auth_invalid_login_deactivated_account">Esta cuenta ha sido desactivada.</string>
|
||||
<string name="upgrade_security">Mejora de cifrado disponible</string>
|
||||
<string name="security_prompt_text">Verifíquese a usted mismo y a los demás para mantener sus chats seguros</string>
|
||||
<string name="security_prompt_text">Verifícate a ti mismo y a los demás para mantener tus chats seguros</string>
|
||||
<string name="bootstrap_invalid_recovery_key">No es una clave de recuperación válida</string>
|
||||
<string name="recovery_key_empty_error_message">Por favor introduce una clave de recuperación</string>
|
||||
<string name="bootstrap_progress_checking_backup">Comprobando la clave de respaldo</string>
|
||||
@ -1622,11 +1622,11 @@
|
||||
<string name="bottom_sheet_setup_secure_backup_security_key_title">Usa una llave de seguridad</string>
|
||||
<string name="bottom_sheet_setup_secure_backup_security_key_subtitle">Genere una clave de seguridad para almacenar en un lugar seguro, como un administrador de contraseñas o una caja fuerte.</string>
|
||||
<string name="bottom_sheet_setup_secure_backup_security_phrase_title">Utilice una frase de seguridad</string>
|
||||
<string name="bottom_sheet_setup_secure_backup_security_phrase_subtitle">Ingrese una frase secreta que solo usted conozca y genere una clave de respaldo.</string>
|
||||
<string name="bottom_sheet_setup_secure_backup_security_phrase_subtitle">Ingresa una frase secreta que solo tú conozcas y genera una clave para tu copia de respaldo.</string>
|
||||
<string name="bottom_sheet_save_your_recovery_key_title">Guarde su llave de seguridad</string>
|
||||
<string name="bottom_sheet_save_your_recovery_key_content">Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte.</string>
|
||||
<string name="set_a_security_phrase_title">Establecer una frase de seguridad</string>
|
||||
<string name="set_a_security_phrase_notice">Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor.</string>
|
||||
<string name="set_a_security_phrase_notice">Ingresa una frase de seguridad que sólo tú conozcas, que se usa para proteger secretos en tu servidor.</string>
|
||||
<string name="set_a_security_phrase_hint">Frase de seguridad</string>
|
||||
<string name="set_a_security_phrase_again_notice">Ingrese su Frase de seguridad nuevamente para confirmarla.</string>
|
||||
<string name="room_settings_name_hint">Nombre de la Sala</string>
|
||||
@ -1636,7 +1636,7 @@
|
||||
<string name="notice_crypto_unable_to_decrypt_friendly">Esperando este mensaje, esto puede tardar un poco</string>
|
||||
<string name="notice_crypto_unable_to_decrypt_friendly_desc">Debido al cifrado Extremo-a-Extremo, es posible que deba esperar a que llegue el mensaje de alguien porque las claves de cifrado no se le enviaron correctamente.</string>
|
||||
<string name="crypto_error_withheld_blacklisted">No puede acceder a este mensaje porque ha sido bloqueado por el remitente</string>
|
||||
<string name="crypto_error_withheld_unverified">No puede acceder a este mensaje porque el remitente no confía en su sesión</string>
|
||||
<string name="crypto_error_withheld_unverified">No puedes acceder a este mensaje porque el remitente no confía en tu sesión</string>
|
||||
<string name="crypto_error_withheld_generic">No puede acceder a este mensaje porque el remitente no envió las claves a propósito</string>
|
||||
<string name="notice_crypto_unable_to_decrypt_merged">Esperando al historial de cifrado</string>
|
||||
<string name="disclaimer_content">¡Nos complace anunciar que hemos cambiado de nombre! Tu aplicación está actualizada y accediste a tu cuenta.</string>
|
||||
@ -1711,7 +1711,7 @@
|
||||
<item quantity="one">Mostrar el dispositivo con el que puede verificar ahora</item>
|
||||
<item quantity="other">Mostrar %d dispositivos con los que puede verificar ahora</item>
|
||||
</plurals>
|
||||
<string name="secure_backup_reset_no_history">Reiniciará sin historia, mensajes, dispositivos o usuarios verificados</string>
|
||||
<string name="secure_backup_reset_no_history">Reiniciarás sin historial, ni mensajes, ni dispositivos o usuarios verificados</string>
|
||||
<string name="secure_backup_reset_if_you_reset_all">Si resetea todo</string>
|
||||
<string name="secure_backup_reset_all_no_other_devices">Solo haga esto si no tiene otro dispositivo con el que verificar éste.</string>
|
||||
<string name="secure_backup_reset_all">Resetear todo</string>
|
||||
@ -1748,7 +1748,7 @@
|
||||
<string name="re_authentication_activity_title">Se necesita una nueva autenticación</string>
|
||||
<string name="qr_code_not_scanned">¡Código QR no escaneado!</string>
|
||||
<string name="invalid_qr_code_uri">Código QR no válido (URL no válida)!</string>
|
||||
<string name="cannot_dm_self">No puede DM usted mismo!</string>
|
||||
<string name="cannot_dm_self">No puedes MD a ti mismo!</string>
|
||||
<string name="share_by_text">Compartir por texto</string>
|
||||
<string name="settings_security_pin_code_change_pin_title">Cambiar PIN</string>
|
||||
<string name="settings_security_pin_code_change_pin_summary">Cambie su PIN actual</string>
|
||||
@ -1960,7 +1960,7 @@
|
||||
<string name="room_settings_room_access_title">Acceso a la sala</string>
|
||||
<string name="option_always_ask">Siempre preguntar</string>
|
||||
<string name="spaces_header">Espacios</string>
|
||||
<string name="settings_room_directory_show_all_rooms_summary">mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito.</string>
|
||||
<string name="settings_room_directory_show_all_rooms_summary">Mostrar todas las salas en el directorio de salas, incluyendo salas con contenido explícito.</string>
|
||||
<string name="settings_room_directory_show_all_rooms">Mostrar salas con contenido explícito</string>
|
||||
<string name="settings_category_room_directory">Directorio de la sala</string>
|
||||
<string name="suggested_header">Salas sugeridas</string>
|
||||
@ -2181,7 +2181,7 @@
|
||||
<string name="settings_notification_new_keyword">Agregar nuevas palabra clave</string>
|
||||
<string name="settings_notification_your_keywords">Tus palabras clave</string>
|
||||
<string name="settings_notification_emails_enable_for_email">Habilitar notificación por correo electrónico para %s</string>
|
||||
<string name="settings_notification_emails_no_emails">Para recibir un correo electrónico con una notificación, asocie un correo electrónico a su cuenta de matrix</string>
|
||||
<string name="settings_notification_emails_no_emails">Para recibir notificaciones por correo electrónico, asocia una direccion de correo electrónico a tu cuenta de Matrix</string>
|
||||
<string name="settings_notification_emails_category">Notificación de correo electrónico</string>
|
||||
<string name="room_settings_none">Ninguno</string>
|
||||
<string name="room_settings_mention_and_keyword_only">Solo menciones y palabras clave</string>
|
||||
@ -2219,7 +2219,7 @@
|
||||
<string name="error_voice_message_unable_to_record">No se puede grabar un mensaje de voz</string>
|
||||
<string name="error_voice_message_unable_to_play">No se puede reproducir este mensaje de voz</string>
|
||||
<string name="voice_message_tap_to_stop_toast">Toca tu grabación para detenerla o escucharla</string>
|
||||
<string name="voice_message_n_seconds_warning_toast">%1$ds dejado</string>
|
||||
<string name="voice_message_n_seconds_warning_toast">Restan %1$ds</string>
|
||||
<string name="voice_message_release_to_send_toast">Mantenga presionado para grabar, suelte para enviar</string>
|
||||
<string name="a11y_delete_recorded_voice_message">Eliminar grabación</string>
|
||||
<string name="a11y_recording_voice_message">Grabación de mensaje de voz</string>
|
||||
@ -2238,7 +2238,7 @@
|
||||
<string name="shortcut_disabled_reason_sign_out">¡Se ha cerrado la sesión!</string>
|
||||
<string name="shortcut_disabled_reason_room_left">¡Se ha abandonado la sala!</string>
|
||||
<string name="thread_list_empty_notice">Consejo: Pulse prolongadamente un mensaje y use \"%s\" .</string>
|
||||
<string name="thread_list_empty_title">Mantén las conversaciones organizadas con hilos</string>
|
||||
<string name="thread_list_empty_title">Mantén las conversaciones organizadas usando hilos</string>
|
||||
<string name="thread_list_modal_my_threads_subtitle">Muestra todos los hilos en que has participado</string>
|
||||
<string name="thread_list_modal_my_threads_title">Mis Hilos</string>
|
||||
<string name="thread_list_modal_all_threads_subtitle">Muestra todos los hilos de la sala actual</string>
|
||||
@ -2457,7 +2457,7 @@
|
||||
<string name="beta">BETA</string>
|
||||
<string name="send_feedback_threads_title">Comentarios de la beta de hilos</string>
|
||||
<string name="threads_labs_enable_notice_title">Beta de hilos</string>
|
||||
<string name="initial_sync_request_reason_unignored_users">- Algunos usuarios han sido dejados de ignorar</string>
|
||||
<string name="initial_sync_request_reason_unignored_users">- Algunos usuarios han dejado de ser ignorados</string>
|
||||
<string name="screen_sharing_notification_description">La compartición de pantalla está en progreso</string>
|
||||
<string name="screen_sharing_notification_title">${app_name} Compartición de pantalla</string>
|
||||
<string name="call_stop_screen_sharing">Dejar de compartir pantalla</string>
|
||||
@ -2474,7 +2474,7 @@
|
||||
<string name="live_location_bottom_sheet_last_updated_at">Actualizado hace %1$s</string>
|
||||
<string name="labs_enable_live_location_summary">Implementación temporal: las ubicaciones persisten en el historial de la sala</string>
|
||||
<string name="labs_enable_live_location">Activar compartir ubicación en tiempo real</string>
|
||||
<string name="location_share_live_remaining_time">Queda %1$s</string>
|
||||
<string name="location_share_live_remaining_time">Restan %1$s</string>
|
||||
<string name="location_share_live_until">Compartiendo hasta %1$s</string>
|
||||
<string name="location_share_live_view">Ver ubicación en tiempo real</string>
|
||||
<string name="location_share_live_ended">La ubicación en tiempo real ha terminado</string>
|
||||
@ -2625,8 +2625,8 @@
|
||||
\nPor favor, inténtelo de nuevo.%s</string>
|
||||
<string name="font_size_use_system">Usar ajustes por defecto del sistema</string>
|
||||
<string name="font_size_section_manually">Escoger manualmente</string>
|
||||
<string name="font_size_section_auto">Tamaño automático de fuente</string>
|
||||
<string name="font_size_title">Escoger tamaño de la fuente</string>
|
||||
<string name="font_size_section_auto">Tamaño automático</string>
|
||||
<string name="font_size_title">Escoge tamaño del tipo de letra</string>
|
||||
<plurals name="search_space_multiple_parents">
|
||||
<item quantity="one">%1$s y %2$d otro</item>
|
||||
<item quantity="other">%1$s y %2$d otros</item>
|
||||
@ -2659,18 +2659,33 @@
|
||||
<string name="grant_permission">Otorgar permiso</string>
|
||||
<string name="settings_troubleshoot_test_system_settings_permission_failed">${app_name} necesita permiso para mostrar notificaciones.
|
||||
\nPor favor, otórgalo.</string>
|
||||
<string name="permissions_rationale_msg_notification">${app_name} necesita permisos para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc.
|
||||
<string name="permissions_rationale_msg_notification">${app_name} necesita permiso para mostrar notificaciones. Las notificaciones pueden mostrar tus mensajes, invitaciones, etc.
|
||||
\n
|
||||
\nPor favor, permite el acceso en las siguientes ventanas emergentes para poder visualizar notificaciones.</string>
|
||||
<string name="labs_enable_rich_text_editor_summary">Prueba el editor de texto enriquecido (pronto llegará la opción de texto sin formato plain text)</string>
|
||||
\nPor favor, a continuacion, en las ventanas emergentes, permite el acceso para poder visualizar notificaciones.</string>
|
||||
<string name="labs_enable_rich_text_editor_summary">Prueba el editor de texto enriquecido (pronto llegará la opción de texto simple, sin formato)</string>
|
||||
<string name="labs_enable_rich_text_editor_title">Habilitar editor de texto enriquecido (rich text)</string>
|
||||
<string name="labs_enable_deferred_dm_summary">Crear MD únicamente al primer mensaje</string>
|
||||
<string name="labs_enable_new_app_layout_summary">Una versión simplificada de Element con pestañas opcionales</string>
|
||||
<string name="labs_enable_new_app_layout_title">Habilitar nueva disposición</string>
|
||||
<string name="action_stop">Sí, Parar</string>
|
||||
<string name="action_stop">Sí, Detener</string>
|
||||
<string name="action_deselect_all">Deseleccionar todo</string>
|
||||
<string name="a11y_collapse_space_children">Ocultar los hijos de %s</string>
|
||||
<string name="a11y_expand_space_children">Mostrar los hijos de %s</string>
|
||||
<string name="a11y_collapse_space_children">Ocultar los subespacios de %s</string>
|
||||
<string name="a11y_expand_space_children">Mostrar los subespacios de %s</string>
|
||||
<string name="notice_voice_broadcast_ended_by_you">Has finalizado una transmisión de voz.</string>
|
||||
<string name="notice_voice_broadcast_ended">%1$s ha finalizado una transmisión de voz.</string>
|
||||
<string name="ftue_auth_choose_server_ems_subtitle">Element Matrix Services (EMS) es un servicio de alojamiento para tus comunicaciones en tiempo real. Robusto, confiable, rápido y seguro. Para saber cómo, ve a <a href=\"${ftue_ems_url}\">element.io/ems</a></string>
|
||||
<string name="attachment_type_voice_broadcast">Difusión de voz</string>
|
||||
<string name="push_gateway_item_enabled">Habilitado:</string>
|
||||
<string name="push_gateway_item_device_id">ID de sesión:</string>
|
||||
<string name="error_check_network" tools:ignore="UnusedResources">Algo falló. Por favor, comprueba tu conexión de red e inténtalo nuevamente.</string>
|
||||
<string name="quoting">Citando</string>
|
||||
<string name="replying_to">Respondiendo a %s</string>
|
||||
<string name="editing">Editando</string>
|
||||
<string name="command_description_devtools">Abrir pantalla de herramientas de desarrollador</string>
|
||||
<string name="room_settings_global_block_unverified_info_text">🔒 Tienes habilitado el cifrado a sesiones verificadas sólo para todas las salas en Ajustes de Seguridad.</string>
|
||||
<string name="some_devices_will_not_be_able_to_decrypt">⚠ Hay dispositivos sin verificar en esta sala, los cuales no seran capaces de descifrar los mensajes que envías.</string>
|
||||
<string name="labs_enable_deferred_dm_title">Habilita MDs pospuestos</string>
|
||||
<string name="settings_enable_direct_share_summary">Mostrar chats recientes en el menú de compartir sistema</string>
|
||||
<string name="encryption_never_send_to_unverified_devices_in_room">No enviar nunca mensajes cifrados a sesiones sin verificar en esta sala.</string>
|
||||
<string name="voice_broadcast_recording_time_left">Restan %1$s</string>
|
||||
</resources>
|
@ -5,24 +5,24 @@
|
||||
<string name="notice_room_invite_you">%1$s convidou você</string>
|
||||
<string name="notice_room_join">%1$s entrou na sala</string>
|
||||
<string name="notice_room_leave">%1$s saiu da sala</string>
|
||||
<string name="notice_room_reject">%1$s rejeitou o convite</string>
|
||||
<string name="notice_room_reject">%1$s recusou o convite</string>
|
||||
<string name="notice_room_remove">%1$s removeu %2$s</string>
|
||||
<string name="notice_room_unban">%1$s desbaniu %2$s</string>
|
||||
<string name="notice_room_ban">%1$s baniu %2$s</string>
|
||||
<string name="notice_room_withdraw">%1$s retirou o convite de %2$s</string>
|
||||
<string name="notice_avatar_url_changed">%1$s mudou o avatar dela(e)</string>
|
||||
<string name="notice_display_name_set">%1$s definiu o nome de exibição dela(e) para %2$s</string>
|
||||
<string name="notice_display_name_changed_from">%1$s mudou o nome de exibição dela(e) de %2$s para %3$s</string>
|
||||
<string name="notice_display_name_removed">%1$s removeu o nome de exibição dela(e) (era %2$s)</string>
|
||||
<string name="notice_room_withdraw">%1$s desfez o convite para %2$s</string>
|
||||
<string name="notice_avatar_url_changed">%1$s mudou seu avatar</string>
|
||||
<string name="notice_display_name_set">%1$s definiu seu nome de exibição para %2$s</string>
|
||||
<string name="notice_display_name_changed_from">%1$s mudou seu nome de exibição de %2$s para %3$s</string>
|
||||
<string name="notice_display_name_removed">%1$s removeu seu nome de exibição (era %2$s)</string>
|
||||
<string name="notice_room_topic_changed">%1$s mudou o tópico para: %2$s</string>
|
||||
<string name="notice_room_name_changed">%1$s mudou o nome da sala para: %2$s</string>
|
||||
<string name="notice_placed_video_call">%s começou uma chamada de vídeo.</string>
|
||||
<string name="notice_placed_voice_call">%s começou uma chamada de voz.</string>
|
||||
<string name="notice_answered_call">%s atendeu a chamada.</string>
|
||||
<string name="notice_ended_call">%s terminou a chamada.</string>
|
||||
<string name="notice_made_future_room_visibility">%1$s fez histórico futuro da sala visível para %2$s</string>
|
||||
<string name="notice_room_visibility_invited">todos os membros da sala, do ponto que foram convidados.</string>
|
||||
<string name="notice_room_visibility_joined">todos os membros da sala, do ponto que se juntaram.</string>
|
||||
<string name="notice_ended_call">%s encerrou a chamada.</string>
|
||||
<string name="notice_made_future_room_visibility">%1$s tornou o histórico futuro da sala visível para %2$s</string>
|
||||
<string name="notice_room_visibility_invited">todos os membros da sala, a partir do ponto que foram convidados.</string>
|
||||
<string name="notice_room_visibility_joined">todos os membros da sala, a partir do ponto que entraram.</string>
|
||||
<string name="notice_room_visibility_shared">todos os membros da sala.</string>
|
||||
<string name="notice_room_visibility_world_readable">qualquer pessoa.</string>
|
||||
<string name="notice_avatar_changed_too">(avatar mudou também)</string>
|
||||
@ -45,11 +45,11 @@
|
||||
<string name="notice_room_invite_by_you">Você convidou %1$s</string>
|
||||
<string name="notice_room_join_by_you">Você entrou na sala</string>
|
||||
<string name="notice_room_leave_by_you">Você saiu da sala</string>
|
||||
<string name="notice_room_reject_by_you">Você rejeitou o convite</string>
|
||||
<string name="notice_room_reject_by_you">Você recusou o convite</string>
|
||||
<string name="notice_room_remove_by_you">Você removeu %1$s</string>
|
||||
<string name="notice_room_unban_by_you">Você desbaniu %1$s</string>
|
||||
<string name="notice_room_ban_by_you">Você baniu %1$s</string>
|
||||
<string name="notice_room_withdraw_by_you">Você retirou o convite de %1$s</string>
|
||||
<string name="notice_room_withdraw_by_you">Você desfez o convite para %1$s</string>
|
||||
<string name="notice_avatar_url_changed_by_you">Você mudou seu avatar</string>
|
||||
<string name="notice_display_name_set_by_you">Você definiu seu nome de exibição para %1$s</string>
|
||||
<string name="notice_display_name_changed_from_by_you">Você mudou seu nome de exibição de %1$s para %2$s</string>
|
||||
@ -63,8 +63,8 @@
|
||||
<string name="notice_call_candidates">%s enviou dados para configurar a chamada.</string>
|
||||
<string name="notice_call_candidates_by_you">Você enviou dados para configurar a chamada.</string>
|
||||
<string name="notice_answered_call_by_you">Você atendeu a chamada.</string>
|
||||
<string name="notice_ended_call_by_you">Você terminou a chamada.</string>
|
||||
<string name="notice_made_future_room_visibility_by_you">Você fez histórico futuro da sala visível para %1$s</string>
|
||||
<string name="notice_ended_call_by_you">Você encerrou a chamada.</string>
|
||||
<string name="notice_made_future_room_visibility_by_you">Você tornou o histórico futuro da sala visível para %1$s</string>
|
||||
<string name="notice_room_update">%s fez o upgrade desta sala.</string>
|
||||
<string name="notice_room_update_by_you">Você fez o upgrade desta sala.</string>
|
||||
<string name="notice_room_name_removed_by_you">Você removeu o nome da sala</string>
|
||||
@ -170,8 +170,8 @@
|
||||
<string name="notice_direct_room_third_party_invite">%1$s convidou %2$s</string>
|
||||
<string name="notice_direct_room_update_by_you">Você fez o upgrade aqui.</string>
|
||||
<string name="notice_direct_room_update">%s fez o upgrade aqui.</string>
|
||||
<string name="notice_made_future_direct_room_visibility_by_you">Você fez mensagens futuras visíveis para %1$s</string>
|
||||
<string name="notice_made_future_direct_room_visibility">%1$s fez mensagens futuras visíveis para %2$s</string>
|
||||
<string name="notice_made_future_direct_room_visibility_by_you">Você tornou as mensagens futuras visíveis para %1$s</string>
|
||||
<string name="notice_made_future_direct_room_visibility">%1$s tornou as mensagens futuras visíveis para %2$s</string>
|
||||
<string name="notice_direct_room_leave_by_you">Você saiu da sala</string>
|
||||
<string name="notice_direct_room_leave">%1$s saiu da sala</string>
|
||||
<string name="notice_direct_room_join_by_you">Você entrou</string>
|
||||
@ -408,7 +408,7 @@
|
||||
<string name="room_settings_read_history_entry_anyone">Qualquer pessoa</string>
|
||||
<string name="room_settings_read_history_entry_members_only_option_time_shared">Membros somente (desde o ponto no tempo de seleção desta opção)</string>
|
||||
<string name="room_settings_read_history_entry_members_only_invited">Membros somente (desde que eles foram convidados)</string>
|
||||
<string name="room_settings_read_history_entry_members_only_joined">Membros somente (desde que eles se juntaram)</string>
|
||||
<string name="room_settings_read_history_entry_members_only_joined">Membros somente (desde que eles entraram)</string>
|
||||
<string name="room_settings_banned_users_title">Usuárias(os) banidas(os)</string>
|
||||
<string name="room_settings_category_advanced_title">Avançadas</string>
|
||||
<string name="room_settings_room_internal_id">ID interno desta sala</string>
|
||||
@ -2009,8 +2009,8 @@
|
||||
<string name="space_add_child_title">Adicionar salas</string>
|
||||
<string name="space_explore_activity_title">Explorar salas</string>
|
||||
<plurals name="space_people_you_know">
|
||||
<item quantity="one">%d pessoa que você conhece já tem se juntado</item>
|
||||
<item quantity="other">%d pessoas que você conhece já têm se juntado</item>
|
||||
<item quantity="one">%d pessoa que você conhece já entrou</item>
|
||||
<item quantity="other">%d pessoas que você conhece já entraram</item>
|
||||
</plurals>
|
||||
<string name="join_space">Juntar-Se a Espaço</string>
|
||||
<string name="create_space">Criar espaço</string>
|
||||
@ -2833,8 +2833,8 @@
|
||||
<string name="action_deselect_all">Desselecionar todas(os)</string>
|
||||
<string name="action_select_all">Selecionar todas(os)</string>
|
||||
<plurals name="x_selected">
|
||||
<item quantity="one">%1$d selecionada(o)</item>
|
||||
<item quantity="other">%1$d selecionadas(os)</item>
|
||||
<item quantity="one">%1$d selecionado(a)</item>
|
||||
<item quantity="other">%1$d selecionados(as)</item>
|
||||
</plurals>
|
||||
<string name="error_voice_broadcast_blocked_by_someone_else_message">Alguma outra pessoa já está gravando um broadcast de voz. Espere que o broadcast de voz dela termine para começar um novo.</string>
|
||||
<string name="rich_text_editor_full_screen_toggle">Alternar modo de tela cheia</string>
|
||||
|
@ -2504,7 +2504,7 @@
|
||||
<string name="home_empty_space_no_rooms_title">%s
|
||||
\nduket paksa si i zbrazët.</string>
|
||||
<string name="labs_enable_voice_broadcast_summary">Jini në gjendje të incizoni dhe dërgoni transmetim zanor në rrjedhën kohore të dhomës.</string>
|
||||
<string name="labs_enable_voice_broadcast_title">Aktivizoni transmetim zanor</string>
|
||||
<string name="labs_enable_voice_broadcast_title">Aktivizoni transmetim zanor (nën zhvillim aktiv)</string>
|
||||
<string name="labs_enable_client_info_recording_title">Aktivizo regjistrim hollësish klienti</string>
|
||||
<string name="labs_enable_session_manager_summary">Shihini më qartë dhe kontrolloni më mirë krejt sesionet tuaj.</string>
|
||||
<string name="labs_enable_session_manager_title">Aktivizo përgjegjës të ri sesionesh</string>
|
||||
|
@ -2338,6 +2338,7 @@
|
||||
<item quantity="one">"One person"</item>
|
||||
<item quantity="other">"%1$d people"</item>
|
||||
</plurals>
|
||||
<string name="room_profile_section_more_polls">Poll history</string>
|
||||
<string name="room_profile_section_more_uploads">Uploads</string>
|
||||
<string name="room_profile_section_more_leave">Leave Room</string>
|
||||
<string name="direct_room_profile_section_more_leave">Leave</string>
|
||||
@ -3193,6 +3194,10 @@
|
||||
<string name="open_poll_option_description">Voters see results as soon as they have voted</string>
|
||||
<string name="closed_poll_option_title">Closed poll</string>
|
||||
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
|
||||
<string name="room_polls_active">Active polls</string>
|
||||
<string name="room_polls_active_no_item">There are no active polls in this room</string>
|
||||
<string name="room_polls_ended">Past polls</string>
|
||||
<string name="room_polls_ended_no_item">There are no past polls in this room</string>
|
||||
|
||||
<!-- Location -->
|
||||
<string name="location_activity_title_static_sharing">Share location</string>
|
||||
@ -3506,4 +3511,7 @@
|
||||
<string name="message_reply_to_sender_sent_video">sent a video.</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">sent a sticker.</string>
|
||||
<string name="message_reply_to_sender_created_poll">created a poll.</string>
|
||||
|
||||
<string name="settings_access_token">Access Token</string>
|
||||
<string name="settings_access_token_summary">Your access token gives full access to your account. Do not share it with anyone.</string>
|
||||
</resources>
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.api.session.crypto.crosssigning
|
||||
|
||||
/**
|
||||
* Container for the three cross signing keys: master, self signing and user signing.
|
||||
*/
|
||||
data class UserIdentity(
|
||||
val masterKey: CryptoCrossSigningKey?,
|
||||
val selfSigningKey: CryptoCrossSigningKey?,
|
||||
val userSigningKey: CryptoCrossSigningKey?,
|
||||
)
|
@ -89,6 +89,7 @@ import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.toRest
|
||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
|
||||
@ -192,21 +193,21 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
private val isStarting = AtomicBoolean(false)
|
||||
private val isStarted = AtomicBoolean(false)
|
||||
|
||||
fun onStateEvent(roomId: String, event: Event) {
|
||||
fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) {
|
||||
when (event.type) {
|
||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator)
|
||||
}
|
||||
}
|
||||
|
||||
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
|
||||
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean, cryptoStoreAggregator: CryptoStoreAggregator?) {
|
||||
// handle state events
|
||||
if (event.isStateEvent()) {
|
||||
when (event.type) {
|
||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator)
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,8 +431,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* A sync response has been received.
|
||||
*
|
||||
* @param syncResponse the syncResponse
|
||||
* @param cryptoStoreAggregator data aggregated during the sync response treatment to store
|
||||
*/
|
||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||
fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) {
|
||||
cryptoStore.storeData(cryptoStoreAggregator)
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching {
|
||||
if (syncResponse.deviceLists != null) {
|
||||
@ -998,15 +1001,26 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
|
||||
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) {
|
||||
if (!event.isStateEvent()) return
|
||||
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
|
||||
val historyVisibility = eventContent?.historyVisibility
|
||||
if (historyVisibility == null) {
|
||||
cryptoStore.setShouldShareHistory(roomId, false)
|
||||
if (cryptoStoreAggregator != null) {
|
||||
cryptoStoreAggregator.setShouldShareHistoryData[roomId] = false
|
||||
} else {
|
||||
// Store immediately
|
||||
cryptoStore.setShouldShareHistory(roomId, false)
|
||||
}
|
||||
} else {
|
||||
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
|
||||
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
|
||||
if (cryptoStoreAggregator != null) {
|
||||
cryptoStoreAggregator.setShouldEncryptForInvitedMembersData[roomId] = historyVisibility != RoomHistoryVisibility.JOINED
|
||||
cryptoStoreAggregator.setShouldShareHistoryData[roomId] = historyVisibility.shouldShareHistory()
|
||||
} else {
|
||||
// Store immediately
|
||||
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
|
||||
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,11 +25,13 @@ import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.extensions.measureMetric
|
||||
import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.store.UserDataToStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||
@ -371,6 +373,8 @@ internal class DeviceListManager @Inject constructor(
|
||||
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
|
||||
}
|
||||
|
||||
val userDataToStore = UserDataToStore()
|
||||
|
||||
for (userId in filteredUsers) {
|
||||
// al devices =
|
||||
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
|
||||
@ -404,7 +408,7 @@ internal class DeviceListManager @Inject constructor(
|
||||
}
|
||||
// Update the store
|
||||
// Note that devices which aren't in the response will be removed from the stores
|
||||
cryptoStore.storeUserDevices(userId, workingCopy)
|
||||
userDataToStore.userDevices[userId] = workingCopy
|
||||
}
|
||||
|
||||
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
|
||||
@ -416,14 +420,15 @@ internal class DeviceListManager @Inject constructor(
|
||||
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
|
||||
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
|
||||
}
|
||||
cryptoStore.storeUserCrossSigningKeys(
|
||||
userId,
|
||||
masterKey,
|
||||
selfSigningKey,
|
||||
userSigningKey
|
||||
userDataToStore.userIdentities[userId] = UserIdentity(
|
||||
masterKey = masterKey,
|
||||
selfSigningKey = selfSigningKey,
|
||||
userSigningKey = userSigningKey
|
||||
)
|
||||
}
|
||||
|
||||
cryptoStore.storeData(userDataToStore)
|
||||
|
||||
// Update devices trust for these users
|
||||
// dispatchDeviceChange(downloadUsers)
|
||||
|
||||
|
@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmOutboundGroupSession
|
||||
@ -230,11 +231,12 @@ internal interface IMXCryptoStore {
|
||||
*/
|
||||
fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?)
|
||||
|
||||
fun storeUserCrossSigningKeys(
|
||||
/**
|
||||
* Store the cross signing keys for the user userId.
|
||||
*/
|
||||
fun storeUserIdentity(
|
||||
userId: String,
|
||||
masterKey: CryptoCrossSigningKey?,
|
||||
selfSigningKey: CryptoCrossSigningKey?,
|
||||
userSigningKey: CryptoCrossSigningKey?
|
||||
userIdentity: UserIdentity
|
||||
)
|
||||
|
||||
/**
|
||||
@ -290,6 +292,13 @@ internal interface IMXCryptoStore {
|
||||
|
||||
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
|
||||
|
||||
/**
|
||||
* Sets a boolean flag that will determine whether or not this device should encrypt Events for
|
||||
* invited members.
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @param shouldEncryptForInvitedMembers The boolean flag
|
||||
*/
|
||||
fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)
|
||||
|
||||
fun shouldShareHistory(roomId: String): Boolean
|
||||
@ -580,4 +589,14 @@ internal interface IMXCryptoStore {
|
||||
fun areDeviceKeysUploaded(): Boolean
|
||||
fun tidyUpDataBase()
|
||||
fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest>
|
||||
|
||||
/**
|
||||
* Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator].
|
||||
*/
|
||||
fun storeData(cryptoStoreAggregator: CryptoStoreAggregator)
|
||||
|
||||
/**
|
||||
* Store a bunch of data related to the users. @See [UserDataToStore].
|
||||
*/
|
||||
fun storeData(userDataToStore: UserDataToStore)
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.crypto.store
|
||||
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
|
||||
internal data class UserDataToStore(
|
||||
/**
|
||||
* Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]).
|
||||
*/
|
||||
val userDevices: MutableMap<String, Map<String, CryptoDeviceInfo>> = mutableMapOf(),
|
||||
/**
|
||||
* Map of userId -> [UserIdentity].
|
||||
*/
|
||||
val userIdentities: MutableMap<String, UserIdentity> = mutableMapOf(),
|
||||
)
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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 org.matrix.android.sdk.internal.crypto.store.db
|
||||
|
||||
data class CryptoStoreAggregator(
|
||||
val setShouldShareHistoryData: MutableMap<String, Boolean> = mutableMapOf(),
|
||||
val setShouldEncryptForInvitedMembersData: MutableMap<String, Boolean> = mutableMapOf(),
|
||||
) {
|
||||
fun isEmpty(): Boolean {
|
||||
return setShouldShareHistoryData.isEmpty() &&
|
||||
setShouldEncryptForInvitedMembersData.isEmpty()
|
||||
}
|
||||
}
|
@ -20,10 +20,12 @@ import android.util.Base64
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.RealmObject
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
/**
|
||||
* Get realm, invoke the action, close realm, and return the result of the action.
|
||||
@ -55,10 +57,12 @@ internal fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: Realm
|
||||
/**
|
||||
* Get realm instance, invoke the action in a transaction and close realm.
|
||||
*/
|
||||
internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {
|
||||
Realm.getInstance(realmConfiguration).use { realm ->
|
||||
realm.executeTransaction { action.invoke(it) }
|
||||
}
|
||||
internal fun doRealmTransaction(tag: String, realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {
|
||||
measureTimeMillis {
|
||||
Realm.getInstance(realmConfiguration).use { realm ->
|
||||
realm.executeTransaction { action.invoke(it) }
|
||||
}
|
||||
}.also { Timber.w("doRealmTransaction for $tag took $it millis") }
|
||||
}
|
||||
|
||||
internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) {
|
||||
|
@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig
|
||||
import org.matrix.android.sdk.api.session.crypto.NewSessionListener
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest
|
||||
import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
|
||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||
@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.store.UserDataToStore
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
|
||||
@ -147,7 +148,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
|
||||
init {
|
||||
// Ensure CryptoMetadataEntity is inserted in DB
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("init", realmConfiguration) { realm ->
|
||||
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
|
||||
|
||||
var deleteAll = false
|
||||
@ -189,7 +190,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun deleteStore() {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("deleteStore", realmConfiguration) {
|
||||
it.deleteAll()
|
||||
}
|
||||
}
|
||||
@ -218,7 +219,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun storeDeviceId(deviceId: String) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("storeDeviceId", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.deviceId = deviceId
|
||||
}
|
||||
}
|
||||
@ -230,7 +231,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun saveOlmAccount() {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("saveOlmAccount", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.putOlmAccount(olmAccount)
|
||||
}
|
||||
}
|
||||
@ -248,7 +249,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
|
||||
@Synchronized
|
||||
override fun getOrCreateOlmAccount(): OlmAccount {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("getOrCreateOlmAccount", realmConfiguration) {
|
||||
val metaData = it.where<CryptoMetadataEntity>().findFirst()
|
||||
val existing = metaData!!.getOlmAccount()
|
||||
if (existing == null) {
|
||||
@ -288,129 +289,139 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
if (devices == null) {
|
||||
Timber.d("Remove user $userId")
|
||||
// Remove the user
|
||||
UserEntity.delete(realm, userId)
|
||||
} else {
|
||||
val userEntity = UserEntity.getOrCreate(realm, userId)
|
||||
// First delete the removed devices
|
||||
val deviceIds = devices.keys
|
||||
userEntity.devices.toTypedArray().iterator().let {
|
||||
while (it.hasNext()) {
|
||||
val deviceInfoEntity = it.next()
|
||||
if (deviceInfoEntity.deviceId !in deviceIds) {
|
||||
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
|
||||
deviceInfoEntity.deleteOnCascade()
|
||||
}
|
||||
doRealmTransaction("storeUserDevices", realmConfiguration) { realm ->
|
||||
storeUserDevices(realm, userId, devices)
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeUserDevices(realm: Realm, userId: String, devices: Map<String, CryptoDeviceInfo>?) {
|
||||
if (devices == null) {
|
||||
Timber.d("Remove user $userId")
|
||||
// Remove the user
|
||||
UserEntity.delete(realm, userId)
|
||||
} else {
|
||||
val userEntity = UserEntity.getOrCreate(realm, userId)
|
||||
// First delete the removed devices
|
||||
val deviceIds = devices.keys
|
||||
userEntity.devices.toTypedArray().iterator().let {
|
||||
while (it.hasNext()) {
|
||||
val deviceInfoEntity = it.next()
|
||||
if (deviceInfoEntity.deviceId !in deviceIds) {
|
||||
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
|
||||
deviceInfoEntity.deleteOnCascade()
|
||||
}
|
||||
}
|
||||
// Then update existing devices or add new one
|
||||
devices.values.forEach { cryptoDeviceInfo ->
|
||||
val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
|
||||
if (existingDeviceInfoEntity == null) {
|
||||
// Add the device
|
||||
Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
|
||||
val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
|
||||
newEntity.firstTimeSeenLocalTs = clock.epochMillis()
|
||||
userEntity.devices.add(newEntity)
|
||||
} else {
|
||||
// Update the device
|
||||
Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
|
||||
CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
|
||||
}
|
||||
}
|
||||
// Then update existing devices or add new one
|
||||
devices.values.forEach { cryptoDeviceInfo ->
|
||||
val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId }
|
||||
if (existingDeviceInfoEntity == null) {
|
||||
// Add the device
|
||||
Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
|
||||
val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
|
||||
newEntity.firstTimeSeenLocalTs = clock.epochMillis()
|
||||
userEntity.devices.add(newEntity)
|
||||
} else {
|
||||
// Update the device
|
||||
Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId")
|
||||
CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeUserCrossSigningKeys(
|
||||
override fun storeUserIdentity(
|
||||
userId: String,
|
||||
masterKey: CryptoCrossSigningKey?,
|
||||
selfSigningKey: CryptoCrossSigningKey?,
|
||||
userSigningKey: CryptoCrossSigningKey?
|
||||
userIdentity: UserIdentity,
|
||||
) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
UserEntity.getOrCreate(realm, userId)
|
||||
.let { userEntity ->
|
||||
if (masterKey == null || selfSigningKey == null) {
|
||||
// The user has disabled cross signing?
|
||||
userEntity.crossSigningInfoEntity?.deleteOnCascade()
|
||||
userEntity.crossSigningInfoEntity = null
|
||||
} else {
|
||||
var shouldResetMyDevicesLocalTrust = false
|
||||
CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo ->
|
||||
// What should we do if we detect a change of the keys?
|
||||
val existingMaster = signingInfo.getMasterKey()
|
||||
if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) {
|
||||
crossSigningKeysMapper.update(existingMaster, masterKey)
|
||||
} else {
|
||||
Timber.d("## CrossSigning MSK change for $userId")
|
||||
val keyEntity = crossSigningKeysMapper.map(masterKey)
|
||||
signingInfo.setMasterKey(keyEntity)
|
||||
if (userId == this.userId) {
|
||||
shouldResetMyDevicesLocalTrust = true
|
||||
// my msk has changed! clear my private key
|
||||
// Could we have some race here? e.g I am the one that did change the keys
|
||||
// could i get this update to early and clear the private keys?
|
||||
// -> initializeCrossSigning is guarding for that by storing all at once
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignMasterPrivateKey = null
|
||||
}
|
||||
doRealmTransaction("storeUserIdentity", realmConfiguration) { realm ->
|
||||
storeUserIdentity(realm, userId, userIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeUserIdentity(
|
||||
realm: Realm,
|
||||
userId: String,
|
||||
userIdentity: UserIdentity,
|
||||
) {
|
||||
UserEntity.getOrCreate(realm, userId)
|
||||
.let { userEntity ->
|
||||
if (userIdentity.masterKey == null || userIdentity.selfSigningKey == null) {
|
||||
// The user has disabled cross signing?
|
||||
userEntity.crossSigningInfoEntity?.deleteOnCascade()
|
||||
userEntity.crossSigningInfoEntity = null
|
||||
} else {
|
||||
var shouldResetMyDevicesLocalTrust = false
|
||||
CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo ->
|
||||
// What should we do if we detect a change of the keys?
|
||||
val existingMaster = signingInfo.getMasterKey()
|
||||
if (existingMaster != null && existingMaster.publicKeyBase64 == userIdentity.masterKey.unpaddedBase64PublicKey) {
|
||||
crossSigningKeysMapper.update(existingMaster, userIdentity.masterKey)
|
||||
} else {
|
||||
Timber.d("## CrossSigning MSK change for $userId")
|
||||
val keyEntity = crossSigningKeysMapper.map(userIdentity.masterKey)
|
||||
signingInfo.setMasterKey(keyEntity)
|
||||
if (userId == this.userId) {
|
||||
shouldResetMyDevicesLocalTrust = true
|
||||
// my msk has changed! clear my private key
|
||||
// Could we have some race here? e.g I am the one that did change the keys
|
||||
// could i get this update to early and clear the private keys?
|
||||
// -> initializeCrossSigning is guarding for that by storing all at once
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignMasterPrivateKey = null
|
||||
}
|
||||
}
|
||||
|
||||
val existingSelfSigned = signingInfo.getSelfSignedKey()
|
||||
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) {
|
||||
crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey)
|
||||
} else {
|
||||
Timber.d("## CrossSigning SSK change for $userId")
|
||||
val keyEntity = crossSigningKeysMapper.map(selfSigningKey)
|
||||
signingInfo.setSelfSignedKey(keyEntity)
|
||||
if (userId == this.userId) {
|
||||
shouldResetMyDevicesLocalTrust = true
|
||||
// my ssk has changed! clear my private key
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignSelfSignedPrivateKey = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only for me
|
||||
if (userSigningKey != null) {
|
||||
val existingUSK = signingInfo.getUserSigningKey()
|
||||
if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) {
|
||||
crossSigningKeysMapper.update(existingUSK, userSigningKey)
|
||||
} else {
|
||||
Timber.d("## CrossSigning USK change for $userId")
|
||||
val keyEntity = crossSigningKeysMapper.map(userSigningKey)
|
||||
signingInfo.setUserSignedKey(keyEntity)
|
||||
if (userId == this.userId) {
|
||||
shouldResetMyDevicesLocalTrust = true
|
||||
// my usk has changed! clear my private key
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignUserPrivateKey = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When my cross signing keys are reset, we consider clearing all existing device trust
|
||||
if (shouldResetMyDevicesLocalTrust) {
|
||||
realm.where<UserEntity>()
|
||||
.equalTo(UserEntityFields.USER_ID, this.userId)
|
||||
.findFirst()
|
||||
?.devices?.forEach {
|
||||
it?.trustLevelEntity?.crossSignedVerified = false
|
||||
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
|
||||
}
|
||||
}
|
||||
userEntity.crossSigningInfoEntity = signingInfo
|
||||
}
|
||||
|
||||
val existingSelfSigned = signingInfo.getSelfSignedKey()
|
||||
if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == userIdentity.selfSigningKey.unpaddedBase64PublicKey) {
|
||||
crossSigningKeysMapper.update(existingSelfSigned, userIdentity.selfSigningKey)
|
||||
} else {
|
||||
Timber.d("## CrossSigning SSK change for $userId")
|
||||
val keyEntity = crossSigningKeysMapper.map(userIdentity.selfSigningKey)
|
||||
signingInfo.setSelfSignedKey(keyEntity)
|
||||
if (userId == this.userId) {
|
||||
shouldResetMyDevicesLocalTrust = true
|
||||
// my ssk has changed! clear my private key
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignSelfSignedPrivateKey = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only for me
|
||||
if (userIdentity.userSigningKey != null) {
|
||||
val existingUSK = signingInfo.getUserSigningKey()
|
||||
if (existingUSK != null && existingUSK.publicKeyBase64 == userIdentity.userSigningKey.unpaddedBase64PublicKey) {
|
||||
crossSigningKeysMapper.update(existingUSK, userIdentity.userSigningKey)
|
||||
} else {
|
||||
Timber.d("## CrossSigning USK change for $userId")
|
||||
val keyEntity = crossSigningKeysMapper.map(userIdentity.userSigningKey)
|
||||
signingInfo.setUserSignedKey(keyEntity)
|
||||
if (userId == this.userId) {
|
||||
shouldResetMyDevicesLocalTrust = true
|
||||
// my usk has changed! clear my private key
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignUserPrivateKey = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When my cross signing keys are reset, we consider clearing all existing device trust
|
||||
if (shouldResetMyDevicesLocalTrust) {
|
||||
realm.where<UserEntity>()
|
||||
.equalTo(UserEntityFields.USER_ID, this.userId)
|
||||
.findFirst()
|
||||
?.devices?.forEach {
|
||||
it?.trustLevelEntity?.crossSignedVerified = false
|
||||
it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId
|
||||
}
|
||||
}
|
||||
userEntity.crossSigningInfoEntity = signingInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||
@ -480,7 +491,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
|
||||
override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) {
|
||||
Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}")
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("storePrivateKeysInfo", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignMasterPrivateKey = msk
|
||||
xSignUserPrivateKey = usk
|
||||
@ -490,7 +501,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("saveBackupRecoveryKey", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
keyBackupRecoveryKey = recoveryKey
|
||||
keyBackupRecoveryKeyVersion = version
|
||||
@ -516,7 +527,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
|
||||
override fun storeMSKPrivateKey(msk: String?) {
|
||||
Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ")
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("storeMSKPrivateKey", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignMasterPrivateKey = msk
|
||||
}
|
||||
@ -525,7 +536,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
|
||||
override fun storeSSKPrivateKey(ssk: String?) {
|
||||
Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ")
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("storeSSKPrivateKey", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignSelfSignedPrivateKey = ssk
|
||||
}
|
||||
@ -534,7 +545,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
|
||||
override fun storeUSKPrivateKey(usk: String?) {
|
||||
Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ")
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("storeUSKPrivateKey", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
|
||||
xSignUserPrivateKey = usk
|
||||
}
|
||||
@ -667,7 +678,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun storeRoomAlgorithm(roomId: String, algorithm: String?) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("storeRoomAlgorithm", realmConfiguration) {
|
||||
CryptoRoomEntity.getOrCreate(it, roomId).let { entity ->
|
||||
entity.algorithm = algorithm
|
||||
// store anyway the new algorithm, but mark the room
|
||||
@ -708,7 +719,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) {
|
||||
CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers
|
||||
}
|
||||
}
|
||||
@ -716,7 +727,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) {
|
||||
Timber.tag(loggerTag.value)
|
||||
.v("setShouldShareHistory for room $roomId is $shouldShareHistory")
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("setShouldShareHistory", realmConfiguration) {
|
||||
CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory
|
||||
}
|
||||
}
|
||||
@ -733,7 +744,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
if (sessionIdentifier != null) {
|
||||
val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey)
|
||||
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("storeSession", realmConfiguration) {
|
||||
val realmOlmSession = OlmSessionEntity().apply {
|
||||
primaryKey = key
|
||||
sessionId = sessionIdentifier
|
||||
@ -790,7 +801,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("storeInboundGroupSessions", realmConfiguration) { realm ->
|
||||
sessions.forEach { wrapper ->
|
||||
|
||||
val sessionIdentifier = try {
|
||||
@ -914,7 +925,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
override fun removeInboundGroupSession(sessionId: String, senderKey: String) {
|
||||
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
|
||||
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("removeInboundGroupSession", realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
|
||||
.findAll()
|
||||
@ -933,7 +944,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setKeyBackupVersion(keyBackupVersion: String?) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("setKeyBackupVersion", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.backupVersion = keyBackupVersion
|
||||
}
|
||||
}
|
||||
@ -945,7 +956,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("setKeysBackupData", realmConfiguration) {
|
||||
if (keysBackupData == null) {
|
||||
// Clear the table
|
||||
it.where<KeysBackupDataEntity>()
|
||||
@ -959,7 +970,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun resetBackupMarkers() {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("resetBackupMarkers", realmConfiguration) {
|
||||
it.where<OlmInboundGroupSessionEntity>()
|
||||
.findAll()
|
||||
.map { inboundGroupSession ->
|
||||
@ -973,7 +984,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("markBackupDoneForInboundGroupSessions", realmConfiguration) { realm ->
|
||||
olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper ->
|
||||
try {
|
||||
val sessionIdentifier =
|
||||
@ -1032,13 +1043,13 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.globalBlacklistUnverifiedDevices = block
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableKeyGossiping(enable: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("enableKeyGossiping", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.globalEnableKeyGossiping = enable
|
||||
}
|
||||
}
|
||||
@ -1062,13 +1073,13 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun enableShareKeyOnInvite(enable: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("enableShareKeyOnInvite", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.enableKeyForwardingOnInvite = enable
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDeviceKeysUploaded(uploaded: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("setDeviceKeysUploaded", realmConfiguration) {
|
||||
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded
|
||||
}
|
||||
}
|
||||
@ -1115,7 +1126,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm ->
|
||||
CryptoRoomEntity.getById(realm, roomId)
|
||||
?.blacklistUnverifiedDevices = block
|
||||
}
|
||||
@ -1135,7 +1146,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) {
|
||||
doRealmTransaction(realmConfiguration) {
|
||||
doRealmTransaction("saveDeviceTrackingStatuses", realmConfiguration) {
|
||||
deviceTrackingStatuses
|
||||
.map { entry ->
|
||||
UserEntity.getOrCreate(it, entry.key)
|
||||
@ -1268,7 +1279,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
): OutgoingKeyRequest {
|
||||
// Insert the request and return the one passed in parameter
|
||||
lateinit var request: OutgoingKeyRequest
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("getOrAddOutgoingRoomKeyRequest", realmConfiguration) { realm ->
|
||||
|
||||
val existing = realm.where<OutgoingKeyRequestEntity>()
|
||||
.equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId)
|
||||
@ -1306,7 +1317,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("updateOutgoingRoomKeyRequestState", realmConfiguration) { realm ->
|
||||
realm.where<OutgoingKeyRequestEntity>()
|
||||
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
|
||||
.findFirst()?.apply {
|
||||
@ -1320,7 +1331,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("updateOutgoingRoomKeyRequiredIndex", realmConfiguration) { realm ->
|
||||
realm.where<OutgoingKeyRequestEntity>()
|
||||
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
|
||||
.findFirst()?.apply {
|
||||
@ -1337,7 +1348,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
fromDevice: String?,
|
||||
event: Event
|
||||
) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("updateOutgoingRoomKeyReply", realmConfiguration) { realm ->
|
||||
realm.where<OutgoingKeyRequestEntity>()
|
||||
.equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId)
|
||||
@ -1353,7 +1364,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun deleteOutgoingRoomKeyRequest(requestId: String) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("deleteOutgoingRoomKeyRequest", realmConfiguration) { realm ->
|
||||
realm.where<OutgoingKeyRequestEntity>()
|
||||
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId)
|
||||
.findFirst()?.deleteOnCascade()
|
||||
@ -1361,7 +1372,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("deleteOutgoingRoomKeyRequestInState", realmConfiguration) { realm ->
|
||||
realm.where<OutgoingKeyRequestEntity>()
|
||||
.equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name)
|
||||
.findAll()
|
||||
@ -1497,7 +1508,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("setMyCrossSigningInfo", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { userId ->
|
||||
addOrUpdateCrossSigningInfo(realm, userId, info)
|
||||
}
|
||||
@ -1505,7 +1516,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("setUserKeysAsTrusted", realmConfiguration) { realm ->
|
||||
val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java)
|
||||
.equalTo(CrossSigningInfoEntityFields.USER_ID, userId)
|
||||
.findFirst()
|
||||
@ -1525,7 +1536,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("setDeviceTrust", realmConfiguration) { realm ->
|
||||
realm.where(DeviceInfoEntity::class.java)
|
||||
.equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId))
|
||||
.findFirst()?.let { deviceInfoEntity ->
|
||||
@ -1545,7 +1556,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun clearOtherUserTrust() {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("clearOtherUserTrust", realmConfiguration) { realm ->
|
||||
val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java)
|
||||
.findAll()
|
||||
xInfoEntities?.forEach { info ->
|
||||
@ -1560,7 +1571,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun updateUsersTrust(check: (String) -> Boolean) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("updateUsersTrust", realmConfiguration) { realm ->
|
||||
val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java)
|
||||
.findAll()
|
||||
xInfoEntities?.forEach { xInfoEntity ->
|
||||
@ -1668,13 +1679,13 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
}
|
||||
|
||||
override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("setCrossSigningInfo", realmConfiguration) { realm ->
|
||||
addOrUpdateCrossSigningInfo(realm, userId, info)
|
||||
}
|
||||
}
|
||||
|
||||
override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("markMyMasterKeyAsLocallyTrusted", realmConfiguration) { realm ->
|
||||
realm.where<CryptoMetadataEntity>().findFirst()?.userId?.let { myUserId ->
|
||||
CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity ->
|
||||
val level = xInfoEntity.trustLevelEntity
|
||||
@ -1713,7 +1724,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
val roomId = withHeldContent.roomId ?: return
|
||||
val sessionId = withHeldContent.sessionId ?: return
|
||||
if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("addWithHeldMegolmSession", realmConfiguration) { realm ->
|
||||
WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let {
|
||||
it.code = withHeldContent.code
|
||||
it.senderKey = withHeldContent.senderKey
|
||||
@ -1745,7 +1756,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
deviceIdentityKey: String,
|
||||
chainIndex: Int
|
||||
) {
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("markedSessionAsShared", realmConfiguration) { realm ->
|
||||
SharedSessionEntity.create(
|
||||
realm = realm,
|
||||
roomId = roomId,
|
||||
@ -1794,7 +1805,7 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
*/
|
||||
override fun tidyUpDataBase() {
|
||||
val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
|
||||
doRealmTransaction(realmConfiguration) { realm ->
|
||||
doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm ->
|
||||
|
||||
// Clean the old ones?
|
||||
realm.where<OutgoingKeyRequestEntity>()
|
||||
@ -1815,4 +1826,31 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
// Can we do something for WithHeldSessionEntity?
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) {
|
||||
if (cryptoStoreAggregator.isEmpty()) {
|
||||
return
|
||||
}
|
||||
doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm ->
|
||||
// setShouldShareHistory
|
||||
cryptoStoreAggregator.setShouldShareHistoryData.forEach {
|
||||
CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value
|
||||
}
|
||||
// setShouldEncryptForInvitedMembers
|
||||
cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach {
|
||||
CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeData(userDataToStore: UserDataToStore) {
|
||||
doRealmTransaction("storeData - UserDataToStore", realmConfiguration) { realm ->
|
||||
userDataToStore.userDevices.forEach {
|
||||
storeUserDevices(realm, it.key, it.value)
|
||||
}
|
||||
userDataToStore.userIdentities.forEach {
|
||||
storeUserIdentity(realm, it.key, it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.di
|
||||
|
||||
import androidx.annotation.Nullable
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonQualifier
|
||||
import com.squareup.moshi.Moshi
|
||||
@ -28,7 +27,6 @@ import java.lang.reflect.Type
|
||||
internal annotation class SerializeNulls {
|
||||
companion object {
|
||||
val JSON_ADAPTER_FACTORY: JsonAdapter.Factory = object : JsonAdapter.Factory {
|
||||
@Nullable
|
||||
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||
val nextAnnotations = Types.nextAnnotations(annotations, SerializeNulls::class.java)
|
||||
?: return null
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.network.interceptors
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
@ -38,7 +37,7 @@ internal class FormattedJsonHttpLogger(
|
||||
* @param message
|
||||
*/
|
||||
@Synchronized
|
||||
override fun log(@NonNull message: String) {
|
||||
override fun log(message: String) {
|
||||
Timber.v(message)
|
||||
|
||||
// Try to log formatted Json only if there is a chance that [message] contains Json.
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.network.parsing
|
||||
|
||||
import androidx.annotation.Nullable
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
@ -32,14 +31,12 @@ internal interface CheckNumberType {
|
||||
|
||||
companion object {
|
||||
val JSON_ADAPTER_FACTORY = object : JsonAdapter.Factory {
|
||||
@Nullable
|
||||
override fun create(type: Type, annotations: Set<Annotation>, moshi: Moshi): JsonAdapter<*>? {
|
||||
if (type !== Any::class.java) {
|
||||
return null
|
||||
}
|
||||
val delegate: JsonAdapter<Any> = moshi.nextAdapter(this, Any::class.java, emptySet())
|
||||
return object : JsonAdapter<Any?>() {
|
||||
@Nullable
|
||||
@Throws(IOException::class)
|
||||
override fun fromJson(reader: JsonReader): Any? {
|
||||
return if (reader.peek() !== JsonReader.Token.NUMBER) {
|
||||
|
@ -42,14 +42,12 @@ internal class StreamEventsManager @Inject constructor() {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) {
|
||||
fun dispatchLiveEventReceived(event: Event, roomId: String) {
|
||||
Timber.v("## dispatchLiveEventReceived ${event.eventId}")
|
||||
coroutineScope.launch {
|
||||
if (!initialSync) {
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onLiveEvent(roomId, event)
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull {
|
||||
it.onLiveEvent(roomId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor(
|
||||
}
|
||||
|
||||
// Give info to crypto module
|
||||
cryptoService.onStateEvent(roomId, event)
|
||||
cryptoService.onStateEvent(roomId, event, null)
|
||||
}
|
||||
|
||||
roomMemberContentsByUser.getOrPut(event.senderId) {
|
||||
|
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
|
||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.session.SessionListeners
|
||||
@ -92,7 +93,7 @@ internal class SyncResponseHandler @Inject constructor(
|
||||
|
||||
postTreatmentSyncResponse(syncResponse, isInitialSync)
|
||||
|
||||
markCryptoSyncCompleted(syncResponse)
|
||||
markCryptoSyncCompleted(syncResponse, aggregator.cryptoStoreAggregator)
|
||||
|
||||
handlePostSync()
|
||||
|
||||
@ -218,10 +219,10 @@ internal class SyncResponseHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun markCryptoSyncCompleted(syncResponse: SyncResponse) {
|
||||
private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) {
|
||||
relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") {
|
||||
measureTimeMillis {
|
||||
cryptoSyncHandler.onSyncCompleted(syncResponse)
|
||||
cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator)
|
||||
}.also {
|
||||
Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms")
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.session.sync
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
|
||||
|
||||
internal class SyncResponsePostTreatmentAggregator {
|
||||
// List of RoomId
|
||||
val ephemeralFilesToDelete = mutableListOf<String>()
|
||||
@ -28,4 +30,7 @@ internal class SyncResponsePostTreatmentAggregator {
|
||||
|
||||
// Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync
|
||||
val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf<String>()
|
||||
|
||||
// For the crypto store
|
||||
val cryptoStoreAggregator = CryptoStoreAggregator()
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
|
||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
||||
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
|
||||
@ -85,8 +86,8 @@ internal class CryptoSyncHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||
cryptoService.onSyncCompleted(syncResponse)
|
||||
fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) {
|
||||
cryptoService.onSyncCompleted(syncResponse, cryptoStoreAggregator)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,7 +258,7 @@ internal class RoomSyncHandler @Inject constructor(
|
||||
root = eventEntity
|
||||
}
|
||||
// Give info to crypto module
|
||||
cryptoService.onStateEvent(roomId, event)
|
||||
cryptoService.onStateEvent(roomId, event, aggregator.cryptoStoreAggregator)
|
||||
roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator)
|
||||
}
|
||||
}
|
||||
@ -376,8 +376,15 @@ internal class RoomSyncHandler @Inject constructor(
|
||||
roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) }
|
||||
roomTypingUsersHandler.handle(realm, roomId, null)
|
||||
roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE)
|
||||
roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary,
|
||||
roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator)
|
||||
roomSummaryUpdater.update(
|
||||
realm,
|
||||
roomId,
|
||||
membership,
|
||||
roomSync.summary,
|
||||
roomSync.unreadNotifications,
|
||||
roomSync.unreadThreadNotifications,
|
||||
aggregator = aggregator,
|
||||
)
|
||||
return roomEntity
|
||||
}
|
||||
|
||||
@ -423,7 +430,9 @@ internal class RoomSyncHandler @Inject constructor(
|
||||
val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
|
||||
|
||||
eventIds.add(event.eventId)
|
||||
liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync)
|
||||
if (!isInitialSync) {
|
||||
liveEventService.get().dispatchLiveEventReceived(event, roomId)
|
||||
}
|
||||
|
||||
if (event.isEncrypted() && !isInitialSync) {
|
||||
try {
|
||||
@ -486,7 +495,7 @@ internal class RoomSyncHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
// Give info to crypto module
|
||||
cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync)
|
||||
cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync, aggregator.cryptoStoreAggregator)
|
||||
|
||||
// Try to remove local echo
|
||||
event.unsignedData?.transactionId?.also { txId ->
|
||||
|
@ -77,6 +77,7 @@
|
||||
<issue id="UseValueOf" severity="error" />
|
||||
<issue id="ObsoleteSdkInt" severity="error" />
|
||||
<issue id="Recycle" severity="error" />
|
||||
<issue id="KotlinNullnessAnnotation" severity="error" />
|
||||
<issue id="KotlinPropertyAccess" severity="error" />
|
||||
<issue id="DefaultLocale" severity="error" />
|
||||
<issue id="CheckResult" severity="error" />
|
||||
|
@ -87,6 +87,14 @@ fi
|
||||
|
||||
printf "OK\n"
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Ensuring main and develop branches are up to date...\n"
|
||||
|
||||
git checkout main
|
||||
git pull
|
||||
git checkout develop
|
||||
git pull
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
# Guessing version to propose a default version
|
||||
versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3`
|
||||
@ -103,14 +111,6 @@ versionMinor=`echo ${version} | cut -d "." -f2`
|
||||
versionPatch=`echo ${version} | cut -d "." -f3`
|
||||
nextPatchVersion=$((versionPatch + 2))
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Ensuring main and develop branches are up to date...\n"
|
||||
|
||||
git checkout main
|
||||
git pull
|
||||
git checkout develop
|
||||
git pull
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Starting the release ${version}\n"
|
||||
git flow release start ${version}
|
||||
@ -190,6 +190,9 @@ yes | towncrier build --version "v${version}"
|
||||
printf "\n================================================================================\n"
|
||||
read -p "Check the file CHANGES.md consistency. It's possible to reorder items (most important changes first) or change their section if relevant. Also an opportunity to fix some typo, or rewrite things. Do not commit your change. Press enter when it's done."
|
||||
|
||||
# Get the changes to use it to create the GitHub release
|
||||
changelogUrlEncoded=`git diff CHANGES.md | grep ^+ | tail -n +2 | cut -c2- | jq -sRr @uri | sed s/\(/%28/g | sed s/\)/%29/g`
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Committing...\n"
|
||||
git commit -a -m "Changelog for version ${version}"
|
||||
@ -263,7 +266,7 @@ else
|
||||
fi
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch.\n"
|
||||
printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%%3Amain to build the 'main' branch.\n"
|
||||
read -p "After GHA is finished, please enter the artifact URL (for 'vector-gplay-release-unsigned'): " artifactUrl
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
@ -354,10 +357,15 @@ apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk"
|
||||
adb -d install ${apkPath}
|
||||
|
||||
read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done."
|
||||
# TODO Get the block to copy from towncrier earlier (be may be edited by the release manager)?
|
||||
read -p "Create the release on gitHub from the tag https://github.com/vector-im/element-android/tags, copy paste the block from the file CHANGES.md. Press enter when it's done."
|
||||
|
||||
read -p "Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}. Press enter when it's done."
|
||||
printf "\n================================================================================\n"
|
||||
githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%%20Android%%20v${version}&body=${changelogUrlEncoded}"
|
||||
printf "Creating the release on gitHub.\n"
|
||||
printf "Open this link: ${githubCreateReleaseLink}\n"
|
||||
printf "Then\n"
|
||||
printf " - click on the 'Generate releases notes' button\n"
|
||||
printf " - Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}\n"
|
||||
read -p ". Press enter when it's done. "
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Message for the Android internal room:\n\n"
|
||||
|
@ -308,7 +308,7 @@ dependencies {
|
||||
// Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868
|
||||
// Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0)
|
||||
//noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26.
|
||||
implementation "org.checkerframework:checker:3.27.0"
|
||||
implementation "org.checkerframework:checker:3.29.0"
|
||||
|
||||
androidTestImplementation libs.androidx.testCore
|
||||
androidTestImplementation libs.androidx.testRunner
|
||||
|
@ -84,6 +84,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListViewModel
|
||||
import im.vector.app.features.roomprofile.members.RoomMemberListViewModel
|
||||
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel
|
||||
import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel
|
||||
import im.vector.app.features.roomprofile.polls.RoomPollsViewModel
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel
|
||||
@ -697,4 +698,9 @@ interface MavericksViewModelModule {
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(SetLinkViewModel::class)
|
||||
fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(RoomPollsViewModel::class)
|
||||
fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
@ -91,6 +92,7 @@ import im.vector.app.features.themes.ActivityOtherThemes
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
@ -123,14 +125,20 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
protected val viewModelProvider
|
||||
get() = ViewModelProvider(this, viewModelFactory)
|
||||
|
||||
fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
hideWaitingView()
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
|
||||
observer: (T) -> Unit,
|
||||
) {
|
||||
val tag = this@VectorBaseActivity::class.simpleName.toString()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewEvents
|
||||
.stream(tag)
|
||||
.collect {
|
||||
hideWaitingView()
|
||||
observer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var toolbar: ToolbarConfig? = null
|
||||
|
@ -26,8 +26,10 @@ import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
@ -43,6 +45,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.github.hyuwah.draggableviewlib.Utils
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
|
||||
@ -199,12 +202,18 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
|
||||
observer: (T) -> Unit,
|
||||
) {
|
||||
val tag = this@VectorBaseBottomSheetDialogFragment::class.simpleName.toString()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewEvents
|
||||
.stream(tag)
|
||||
.collect {
|
||||
observer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,10 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
@ -37,6 +39,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
|
||||
@ -145,11 +148,15 @@ abstract class VectorBaseDialogFragment<VB : ViewBinding> : DialogFragment(), Ma
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
val tag = this@VectorBaseDialogFragment::class.simpleName.toString()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewEvents
|
||||
.stream(tag)
|
||||
.collect {
|
||||
observer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.bumptech.glide.util.Util.assertMainThread
|
||||
@ -53,6 +54,7 @@ import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
|
||||
@ -272,14 +274,20 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
dismissLoadingDialog()
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
|
||||
observer: (T) -> Unit,
|
||||
) {
|
||||
val tag = this@VectorBaseFragment::class.simpleName.toString()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewEvents
|
||||
.stream(tag)
|
||||
.collect {
|
||||
dismissLoadingDialog()
|
||||
observer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -18,15 +18,16 @@ package im.vector.app.core.platform
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.MavericksViewModel
|
||||
import im.vector.app.core.utils.DataSource
|
||||
import im.vector.app.core.utils.PublishDataSource
|
||||
import im.vector.app.core.utils.EventQueue
|
||||
import im.vector.app.core.utils.SharedEvents
|
||||
|
||||
abstract class VectorViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S) :
|
||||
MavericksViewModel<S>(initialState) {
|
||||
|
||||
// Used to post transient events to the View
|
||||
protected val _viewEvents = PublishDataSource<VE>()
|
||||
val viewEvents: DataSource<VE> = _viewEvents
|
||||
protected val _viewEvents = EventQueue<VE>(capacity = 64)
|
||||
val viewEvents: SharedEvents<VE>
|
||||
get() = _viewEvents
|
||||
|
||||
abstract fun handle(action: VA)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package im.vector.app.core.resources
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.ArrayRes
|
||||
import androidx.annotation.NonNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class StringArrayProvider @Inject constructor(private val resources: Resources) {
|
||||
@ -31,7 +30,6 @@ class StringArrayProvider @Inject constructor(private val resources: Resources)
|
||||
* @return The string array associated with the resource, stripped of styled
|
||||
* text information.
|
||||
*/
|
||||
@NonNull
|
||||
fun getStringArray(@ArrayRes resId: Int): Array<String> {
|
||||
return resources.getStringArray(resId)
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.app.core.resources
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.annotation.StringRes
|
||||
import javax.inject.Inject
|
||||
@ -32,7 +31,6 @@ class StringProvider @Inject constructor(private val resources: Resources) {
|
||||
* @return The string data associated with the resource, stripped of styled
|
||||
* text information.
|
||||
*/
|
||||
@NonNull
|
||||
fun getString(@StringRes resId: Int): String {
|
||||
return resources.getString(resId)
|
||||
}
|
||||
@ -48,12 +46,10 @@ class StringProvider @Inject constructor(private val resources: Resources) {
|
||||
* @return The string data associated with the resource, formatted and
|
||||
* stripped of styled text information.
|
||||
*/
|
||||
@NonNull
|
||||
fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String {
|
||||
return resources.getString(resId, *formatArgs)
|
||||
}
|
||||
|
||||
@NonNull
|
||||
fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
|
||||
return resources.getQuantityString(resId, quantity, *formatArgs)
|
||||
}
|
||||
|
58
vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt
Normal file
58
vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2022 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.app.core.utils
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
interface SharedEvents<out T> {
|
||||
fun stream(consumerId: String): Flow<T>
|
||||
}
|
||||
|
||||
class EventQueue<T>(capacity: Int) : SharedEvents<T> {
|
||||
|
||||
private val innerQueue = MutableSharedFlow<OneTimeEvent<T>>(replay = capacity)
|
||||
|
||||
fun post(event: T) {
|
||||
innerQueue.tryEmit(OneTimeEvent(event))
|
||||
}
|
||||
|
||||
override fun stream(consumerId: String): Flow<T> = innerQueue.filterNotHandledBy(consumerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Event designed to be delivered only once to a concrete entity,
|
||||
* but it can also be delivered to multiple different entities.
|
||||
*
|
||||
* Keeps track of who has already handled its content.
|
||||
*/
|
||||
private class OneTimeEvent<out T>(private val content: T) {
|
||||
|
||||
private val handlers = CopyOnWriteArraySet<String>()
|
||||
|
||||
/**
|
||||
* @param asker Used to identify, whether this "asker" has already handled this Event.
|
||||
* @return Event content or null if it has been already handled by asker
|
||||
*/
|
||||
fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null
|
||||
}
|
||||
|
||||
private fun <T> Flow<OneTimeEvent<T>>.filterNotHandledBy(consumerId: String): Flow<T> = transform { event ->
|
||||
event.getIfNotHandled(consumerId)?.let { emit(it) }
|
||||
}
|
@ -55,8 +55,6 @@ import im.vector.app.features.themes.ActivityOtherThemes
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@ -142,9 +140,9 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity
|
||||
startAppViewModel.onEach {
|
||||
renderState(it)
|
||||
}
|
||||
startAppViewModel.viewEvents.stream()
|
||||
.onEach(::handleViewEvents)
|
||||
.launchIn(lifecycleScope)
|
||||
startAppViewModel.observeViewEvents {
|
||||
handleViewEvents(it)
|
||||
}
|
||||
|
||||
startAppViewModel.handle(StartAppAction.StartApp)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class SharedSecureStorageActivity :
|
||||
|
||||
views.toolbar.visibility = View.GONE
|
||||
|
||||
viewModel.observeViewEvents { observeViewEvents(it) }
|
||||
viewModel.observeViewEvents { onViewEvents(it) }
|
||||
|
||||
viewModel.onEach { renderState(it) }
|
||||
}
|
||||
@ -85,7 +85,7 @@ class SharedSecureStorageActivity :
|
||||
showFragment(fragment)
|
||||
}
|
||||
|
||||
private fun observeViewEvents(it: SharedSecureStorageViewEvent?) {
|
||||
private fun onViewEvents(it: SharedSecureStorageViewEvent) {
|
||||
when (it) {
|
||||
is SharedSecureStorageViewEvent.Dismiss -> {
|
||||
finish()
|
||||
|
@ -29,7 +29,9 @@ import androidx.core.transition.addListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Transition
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@ -50,8 +52,6 @@ import im.vector.lib.attachmentviewer.AttachmentViewerActivity
|
||||
import im.vector.lib.core.utils.compat.getParcelableArrayListExtraCompat
|
||||
import im.vector.lib.core.utils.compat.getParcelableExtraCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@ -239,10 +239,15 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt
|
||||
}
|
||||
|
||||
private fun observeViewEvents() {
|
||||
viewModel.viewEvents
|
||||
.stream()
|
||||
.onEach(::handleViewEvents)
|
||||
.launchIn(lifecycleScope)
|
||||
val tag = this::class.simpleName.toString()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel
|
||||
.viewEvents
|
||||
.stream(tag)
|
||||
.collect(::handleViewEvents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleViewEvents(event: VectorAttachmentViewerViewEvents) {
|
||||
|
@ -36,6 +36,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
|
||||
import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
||||
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment
|
||||
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
||||
import im.vector.app.features.roomprofile.polls.RoomPollsFragment
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
||||
import im.vector.lib.core.utils.compat.getParcelableCompat
|
||||
@ -98,6 +99,7 @@ class RoomProfileActivity :
|
||||
RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
|
||||
RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias()
|
||||
RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions()
|
||||
RoomProfileSharedAction.OpenRoomPolls -> openRoomPolls()
|
||||
RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
|
||||
RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
|
||||
RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings()
|
||||
@ -126,6 +128,10 @@ class RoomProfileActivity :
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun openRoomPolls() {
|
||||
addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs)
|
||||
}
|
||||
|
||||
private fun openRoomUploads() {
|
||||
addFragmentToBackstack(views.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
package im.vector.app.features.roomprofile
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.expandableTextItem
|
||||
import im.vector.app.core.epoxy.profiles.buildProfileAction
|
||||
@ -56,6 +57,7 @@ class RoomProfileController @Inject constructor(
|
||||
fun onMemberListClicked()
|
||||
fun onBannedMemberListClicked()
|
||||
fun onNotificationsClicked()
|
||||
fun onPollHistoryClicked()
|
||||
fun onUploadsClicked()
|
||||
fun createShortcut()
|
||||
fun onSettingsClicked()
|
||||
@ -263,6 +265,15 @@ class RoomProfileController @Inject constructor(
|
||||
action = { callback?.onBannedMemberListClicked() }
|
||||
)
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
// WIP, will be in release when related screens will be finished
|
||||
buildProfileAction(
|
||||
id = "poll_history",
|
||||
title = stringProvider.getString(R.string.room_profile_section_more_polls),
|
||||
icon = R.drawable.ic_attachment_poll,
|
||||
action = { callback?.onPollHistoryClicked() }
|
||||
)
|
||||
}
|
||||
buildProfileAction(
|
||||
id = "uploads",
|
||||
title = stringProvider.getString(R.string.room_profile_section_more_uploads),
|
||||
|
@ -269,6 +269,10 @@ class RoomProfileFragment :
|
||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificationSettings)
|
||||
}
|
||||
|
||||
override fun onPollHistoryClicked() {
|
||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPolls)
|
||||
}
|
||||
|
||||
override fun onUploadsClicked() {
|
||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomUploads)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction {
|
||||
object OpenRoomSettings : RoomProfileSharedAction()
|
||||
object OpenRoomAliasesSettings : RoomProfileSharedAction()
|
||||
object OpenRoomPermissionsSettings : RoomProfileSharedAction()
|
||||
object OpenRoomPolls : RoomProfileSharedAction()
|
||||
object OpenRoomUploads : RoomProfileSharedAction()
|
||||
object OpenRoomMembers : RoomProfileSharedAction()
|
||||
object OpenBannedRoomMembers : RoomProfileSharedAction()
|
||||
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetPollsUseCase @Inject constructor() {
|
||||
|
||||
fun execute(): Flow<List<PollSummary>> {
|
||||
// TODO unmock and add unit tests
|
||||
return flowOf(getActivePolls() + getEndedPolls())
|
||||
.map { it.sortedByDescending { poll -> poll.creationTimestamp } }
|
||||
}
|
||||
|
||||
private fun getActivePolls(): List<PollSummary.ActivePoll> {
|
||||
return listOf(
|
||||
PollSummary.ActivePoll(
|
||||
id = "id1",
|
||||
// 2022/06/28 UTC+1
|
||||
creationTimestamp = 1656367200000,
|
||||
title = "Which charity would you like to support?"
|
||||
),
|
||||
PollSummary.ActivePoll(
|
||||
id = "id2",
|
||||
// 2022/06/26 UTC+1
|
||||
creationTimestamp = 1656194400000,
|
||||
title = "Which sport should the pupils do this year?"
|
||||
),
|
||||
PollSummary.ActivePoll(
|
||||
id = "id3",
|
||||
// 2022/06/24 UTC+1
|
||||
creationTimestamp = 1656021600000,
|
||||
title = "What type of food should we have at the party?"
|
||||
),
|
||||
PollSummary.ActivePoll(
|
||||
id = "id4",
|
||||
// 2022/06/22 UTC+1
|
||||
creationTimestamp = 1655848800000,
|
||||
title = "What film should we show at the end of the year party?"
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getEndedPolls(): List<PollSummary.EndedPoll> {
|
||||
return listOf(
|
||||
PollSummary.EndedPoll(
|
||||
id = "id1-ended",
|
||||
// 2022/06/28 UTC+1
|
||||
creationTimestamp = 1656367200000,
|
||||
title = "Which charity would you like to support?",
|
||||
totalVotes = 22,
|
||||
winnerOptions = listOf(
|
||||
PollOptionViewState.PollEnded(
|
||||
optionId = "id1",
|
||||
optionAnswer = "Cancer research",
|
||||
voteCount = 13,
|
||||
votePercentage = 13 / 22.0,
|
||||
isWinner = true,
|
||||
)
|
||||
),
|
||||
),
|
||||
PollSummary.EndedPoll(
|
||||
id = "id2-ended",
|
||||
// 2022/06/26 UTC+1
|
||||
creationTimestamp = 1656194400000,
|
||||
title = "Where should we do the offsite?",
|
||||
totalVotes = 92,
|
||||
winnerOptions = listOf(
|
||||
PollOptionViewState.PollEnded(
|
||||
optionId = "id1",
|
||||
optionAnswer = "Hawaii",
|
||||
voteCount = 43,
|
||||
votePercentage = 43 / 92.0,
|
||||
isWinner = true,
|
||||
)
|
||||
),
|
||||
),
|
||||
PollSummary.EndedPoll(
|
||||
id = "id3-ended",
|
||||
// 2022/06/24 UTC+1
|
||||
creationTimestamp = 1656021600000,
|
||||
title = "What type of food should we have at the party?",
|
||||
totalVotes = 22,
|
||||
winnerOptions = listOf(
|
||||
PollOptionViewState.PollEnded(
|
||||
optionId = "id1",
|
||||
optionAnswer = "Brazilian",
|
||||
voteCount = 13,
|
||||
votePercentage = 13 / 22.0,
|
||||
isWinner = true,
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||
|
||||
sealed interface PollSummary {
|
||||
val id: String
|
||||
val creationTimestamp: Long
|
||||
val title: String
|
||||
|
||||
data class ActivePoll(
|
||||
override val id: String,
|
||||
override val creationTimestamp: Long,
|
||||
override val title: String,
|
||||
) : PollSummary
|
||||
|
||||
data class EndedPoll(
|
||||
override val id: String,
|
||||
override val creationTimestamp: Long,
|
||||
override val title: String,
|
||||
val totalVotes: Int,
|
||||
val winnerOptions: List<PollOptionViewState.PollEnded>,
|
||||
) : PollSummary
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed interface RoomPollsAction : VectorViewModelAction
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentRoomPollsBinding
|
||||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RoomPollsFragment : VectorBaseFragment<FragmentRoomPollsBinding>() {
|
||||
|
||||
private val roomProfileArgs: RoomProfileArgs by args()
|
||||
|
||||
private val viewModel: RoomPollsViewModel by fragmentViewModel()
|
||||
|
||||
private var tabLayoutMediator: TabLayoutMediator? = null
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding {
|
||||
return FragmentRoomPollsBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupToolbar()
|
||||
setupTabs()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
views.roomPollsViewPager.adapter = null
|
||||
tabLayoutMediator?.detach()
|
||||
tabLayoutMediator = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
setupToolbar(views.roomPollsToolbar)
|
||||
.allowBack()
|
||||
}
|
||||
|
||||
private fun setupTabs() {
|
||||
views.roomPollsViewPager.adapter = RoomPollsPagerAdapter(this)
|
||||
|
||||
tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position ->
|
||||
when (position) {
|
||||
RoomPollsType.ACTIVE.ordinal -> tab.text = getString(R.string.room_polls_active)
|
||||
RoomPollsType.ENDED.ordinal -> tab.text = getString(R.string.room_polls_ended)
|
||||
}
|
||||
}.also { it.attach() }
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment
|
||||
import im.vector.app.features.roomprofile.polls.ended.RoomEndedPollsFragment
|
||||
|
||||
class RoomPollsPagerAdapter(
|
||||
private val fragment: Fragment
|
||||
) : FragmentStateAdapter(fragment) {
|
||||
|
||||
override fun getItemCount() = RoomPollsType.values().size
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
RoomPollsType.ACTIVE.ordinal -> instantiateFragment(RoomActivePollsFragment::class.java.name)
|
||||
RoomPollsType.ENDED.ordinal -> instantiateFragment(RoomEndedPollsFragment::class.java.name)
|
||||
else -> throw IllegalArgumentException("position should be between 0 and ${itemCount - 1}, while it was $position")
|
||||
}
|
||||
}
|
||||
|
||||
private fun instantiateFragment(fragmentName: String): Fragment {
|
||||
return fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, fragmentName)
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
enum class RoomPollsType {
|
||||
ACTIVE,
|
||||
ENDED,
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class RoomPollsViewEvent : VectorViewEvents
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
class RoomPollsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: RoomPollsViewState,
|
||||
private val getPollsUseCase: GetPollsUseCase,
|
||||
) : VectorViewModel<RoomPollsViewState, RoomPollsAction, RoomPollsViewEvent>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<RoomPollsViewModel, RoomPollsViewState> {
|
||||
override fun create(initialState: RoomPollsViewState): RoomPollsViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<RoomPollsViewModel, RoomPollsViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
init {
|
||||
observePolls()
|
||||
}
|
||||
|
||||
private fun observePolls() {
|
||||
getPollsUseCase.execute()
|
||||
.onEach { setState { copy(polls = it) } }
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handle(action: RoomPollsAction) {
|
||||
// do nothing for now
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||
|
||||
data class RoomPollsViewState(
|
||||
val roomId: String,
|
||||
val polls: List<PollSummary> = emptyList(),
|
||||
) : MavericksState {
|
||||
|
||||
constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId)
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls.active
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.roomprofile.polls.RoomPollsType
|
||||
import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RoomActivePollsFragment : RoomPollsListFragment() {
|
||||
|
||||
override fun getEmptyListTitle(): String {
|
||||
return getString(R.string.room_polls_active_no_item)
|
||||
}
|
||||
|
||||
override fun getRoomPollsType(): RoomPollsType {
|
||||
return RoomPollsType.ACTIVE
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls.ended
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.roomprofile.polls.RoomPollsType
|
||||
import im.vector.app.features.roomprofile.polls.list.RoomPollsListFragment
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RoomEndedPollsFragment : RoomPollsListFragment() {
|
||||
|
||||
override fun getEmptyListTitle(): String {
|
||||
return getString(R.string.room_polls_ended_no_item)
|
||||
}
|
||||
|
||||
override fun getRoomPollsType(): RoomPollsType {
|
||||
return RoomPollsType.ENDED
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls.list
|
||||
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionView
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
|
||||
|
||||
@EpoxyModelClass
|
||||
abstract class RoomPollItem : VectorEpoxyModel<RoomPollItem.Holder>(R.layout.item_poll) {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var formattedDate: String
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var title: String
|
||||
|
||||
@EpoxyAttribute
|
||||
var winnerOptions: List<PollOptionViewState.PollEnded> = emptyList()
|
||||
|
||||
@EpoxyAttribute
|
||||
var totalVotesStatus: String? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var clickListener: ClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.view.onClick(clickListener)
|
||||
holder.date.text = formattedDate
|
||||
holder.title.text = title
|
||||
holder.winnerOptions.removeAllViews()
|
||||
holder.winnerOptions.isVisible = winnerOptions.isNotEmpty()
|
||||
for (winnerOption in winnerOptions) {
|
||||
val optionView = PollOptionView(holder.view.context)
|
||||
holder.winnerOptions.addView(optionView)
|
||||
optionView.render(winnerOption)
|
||||
}
|
||||
holder.totalVotes.setTextOrHide(totalVotesStatus)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val date by bind<TextView>(R.id.pollDate)
|
||||
val title by bind<TextView>(R.id.pollTitle)
|
||||
val winnerOptions by bind<LinearLayout>(R.id.pollWinnerOptionsContainer)
|
||||
val totalVotes by bind<TextView>(R.id.pollTotalVotes)
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls.list
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.roomprofile.polls.PollSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomPollsController @Inject constructor(
|
||||
val dateFormatter: VectorDateFormatter,
|
||||
val stringProvider: StringProvider,
|
||||
) : TypedEpoxyController<List<PollSummary>>() {
|
||||
|
||||
interface Listener {
|
||||
fun onPollClicked(pollId: String)
|
||||
}
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildModels(data: List<PollSummary>?) {
|
||||
if (data.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (poll in data) {
|
||||
when (poll) {
|
||||
is PollSummary.ActivePoll -> buildActivePollItem(poll)
|
||||
is PollSummary.EndedPoll -> buildEndedPollItem(poll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildActivePollItem(poll: PollSummary.ActivePoll) {
|
||||
val host = this
|
||||
roomPollItem {
|
||||
id(poll.id)
|
||||
formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER))
|
||||
title(poll.title)
|
||||
clickListener {
|
||||
host.listener?.onPollClicked(poll.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildEndedPollItem(poll: PollSummary.EndedPoll) {
|
||||
val host = this
|
||||
roomPollItem {
|
||||
id(poll.id)
|
||||
formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER))
|
||||
title(poll.title)
|
||||
winnerOptions(poll.winnerOptions)
|
||||
totalVotesStatus(host.stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, poll.totalVotes, poll.totalVotes))
|
||||
clickListener {
|
||||
host.listener?.onPollClicked(poll.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls.list
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.parentFragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentRoomPollsListBinding
|
||||
import im.vector.app.features.roomprofile.polls.PollSummary
|
||||
import im.vector.app.features.roomprofile.polls.RoomPollsType
|
||||
import im.vector.app.features.roomprofile.polls.RoomPollsViewModel
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class RoomPollsListFragment :
|
||||
VectorBaseFragment<FragmentRoomPollsListBinding>(),
|
||||
RoomPollsController.Listener {
|
||||
|
||||
@Inject
|
||||
lateinit var roomPollsController: RoomPollsController
|
||||
|
||||
private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class)
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding {
|
||||
return FragmentRoomPollsListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupList()
|
||||
}
|
||||
|
||||
abstract fun getEmptyListTitle(): String
|
||||
|
||||
abstract fun getRoomPollsType(): RoomPollsType
|
||||
|
||||
private fun setupList() {
|
||||
roomPollsController.listener = this
|
||||
views.roomPollsList.configureWith(roomPollsController)
|
||||
views.roomPollsEmptyTitle.text = getEmptyListTitle()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
cleanUpList()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun cleanUpList() {
|
||||
views.roomPollsList.cleanup()
|
||||
roomPollsController.listener = null
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) { viewState ->
|
||||
when (getRoomPollsType()) {
|
||||
RoomPollsType.ACTIVE -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java))
|
||||
RoomPollsType.ENDED -> renderList(viewState.polls.filterIsInstance(PollSummary.EndedPoll::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderList(polls: List<PollSummary>) {
|
||||
roomPollsController.setData(polls)
|
||||
views.roomPollsEmptyTitle.isVisible = polls.isEmpty()
|
||||
}
|
||||
|
||||
override fun onPollClicked(pollId: String) {
|
||||
// TODO navigate to details
|
||||
Timber.d("poll with id $pollId clicked")
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.preference.VectorPreference
|
||||
import im.vector.app.core.preference.VectorPreferenceCategory
|
||||
import im.vector.app.core.preference.VectorSwitchPreference
|
||||
import im.vector.app.core.utils.copyToClipboard
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import im.vector.app.features.home.NightlyProxy
|
||||
import im.vector.app.features.rageshake.RageShake
|
||||
@ -64,6 +65,14 @@ class VectorSettingsAdvancedSettingsFragment :
|
||||
override fun bindPref() {
|
||||
setupRageShakeSection()
|
||||
setupNightlySection()
|
||||
setupDevToolsSection()
|
||||
}
|
||||
|
||||
private fun setupDevToolsSection() {
|
||||
findPreference<VectorPreference>("SETTINGS_ACCESS_TOKEN")?.setOnPreferenceClickListener {
|
||||
copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRageShakeSection() {
|
||||
|
@ -20,7 +20,9 @@ import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@ -35,6 +37,7 @@ import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
@ -66,13 +69,19 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.stream()
|
||||
.onEach {
|
||||
observer(it)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(
|
||||
observer: (T) -> Unit,
|
||||
) {
|
||||
val tag = this@VectorSettingsBaseFragment::class.simpleName.toString()
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewEvents
|
||||
.stream(tag)
|
||||
.collect {
|
||||
observer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -419,7 +419,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||
// Next media player is already attached to this player and will start playing automatically
|
||||
if (nextMediaPlayer != null) return
|
||||
|
||||
val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence
|
||||
val currentSequence = playlist.currentSequence ?: 0
|
||||
val lastChunkSequence = mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence ?: 0
|
||||
val hasEnded = !isLiveListening && currentSequence >= lastChunkSequence
|
||||
if (hasEnded) {
|
||||
// We'll not receive new chunks anymore so we can stop the live listening
|
||||
stop()
|
||||
|
54
vector/src/main/res/layout/fragment_room_polls.xml
Normal file
54
vector/src/main/res/layout/fragment_room_polls.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/roomPollsToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:title="@string/room_profile_section_more_polls" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/roomPollsTabs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="?android:colorBackground"
|
||||
app:layout_constraintBottom_toTopOf="@id/roomPollsViewPager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
|
||||
app:tabGravity="start"
|
||||
app:tabIndicatorFullWidth="false"
|
||||
app:tabIndicatorHeight="1dp"
|
||||
app:tabMaxWidth="0dp"
|
||||
app:tabMode="scrollable"
|
||||
app:tabPaddingBottom="-15dp"
|
||||
app:tabSelectedTextColor="?colorSecondary"
|
||||
app:tabTextAppearance="@style/TextAppearance.Vector.Body"
|
||||
app:tabTextColor="?vctr_content_primary" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/roomPollsViewPager"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/roomPollsTabs" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
42
vector/src/main/res/layout/fragment_room_polls_list.xml
Normal file
42
vector/src/main/res/layout/fragment_room_polls_list.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/roomPollsList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:itemCount="5"
|
||||
tools:listitem="@layout/item_poll" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roomPollsEmptyTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/layout_vertical_margin"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Body"
|
||||
android:textColor="?vctr_content_secondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/roomPollsEmptyGuideline"
|
||||
tools:text="@string/room_polls_active_no_item" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/roomPollsEmptyGuideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.33" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
69
vector/src/main/res/layout/item_poll.xml
Normal file
69
vector/src/main/res/layout/item_poll.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pollDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Caption"
|
||||
android:textColor="?vctr_content_tertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="28/06/22" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pollIcon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/ic_attachment_poll"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pollDate"
|
||||
app:tint="?vctr_content_secondary"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pollTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:textAppearance="@style/TextAppearance.Vector.Subtitle"
|
||||
android:textColor="?vctr_content_primary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/pollIcon"
|
||||
app:layout_constraintTop_toBottomOf="@id/pollDate"
|
||||
tools:text="Which sport should the pupils do this year?" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pollWinnerOptionsContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13dp"
|
||||
android:divider="@drawable/divider_poll_options"
|
||||
android:orientation="vertical"
|
||||
android:showDividers="middle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pollTitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pollTotalVotes"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/pollWinnerOptionsContainer"
|
||||
tools:text="@sample/poll.json/totalVotes" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -93,6 +93,12 @@
|
||||
android:title="@string/settings_key_requests"
|
||||
app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" />
|
||||
|
||||
<im.vector.app.core.preference.VectorPreference
|
||||
android:key="SETTINGS_ACCESS_TOKEN"
|
||||
android:persistent="false"
|
||||
android:summary="@string/settings_access_token_summary"
|
||||
android:title="@string/settings_access_token" />
|
||||
|
||||
</im.vector.app.core.preference.VectorPreferenceCategory>
|
||||
|
||||
<im.vector.app.core.preference.VectorPreferenceCategory
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.app.features.roomprofile.polls
|
||||
|
||||
import com.airbnb.mvrx.test.MavericksTestRule
|
||||
import im.vector.app.test.test
|
||||
import im.vector.app.test.testDispatcher
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
private const val ROOM_ID = "room-id"
|
||||
|
||||
class RoomPollsViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
|
||||
|
||||
private val fakeGetPollsUseCase = mockk<GetPollsUseCase>()
|
||||
private val initialState = RoomPollsViewState(ROOM_ID)
|
||||
|
||||
private fun createViewModel(): RoomPollsViewModel {
|
||||
return RoomPollsViewModel(
|
||||
initialState = initialState,
|
||||
getPollsUseCase = fakeGetPollsUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given viewModel when created then polls list is observed and viewState is updated`() {
|
||||
// Given
|
||||
val polls = listOf(givenAPollSummary())
|
||||
every { fakeGetPollsUseCase.execute() } returns flowOf(polls)
|
||||
val expectedViewState = initialState.copy(polls = polls)
|
||||
|
||||
// When
|
||||
val viewModel = createViewModel()
|
||||
val viewModelTest = viewModel.test()
|
||||
|
||||
// Then
|
||||
viewModelTest
|
||||
.assertLatestState(expectedViewState)
|
||||
.finish()
|
||||
verify {
|
||||
fakeGetPollsUseCase.execute()
|
||||
}
|
||||
}
|
||||
|
||||
private fun givenAPollSummary(): PollSummary {
|
||||
return mockk()
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ fun String.trimIndentOneLine() = trimIndent().replace("\n", "")
|
||||
fun <S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents> VectorViewModel<S, VA, VE>.test(): ViewModelTest<S, VE> {
|
||||
val testResultCollectingScope = CoroutineScope(Dispatchers.Unconfined)
|
||||
val state = stateFlow.test(testResultCollectingScope)
|
||||
val viewEvents = viewEvents.stream().test(testResultCollectingScope)
|
||||
val viewEvents = viewEvents.stream("test").test(testResultCollectingScope)
|
||||
return ViewModelTest(state, viewEvents)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user