Merge tag 'v1.1.16' into sc

v1.1.16

Change-Id: Id68bd7e36129c9168cc871d81c5c0935b689e1e9

Conflicts:
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
	vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt
This commit is contained in:
SpiritCroc 2021-08-10 09:54:58 +02:00
commit 677330921d
120 changed files with 2882 additions and 395 deletions

View file

@ -1,3 +1,18 @@
Changes in Element v1.1.16 (2021-08-09)
=======================================
Features ✨
----------
- Spaces - Support Restricted Room via room capabilities API ([#3509](https://github.com/vector-im/element-android/issues/3509))
- Spaces | Support restricted room access in room settings ([#3665](https://github.com/vector-im/element-android/issues/3665))
Bugfixes 🐛
----------
- Fix crash when opening Troubleshoot Notifications ([#3778](https://github.com/vector-im/element-android/issues/3778))
- Fix error when sending encrypted message if someone in the room logs out. ([#3792](https://github.com/vector-im/element-android/issues/3792))
- Voice Message - Amplitude update java.util.ConcurrentModificationException ([#3796](https://github.com/vector-im/element-android/issues/3796))
Changes in Element v1.1.15 (2021-07-30) Changes in Element v1.1.15 (2021-07-30)
======================================= =======================================

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: aktualizace hlavně kvůli stabilitě a opravám chyb
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: oprava chyby ohledně šifrovaných zpráv
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Hauptänderung dieser Version: Beheben eines Problems mit verschlüsselten Nachrichten.
Alle Änderungen: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Main changes in this version: Fix error when sending encrypted message if someone in the room logs out.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.16

View file

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: üldiste vigade parandus.
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: krüptitud sõnumitega seotud vigade parandus
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Fő változás ebben a verzióban: leginkább hibajavító és stabilitást növelő frissítés
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Fő változás ebben a verzióban: titkosított üzenetekkel kapcsolatos hibajavítás
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Versi baru ini terutama berisi perbaikan bug dan peningkatan. Mengirim pesan sekarang jauh lebih cepat.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.10

View file

@ -0,0 +1,2 @@
Versi baru ini terutama berisi antarmuka pengguna dan peningkatan pengalaman pengguna. Sekarang Anda dapat mengundang teman, dan membuat sebuah DM sangat cepat dengan memindai kode QR.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.11

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Pratinjau URL, keyboard Emoji baru, kemampuan pengaturan ruangan baru, dan salju untuk Natal!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.12

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Pratinjau URL, keyboard Emoji baru, kemampuan pengaturan ruangan baru, dan salju untuk Natal!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Edit izin ruangan, tema cahaya/gelap otomatis, dan banyak perbaikan bug.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Dukungan login sosial.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Dukungan login sosial.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.15 dan https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: perbaikan bug!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: perbaikan VoIP (panggilan audio dan video dalam DM) dan perbaikan bug!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.1

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.2

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.3

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.4

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: perbaikan hot-fix untuk 1.1.4
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.5

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: perbaikan hot-fix untuk 1.1.5
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.6

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: dukungan beta untuk Spaces. Kompres video sebelum mengirim.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.7

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: perbaikan untuk Spaces.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.8

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: menambahkan dukungan untuk jaringan gitter.im.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.9

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: pembaruan tema dan gaya dan fitur-fitur baru untuk Spaces.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: pembaruan tema dan gaya dan fitur baru untuk spaces (perbaikan bug untuk 1.1.10)
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: pembaruan tema dan gaya dan perbaiki crash setelah panggilan video
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.12

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: terutama pembaruan stabilitas dan perbaikan bug.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: memperbaiki masalah tentang pesan terenkripsi.
Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,39 @@
Element adalah perpesanan yang aman dan aplikasi kolaborasi tim produktivitas yang ideal untuk obrolan grup saat bekerja jarak jauh. Aplikasi obrolan ini menggunakan enkripsi ujung-ke-ujung untuk memberikan konferensi video, berbagi file, dan panggilan suara.
<b>Fitur Element termasuk:</b>
- Alat komunikasi online yang canggih
- Pesan terenkripsi sepenuhnya untuk memungkinkan komunikasi perusahaan yang lebih aman, bahkan untuk pekerja jarak jauh
- Obrolan terdesentralisasi berdasarkan framework sumber-terbuka Matrix
- Berbagi file dengan aman dengan data terenkripsi saat mengelola proyek
- Obrolan video dengan VoIP dan berbagi layar
- Integrasi yang mudah dengan alat kolaborasi online favorit Anda, alat manajemen proyek, layanan VoIP dan aplikasi perpesanan tim lainnya
Element benar-benar berbeda dari aplikasi perpesanan dan kolaborasi lainnya. Ini beroperasi pada Matrix, jaringan terbuka untuk pengiriman pesan yang aman dan komunikasi terdesentralisasi. Ini memungkinkan hosting sendiri untuk memberi pengguna kepemilikan maksimum dan kontrol data dan pesan mereka.
<b>Pesan privasi dan terenkripsi</b>
Element melindungi Anda dari iklan yang tidak diinginkan, data penambangan dan taman berdinding. Ini juga mengamankan semua data Anda, komunikasi video dan suara satu-ke-satu melalui enkripsi ujung-ke-ujung dan verifikasi perangkat yang di-cross-signed.
Element memberi Anda kendali atas privasi Anda sambil memungkinkan Anda untuk berkomunikasi dengan aman dengan siapa pun di jaringan Matrix, atau alat kolaborasi bisnis lainnya dengan mengintegrasikan dengan aplikasi seperti Slack.
<b>Element dapat dihost sendiri</b>
Untuk memungkinkan lebih banyak kendali atas data dan percakapan sensitif Anda, Element bisa dihost sendiri atau Anda dapat memilih host berbasis Matrix - standar untuk komunikasi terdesentralisasi sumber-terbuka. Element memberi Anda privasi, kepatuhan keamanan, dan fleksibilitas integrasi.
<b>Miliki data Anda</b>
Anda memutuskan di mana menyimpan data dan pesan Anda. Tanpa risiko penambangan data atau akses dari pihak ketiga.
Element menempatkan Anda dalam kendali dengan cara yang berbeda:
1. Dapatkan akun gratis pada server publik matrix.org yang dihost oleh pengembang Matrix, atau memilih dari ribuan server publik yang dihost oleh sukarelawan
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
3. Daftar untuk akun di server khusus dengan hanya berlangganan platform hosting Element Matrix Services
<b>Pesan terbuka dan kolaborasi</b>
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda.
<b>Sangat aman</b>
Enkripsi ujung-ke-ujung beneran (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat yang di-cross-signed.
<b>Komunikasi dan integrasi lengkap</b>
Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal.
<b>Ambil di mana Anda tinggalkan</b>
Tetap terhubung di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io

View file

@ -0,0 +1 @@
Perpesanan grup - pesan terenkripsi, panggilan grup dan video

View file

@ -0,0 +1 @@
Element - Perpesanan Aman

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: aggiornamento di stabilità e correzione errori.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: corretto un problema con i messaggi cifrati.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Principais mudanças nesta versão: principalmente atualização de estabilidade e consertos de bug.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Principais mudanças nesta versão: consertar um problema sobre mensagens encriptadas.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: улучшение и исправления ошибок!
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.4

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: исправление для 1.1.4
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.5

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: исправление для 1.1.5
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.6

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: бета-поддержка Пространств. Сжатие видео перед отправкой.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.7

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Усовершенствованы Пространства!
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.8

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: добавлена поддержка сети gitter.im.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.9

View file

@ -0,0 +1,2 @@
Основные изменения этой версии: обновлен внешний вид и новые возможности для пространств
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Основные изменения этой версии: обновлен внешний вид и новые возможности для пространств (bugfix для 1.1.10)
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: обновление темы и стиля и исправления сбоев после видеовызова
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.12

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: улучшение стабильности и исправления ошибок.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: исправление проблемы с зашифрованными сообщениями.
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -1,30 +1,39 @@
Element - это новый тип приложения для обмена сообщениями и совместной работы, которое: Element - это одновременно безопасный мессенджер и приложение для совместной работы, которое идеально подходит для групповых чатов при удаленной работе. Это приложение для чатов использует сквозное шифрование для обеспечения мощных видеоконференций, обмена файлами и голосовых звонков.
1. Позволяет вам контролировать вашу конфиденциальность <b>Особенности Element включают:</b>
2. Позволяет общаться с кем угодно в сети Matrix и даже за ее пределами за счет интеграции с такими приложениями, как Slack - Передовые средства онлайн-общения
3. Защищает вас от рекламы, данных и огороженных стеной садов - Полностью зашифрованные сообщения, обеспечивающие безопасное корпоративное общение даже для удаленных работников
4. Обеспечивает безопасность с помощью сквозного шифрования с перекрестной подписью для проверки других пользователей - Децентрализованный чат на базе платформы Matrix с открытым исходным кодом
- Безопасный обмен файлами с зашифрованными данными при управлении проектами
- Видеочаты с VoIP и совместным использованием экрана
- Простая интеграция с вашими любимыми инструментами для совместной работы в Интернете, средствами управления проектами, VoIP-сервисами и другими приложениями для обмена сообщениями в команде.
Element полностью отличается от других приложений для обмена сообщениями и совместной работы, потому что он децентрализован и имеет открытый исходный код. Element полностью отличается от других приложений для обмена сообщениями и совместной работы. Он работает на базе Matrix, открытой сети для безопасного обмена сообщениями и децентрализованного общения. Он позволяет самостоятельно размещать свои данные и сообщения, предоставляя пользователям максимальный контроль над ними.
Element позволяет вам самостоятельно размещать или выбирать хост-узел, чтобы у вас была конфиденциальность, право собственности и контроль над своими данными и разговорами. Он предоставляет вам доступ к открытой сети, поэтому вы не ограничены общением исключительно с пользователями Element. И он очень надежен и безопаснен. <b>Приватность и зашифрованный обмен сообщениями</b>.
Element защищает вас от нежелательной рекламы, сбора данных и "садов". Он также защищает все ваши данные, видео- и голосовую связь один на один благодаря сквозному шифрованию и перекрестной проверке устройств.
Element может делать все это, потому что он работает на Matrix - стандарте открытого, децентрализованного общения. Element дает вам контроль над вашей конфиденциальностью, позволяя безопасно общаться с любым человеком в сети Matrix или с другими инструментами совместной работы благодаря интеграции с такими приложениями, как Slack.
Element предоставляет вам полный контроль, позволяя выбрать поставщиков услуг, обслуживающих серверы с вашими беседами. Вы свободны выбрать любой способ размещения прямо из приложения Element: <b>Element может быть размещен самостоятельно</b>.
Чтобы обеспечить больший контроль над конфиденциальными данными и разговорами, Element может быть размещен самостоятельно или вы можете выбрать любой хост на базе Matrix - стандарт децентрализованного общения с открытым исходным кодом. Element обеспечивает конфиденциальность, соответствие требованиям безопасности и гибкость интеграции.
1. Получить бесплатную учетную запись на общедоступном сервере matrix.org, размещенном разработчиками Matrix, или выберите один из тысяч общедоступных серверов, размещенных волонтерами. <b>Владение своими данными</b>.
2. Разместить свою учетную запись на собственном сервере Вы сами решаете, где хранить свои данные и сообщения. Без риска добычи данных или доступа третьих лиц.
3. Зарегистрироваться на индивидуальном сервере, просто подписавшись на услуги платформы Element Matrix Services
<b>Почему выбирают Element?</b> Element дает вам возможность контролировать ситуацию различными способами:
1. Получить бесплатный аккаунт на публичном сервере matrix.org, размещенном разработчиками Matrix, или выбрать один из тысяч публичных серверов, размещенных добровольцами.
2. Самостоятельно разместить свою учетную запись, запустив сервер на собственной IT-инфраструктуре.
3. Зарегистрировать учетную запись на пользовательском сервере, просто подписавшись на хостинг-платформу Element Matrix Services.
<b>СОБСТВЕННЫЕ ДАННЫЕ</b>: Вы решаете, где хранить свои данные и сообщения. Вы владеете ими и контролируете их, а не какая-то мегакорпорация, что собирает ваши данные и предоставляет сторонним лицам доступ к ним. <b>Открытый обмен сообщениями и сотрудничество</b>.
Вы можете общаться с любым человеком в сети Matrix, независимо от того, использует ли он Element, другое приложение Matrix или даже если он использует другое приложение для обмена сообщениями.
<b>ОТКРЫТОЕ ОБЩЕНИЕ И СОТРУДНИЧЕСТВО</b>: Вы можете общаться с кем угодно в сети Matrix, независимо от того, используют ли они приложение Element или другое приложение Matrix, и даже если они используют другую систему обмена сообщениями, такую как Slack, IRC или XMPP. <b>Супербезопасно</b>
Настоящее сквозное шифрование (только участники разговора могут расшифровывать сообщения) и проверка устройств с перекрестной подписью.
<b>СУПЕР-БЕЗОПАСНОСТЬ</b>: Настоящее сквозное шифрование (только участники разговора могут расшифровывать сообщения) и перекрестная подпись для проверки устройств участников разговора. <b>Полная коммуникация и интеграция</b>.
Обмен сообщениями, голосовые и видеозвонки, совместное использование файлов, совместное использование экрана и целый ряд интеграций, ботов и виджетов. Создавайте комнаты, сообщества, оставайтесь на связи и выполняйте задачи.
<b>ПОЛНАЯ КОММУНИКАЦИЯ</b>: Обмен сообщениями, голосовые и видеозвонки, совместное использование файлов, совместное использование экрана и целый ряд интеграций, ботов и виджетов. Создавайте комнаты, сообщества, оставайтесь на связи и добивайтесь результатов. <b>Восстанавливайте связь с того места, где остановились</b>.
Оставайтесь на связи, где бы вы ни находились, с полностью синхронизированной историей сообщений на всех ваших устройствах и в Интернете по адресу https://app.element.io
<b>ВЫ ВЕЗДЕ</b>: Оставайтесь на связи, где бы вы ни находились, благодаря полностью синхронизированной истории сообщений на всех ваших устройствах и в Интернете по адресу https://app.element.io.

View file

@ -1 +1 @@
Защищённый децентрализованный чат и звонки. Держите ваши данные в безопасности. Групповой мессенджер - зашифрованные сообщения, групповые беседы и видеовызовы

View file

@ -1 +1 @@
Element (ранее Riot.im) Element - Безопасный мессенджер

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: оновлення теми та стилю та виправлення збоїв після відеовиклику
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.12

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: в поліпшення стабільності та виправлення помилок.
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Основні зміни у цій версії: виправлення проблеми із зашифрованими повідомленнями.
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
此版本中的主要變動:主要是穩定性與臭蟲修復更新。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
此版本中的主要變動:修復關於加密訊息的問題。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -40,7 +40,63 @@ data class HomeServerCapabilities(
*/ */
val roomVersions: RoomVersionCapabilities? = null val roomVersions: RoomVersionCapabilities? = null
) { ) {
enum class RoomCapabilitySupport {
SUPPORTED,
SUPPORTED_UNSTABLE,
UNSUPPORTED,
UNKNOWN
}
/**
* Check if a feature is supported by the homeserver.
* @return
* UNKNOWN if the server does not implement room caps
* UNSUPPORTED if this feature is not supported
* SUPPORTED if this feature is supported by a stable version
* SUPPORTED_UNSTABLE if this feature is supported by an unstable version
* (unstable version should only be used for dev/experimental purpose)
*/
fun isFeatureSupported(feature: String): RoomCapabilitySupport {
if (roomVersions?.capabilities == null) return RoomCapabilitySupport.UNKNOWN
val info = roomVersions.capabilities[feature] ?: return RoomCapabilitySupport.UNSUPPORTED
val preferred = info.preferred ?: info.support.lastOrNull()
val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred }
return when {
versionCap == null -> {
RoomCapabilitySupport.UNKNOWN
}
versionCap.status == RoomVersionStatus.STABLE -> {
RoomCapabilitySupport.SUPPORTED
}
else -> {
RoomCapabilitySupport.SUPPORTED_UNSTABLE
}
}
}
fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean {
if (roomVersions?.capabilities == null) return false
val info = roomVersions.capabilities[feature] ?: return false
return info.preferred == byRoomVersion || info.support.contains(byRoomVersion)
}
/**
* Use this method to know if you should force a version when creating
* a room that requires this feature.
* You can also use #isFeatureSupported prior to this call to check if the
* feature is supported and report some feedback to user.
*/
fun versionOverrideForFeature(feature: String) : String? {
val cap = roomVersions?.capabilities?.get(feature)
return cap?.preferred ?: cap?.support?.lastOrNull()
}
companion object { companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
const val ROOM_CAP_KNOCK = "knock"
const val ROOM_CAP_RESTRICTED = "restricted"
} }
} }

View file

@ -18,7 +18,9 @@ package org.matrix.android.sdk.api.session.homeserver
data class RoomVersionCapabilities( data class RoomVersionCapabilities(
val defaultRoomVersion: String, val defaultRoomVersion: String,
val supportedVersion: List<RoomVersionInfo> val supportedVersion: List<RoomVersionInfo>,
// Keys are capabilities defined per spec, as for now knock or restricted
val capabilities: Map<String, RoomCapabilitySupport>?
) )
data class RoomVersionInfo( data class RoomVersionInfo(
@ -26,6 +28,11 @@ data class RoomVersionInfo(
val status: RoomVersionStatus val status: RoomVersionStatus
) )
data class RoomCapabilitySupport(
val preferred: String?,
val support: List<String>
)
enum class RoomVersionStatus { enum class RoomVersionStatus {
STABLE, STABLE,
UNSTABLE UNSTABLE

View file

@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
open class CreateRoomParams { open class CreateRoomParams {
@ -162,7 +161,7 @@ open class CreateRoomParams {
var roomVersion: String? = null var roomVersion: String? = null
var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null var featurePreset: RoomFeaturePreset? = null
companion object { companion object {
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"

View file

@ -0,0 +1,56 @@
/*
* Copyright 2020 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.room.model.create
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
interface RoomFeaturePreset {
fun updateRoomParams(params: CreateRoomParams)
fun setupInitialStates(): List<Event>?
}
class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset {
override fun updateRoomParams(params: CreateRoomParams) {
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
}
override fun setupInitialStates(): List<Event>? {
return listOf(
Event(
type = EventType.STATE_ROOM_JOIN_RULES,
stateKey = "",
content = RoomJoinRulesContent(
_joinRules = RoomJoinRules.RESTRICTED.value,
allowList = restrictedList
).toContent()
)
)
}
}

View file

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -53,7 +54,7 @@ interface StateService {
/** /**
* Update the join rule and/or the guest access * Update the join rule and/or the guest access
*/ */
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>? = null)
/** /**
* Update the avatar of the room * Update the avatar of the room
@ -91,4 +92,8 @@ interface StateService {
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed * @param eventTypes Set of eventType to observe. If empty, all state events will be observed
*/ */
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>> fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
suspend fun setJoinRulePublic()
suspend fun setJoinRuleInviteOnly()
suspend fun setJoinRuleRestricted(allowList: List<String>)
} }

View file

@ -289,12 +289,12 @@ internal class RealmCryptoStore @Inject constructor(
val devicesToDelete = ArrayList<DeviceInfoEntity>() val devicesToDelete = ArrayList<DeviceInfoEntity>()
userEntity.devices.iterator().forEach { deviceInfoEntity -> userEntity.devices.iterator().forEach { deviceInfoEntity ->
if (deviceInfoEntity.deviceId !in deviceIds) { if (deviceInfoEntity.deviceId !in deviceIds) {
Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId")
devicesToDelete.add(deviceInfoEntity) devicesToDelete.add(deviceInfoEntity)
} }
} }
while (devicesToDelete.isNotEmpty()) { while (devicesToDelete.isNotEmpty()) {
val device = devicesToDelete.removeAt(0) val device = devicesToDelete.removeAt(0)
Timber.d("Remove device ${device.deviceId} of user $userId")
device.deleteOnCascade() device.deleteOnCascade()
} }
// Then update existing devices or add new one // Then update existing devices or add new one

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomCapabilitySupport
import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
@ -45,19 +46,28 @@ internal object HomeServerCapabilitiesMapper {
roomVersionsJson ?: return null roomVersionsJson ?: return null
return tryOrNull { return tryOrNull {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { roomVersions ->
RoomVersionCapabilities( RoomVersionCapabilities(
defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION, defaultRoomVersion = roomVersions.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
supportedVersion = it.available.entries.map { entry -> supportedVersion = roomVersions.available?.entries?.map { entry ->
RoomVersionInfo( RoomVersionInfo(entry.key, RoomVersionStatus.STABLE
version = entry.key, .takeIf { entry.value == "stable" }
status = if (entry.value == "stable") { ?: RoomVersionStatus.UNSTABLE)
RoomVersionStatus.STABLE }.orEmpty(),
} else { capabilities = roomVersions.roomCapabilities?.entries?.mapNotNull { entry ->
RoomVersionStatus.UNSTABLE (entry.value as? Map<*, *>)?.let {
} val preferred = it["preferred"] as? String ?: return@mapNotNull null
) val support = (it["support"] as? List<*>)?.filterIsInstance<String>()
entry.key to RoomCapabilitySupport(preferred, support.orEmpty())
} }
}?.toMap()
// Just for debug purpose
// ?: mapOf(
// HomeServerCapabilities.ROOM_CAP_RESTRICTED to RoomCapabilitySupport(
// preferred = null,
// support = listOf("org.matrix.msc3083")
// )
// )
) )
} }
} }

View file

@ -70,7 +70,22 @@ internal data class RoomVersions(
* Required. A detailed description of the room versions the server supports. * Required. A detailed description of the room versions the server supports.
*/ */
@Json(name = "available") @Json(name = "available")
val available: JsonDict val available: JsonDict? = null,
/**
* "room_capabilities": {
* "knock" : {
* "preferred": "7",
* "support" : ["7"]
* },
* "restricted" : {
* "preferred": "9",
* "support" : ["8", "9"]
* }
* }
*/
@Json(name = "room_capabilities")
val roomCapabilities: JsonDict? = null
) )
// The spec says: If not present, the client should assume that password changes are possible via the API // The spec says: If not present, the client should assume that password changes are possible via the API

View file

@ -17,17 +17,24 @@
package org.matrix.android.sdk.internal.session.permalinks package org.matrix.android.sdk.internal.session.permalinks
import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.RoomGetter
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import java.net.URLEncoder import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
internal class ViaParameterFinder @Inject constructor( internal class ViaParameterFinder @Inject constructor(
@UserId private val userId: String, @UserId private val userId: String,
private val roomGetterProvider: Provider<RoomGetter> private val roomGetterProvider: Provider<RoomGetter>,
private val stateEventDataSource: StateEventDataSource
) { ) {
fun computeViaParams(roomId: String, max: Int): List<String> { fun computeViaParams(roomId: String, max: Int): List<String> {
@ -70,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor(
.orEmpty() .orEmpty()
.toSet() .toSet()
} }
fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
?.filter { userCanInvite(userId, roomId) }
.orEmpty()
.toSet()
return userThatCanInvite.map { it.getDomain() }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
.let { map -> map.keys.sortedByDescending { map[it] } }
.take(max)
}
fun userCanInvite(userId: String, roomId: String): Boolean {
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
?.content?.toModel<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }
return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
}
} }

View file

@ -17,16 +17,10 @@
package org.matrix.android.sdk.internal.session.room.create package org.matrix.android.sdk.internal.session.room.create
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.toMedium import org.matrix.android.sdk.api.session.identity.toMedium
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.DeviceListManager
@ -45,7 +39,6 @@ import javax.inject.Inject
internal class CreateRoomBodyBuilder @Inject constructor( internal class CreateRoomBodyBuilder @Inject constructor(
private val ensureIdentityTokenTask: EnsureIdentityTokenTask, private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
private val crossSigningService: CrossSigningService,
private val deviceListManager: DeviceListManager, private val deviceListManager: DeviceListManager,
private val identityStore: IdentityStore, private val identityStore: IdentityStore,
private val fileUploader: FileUploader, private val fileUploader: FileUploader,
@ -76,19 +69,18 @@ internal class CreateRoomBodyBuilder @Inject constructor(
} }
} }
if (params.joinRuleRestricted != null) { params.featurePreset?.updateRoomParams(params)
params.roomVersion = "org.matrix.msc3083"
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED val initialStates = (
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden listOfNotNull(
}
val initialStates = (listOfNotNull(
buildEncryptionWithAlgorithmEvent(params), buildEncryptionWithAlgorithmEvent(params),
buildHistoryVisibilityEvent(params), buildHistoryVisibilityEvent(params),
buildAvatarEvent(params), buildAvatarEvent(params),
buildGuestAccess(params), buildGuestAccess(params)
buildJoinRulesRestricted(params) )
+ params.featurePreset?.setupInitialStates().orEmpty()
+ buildCustomInitialStates(params)
) )
+ buildCustomInitialStates(params))
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
return CreateRoomBody( return CreateRoomBody(
@ -158,20 +150,6 @@ internal class CreateRoomBodyBuilder @Inject constructor(
} }
} }
private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? {
return params.joinRuleRestricted
?.let { allowList ->
Event(
type = EventType.STATE_ROOM_JOIN_RULES,
stateKey = "",
content = RoomJoinRulesContent(
_joinRules = RoomJoinRules.RESTRICTED.value,
allowList = allowList
).toContent()
)
}
}
/** /**
* Add the crypto algorithm to the room creation parameters. * Add the crypto algorithm to the room creation parameters.
*/ */

View file

@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room.state
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
@ -29,17 +29,20 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.content.FileUploader
import java.lang.UnsupportedOperationException import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
private val stateEventDataSource: StateEventDataSource, private val stateEventDataSource: StateEventDataSource,
private val sendStateTask: SendStateTask, private val sendStateTask: SendStateTask,
private val fileUploader: FileUploader private val fileUploader: FileUploader,
private val viaParameterFinder: ViaParameterFinder
) : StateService { ) : StateService {
@AssistedFactory @AssistedFactory
@ -126,12 +129,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
) )
} }
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) { override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>?) {
if (joinRules != null) { if (joinRules != null) {
if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported") val body = if (joinRules == RoomJoinRules.RESTRICTED) {
RoomJoinRulesContent(
_joinRules = RoomJoinRules.RESTRICTED.value,
allowList = allowList
).toContent()
} else {
mapOf("join_rule" to joinRules)
}
sendStateEvent( sendStateEvent(
eventType = EventType.STATE_ROOM_JOIN_RULES, eventType = EventType.STATE_ROOM_JOIN_RULES,
body = mapOf("join_rule" to joinRules), body = body,
stateKey = null stateKey = null
) )
} }
@ -160,4 +170,20 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
stateKey = null stateKey = null
) )
} }
override suspend fun setJoinRulePublic() {
updateJoinRule(RoomJoinRules.PUBLIC, null)
}
override suspend fun setJoinRuleInviteOnly() {
updateJoinRule(RoomJoinRules.INVITE, null)
}
override suspend fun setJoinRuleRestricted(allowList: List<String>) {
// we need to compute correct via parameters and check if PL are correct
val allowEntries = allowList.map { spaceId ->
RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3))
}
updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries)
}
} }

View file

@ -162,7 +162,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils # android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===103 enum class===105
### Do not import temporary legacy classes ### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3 import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 1 ext.versionMinor = 1
ext.versionPatch = 15 ext.versionPatch = 16
ext.scVersion = 39 ext.scVersion = 39

View file

@ -225,6 +225,7 @@
</activity> </activity>
<activity android:name=".features.roomprofile.RoomProfileActivity" /> <activity android:name=".features.roomprofile.RoomProfileActivity" />
<activity android:name=".features.roomprofile.settings.joinrule.RoomJoinRuleActivity" />
<activity android:name=".features.roomprofile.members.RoomMemberListActivity" /> <activity android:name=".features.roomprofile.members.RoomMemberListActivity" />

View file

@ -108,6 +108,8 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleChooseRestrictedFragment
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
@ -804,4 +806,14 @@ interface FragmentModule {
@IntoMap @IntoMap
@FragmentKey(SpaceManageRoomsFragment::class) @FragmentKey(SpaceManageRoomsFragment::class)
fun bindSpaceManageRoomsFragment(fragment: SpaceManageRoomsFragment): Fragment fun bindSpaceManageRoomsFragment(fragment: SpaceManageRoomsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomJoinRuleFragment::class)
fun bindRoomJoinRuleFragment(fragment: RoomJoinRuleFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomJoinRuleChooseRestrictedFragment::class)
fun bindRoomJoinRuleChooseRestrictedFragment(fragment: RoomJoinRuleChooseRestrictedFragment): Fragment
} }

View file

@ -77,6 +77,7 @@ import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
@ -169,6 +170,7 @@ interface ScreenComponent {
fun inject(activity: SpaceCreationActivity) fun inject(activity: SpaceCreationActivity)
fun inject(activity: SpaceExploreActivity) fun inject(activity: SpaceExploreActivity)
fun inject(activity: SpaceManageActivity) fun inject(activity: SpaceManageActivity)
fun inject(activity: RoomJoinRuleActivity)
/* ========================================================================================== /* ==========================================================================================
* BottomSheets * BottomSheets

View file

@ -328,11 +328,11 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
} }
} }
private fun renderRecordingWaveform(amplitudeList: List<Int>) { private fun renderRecordingWaveform(amplitudeList: Array<Int>) {
views.voicePlaybackWaveform.apply {
post { post {
amplitudeList.forEach { amplitude -> views.voicePlaybackWaveform.apply {
update(amplitude) amplitudeList.iterator().forEach {
update(it)
} }
} }
} }
@ -514,7 +514,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) { override fun onUpdate(state: VoiceMessagePlaybackTracker.Listener.State) {
when (state) { when (state) {
is VoiceMessagePlaybackTracker.Listener.State.Recording -> { is VoiceMessagePlaybackTracker.Listener.State.Recording -> {
renderRecordingWaveform(state.amplitudeList) renderRecordingWaveform(state.amplitudeList.toTypedArray())
} }
is VoiceMessagePlaybackTracker.Listener.State.Playing -> { is VoiceMessagePlaybackTracker.Listener.State.Playing -> {
views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause) views.voicePlaybackControlButton.setImageResource(R.drawable.ic_play_pause_pause)

View file

@ -40,10 +40,17 @@ class MigrateRoomBottomSheet :
VectorBaseBottomSheetDialogFragment<BottomSheetRoomUpgradeBinding>(), VectorBaseBottomSheetDialogFragment<BottomSheetRoomUpgradeBinding>(),
MigrateRoomViewModel.Factory { MigrateRoomViewModel.Factory {
enum class MigrationReason {
MANUAL,
FOR_RESTRICTED
}
@Parcelize @Parcelize
data class Args( data class Args(
val roomId: String, val roomId: String,
val newVersion: String val newVersion: String,
val reason: MigrationReason = MigrationReason.MANUAL,
val customDescription: CharSequence? = null
) : Parcelable ) : Parcelable
@Inject @Inject
@ -62,11 +69,22 @@ class MigrateRoomBottomSheet :
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
views.headerText.setText(if (state.isPublic) R.string.upgrade_public_room else R.string.upgrade_private_room) views.headerText.setText(if (state.isPublic) R.string.upgrade_public_room else R.string.upgrade_private_room)
if (state.migrationReason == MigrationReason.MANUAL) {
views.descriptionText.text = getString(R.string.upgrade_room_warning)
views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion) views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion)
} else if (state.migrationReason == MigrationReason.FOR_RESTRICTED) {
views.descriptionText.setTextOrHide(state.customDescription)
views.upgradeFromTo.text = getString(R.string.upgrade_room_for_restricted_note)
}
if (state.autoMigrateMembersAndParents) {
views.autoUpdateParent.isVisible = false
views.autoInviteSwitch.isVisible = false
} else {
views.autoInviteSwitch.isVisible = !state.isPublic && state.otherMemberCount > 0 views.autoInviteSwitch.isVisible = !state.isPublic && state.otherMemberCount > 0
views.autoUpdateParent.isVisible = state.knownParents.isNotEmpty() views.autoUpdateParent.isVisible = state.knownParents.isNotEmpty()
}
when (state.upgradingStatus) { when (state.upgradingStatus) {
is Loading -> { is Loading -> {
@ -143,9 +161,12 @@ class MigrateRoomBottomSheet :
const val REQUEST_KEY = "MigrateRoomBottomSheetRequest" const val REQUEST_KEY = "MigrateRoomBottomSheetRequest"
const val BUNDLE_KEY_REPLACEMENT_ROOM = "BUNDLE_KEY_REPLACEMENT_ROOM" const val BUNDLE_KEY_REPLACEMENT_ROOM = "BUNDLE_KEY_REPLACEMENT_ROOM"
fun newInstance(roomId: String, newVersion: String): MigrateRoomBottomSheet { fun newInstance(roomId: String, newVersion: String,
reason: MigrationReason = MigrationReason.MANUAL,
customDescription: CharSequence? = null
): MigrateRoomBottomSheet {
return MigrateRoomBottomSheet().apply { return MigrateRoomBottomSheet().apply {
setArguments(Args(roomId, newVersion)) setArguments(Args(roomId, newVersion, reason, customDescription))
} }
} }
} }

View file

@ -90,11 +90,23 @@ class MigrateRoomViewModel @AssistedInject constructor(
copy(upgradingStatus = Loading()) copy(upgradingStatus = Loading())
} }
session.coroutineScope.launch { session.coroutineScope.launch {
val userToInvite = if (state.autoMigrateMembersAndParents) {
summary?.otherMemberIds?.takeIf { !state.isPublic }
} else {
summary?.otherMemberIds?.takeIf { state.shouldIssueInvites }
}.orEmpty()
val parentSpaceToUpdate = if (state.autoMigrateMembersAndParents) {
summary?.flattenParentIds
} else {
summary?.flattenParentIds?.takeIf { state.shouldUpdateKnownParents }
}.orEmpty()
val result = upgradeRoomViewModelTask.execute(UpgradeRoomViewModelTask.Params( val result = upgradeRoomViewModelTask.execute(UpgradeRoomViewModelTask.Params(
roomId = state.roomId, roomId = state.roomId,
newVersion = state.newVersion, newVersion = state.newVersion,
userIdsToAutoInvite = summary?.otherMemberIds?.takeIf { state.shouldIssueInvites } ?: emptyList(), userIdsToAutoInvite = userToInvite,
parentSpaceToUpdate = summary?.flattenParentIds?.takeIf { state.shouldUpdateKnownParents } ?: emptyList(), parentSpaceToUpdate = parentSpaceToUpdate,
progressReporter = { indeterminate, progress, total -> progressReporter = { indeterminate, progress, total ->
setState { setState {
copy( copy(

View file

@ -23,6 +23,7 @@ import com.airbnb.mvrx.Uninitialized
data class MigrateRoomViewState( data class MigrateRoomViewState(
val roomId: String, val roomId: String,
val newVersion: String, val newVersion: String,
val customDescription: CharSequence? = null,
val currentVersion: String? = null, val currentVersion: String? = null,
val isPublic: Boolean = false, val isPublic: Boolean = false,
val shouldIssueInvites: Boolean = false, val shouldIssueInvites: Boolean = false,
@ -32,10 +33,15 @@ data class MigrateRoomViewState(
val upgradingStatus: Async<UpgradeRoomViewModelTask.Result> = Uninitialized, val upgradingStatus: Async<UpgradeRoomViewModelTask.Result> = Uninitialized,
val upgradingProgress: Int = 0, val upgradingProgress: Int = 0,
val upgradingProgressTotal: Int = 0, val upgradingProgressTotal: Int = 0,
val upgradingProgressIndeterminate: Boolean = true val upgradingProgressIndeterminate: Boolean = true,
val migrationReason: MigrateRoomBottomSheet.MigrationReason = MigrateRoomBottomSheet.MigrationReason.MANUAL,
val autoMigrateMembersAndParents: Boolean = false
) : MvRxState { ) : MvRxState {
constructor(args: MigrateRoomBottomSheet.Args) : this( constructor(args: MigrateRoomBottomSheet.Args) : this(
roomId = args.roomId, roomId = args.roomId,
newVersion = args.newVersion newVersion = args.newVersion,
migrationReason = args.reason,
autoMigrateMembersAndParents = args.reason == MigrateRoomBottomSheet.MigrationReason.FOR_RESTRICTED,
customDescription = args.customDescription
) )
} }

View file

@ -18,12 +18,13 @@ package im.vector.app.features.roomdirectory.createroom
import android.net.Uri import android.net.Uri
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
sealed class CreateRoomAction : VectorViewModelAction { sealed class CreateRoomAction : VectorViewModelAction {
data class SetAvatar(val imageUri: Uri?) : CreateRoomAction() data class SetAvatar(val imageUri: Uri?) : CreateRoomAction()
data class SetName(val name: String) : CreateRoomAction() data class SetName(val name: String) : CreateRoomAction()
data class SetTopic(val topic: String) : CreateRoomAction() data class SetTopic(val topic: String) : CreateRoomAction()
data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction() data class SetVisibility(val rule: RoomJoinRules) : CreateRoomAction()
data class SetRoomAliasLocalPart(val aliasLocalPart: String) : CreateRoomAction() data class SetRoomAliasLocalPart(val aliasLocalPart: String) : CreateRoomAction()
data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction() data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction()

View file

@ -21,6 +21,7 @@ import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.dividerItem
import im.vector.app.core.epoxy.profiles.buildProfileAction
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsSectionTitleItem import im.vector.app.features.discovery.settingsSectionTitleItem
import im.vector.app.features.form.formAdvancedToggleItem import im.vector.app.features.form.formAdvancedToggleItem
@ -30,6 +31,7 @@ import im.vector.app.features.form.formSubmitButtonItem
import im.vector.app.features.form.formSwitchItem import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject import javax.inject.Inject
class CreateRoomController @Inject constructor( class CreateRoomController @Inject constructor(
@ -92,24 +94,56 @@ class CreateRoomController @Inject constructor(
settingsSectionTitleItem { settingsSectionTitleItem {
id("settingsSection") id("settingsSection")
titleResId(R.string.create_room_settings_section) titleResId(R.string.create_room_settings_section)
id("visibility")
titleResId(R.string.room_settings_room_access_title)
}
when (viewState.roomJoinRules) {
RoomJoinRules.INVITE -> {
buildProfileAction(
id = "joinRule",
title = stringProvider.getString(R.string.room_settings_room_access_private_title),
subtitle = stringProvider.getString(R.string.room_settings_room_access_private_description),
divider = false,
editable = true,
action = { host.listener?.selectVisibility() }
)
}
RoomJoinRules.PUBLIC -> {
buildProfileAction(
id = "joinRule",
title = stringProvider.getString(R.string.room_settings_room_access_public_title),
subtitle = stringProvider.getString(R.string.room_settings_room_access_public_description),
divider = false,
editable = true,
action = { host.listener?.selectVisibility() }
)
}
RoomJoinRules.RESTRICTED -> {
buildProfileAction(
id = "joinRule",
title = stringProvider.getString(R.string.room_settings_room_access_restricted_title),
subtitle = stringProvider.getString(R.string.room_create_member_of_space_name_can_join, viewState.parentSpaceSummary?.displayName),
divider = false,
editable = true,
action = { host.listener?.selectVisibility() }
)
}
else -> {
// not yet supported
}
} }
formSwitchItem {
id("public")
enabled(enableFormElement)
title(host.stringProvider.getString(R.string.create_room_public_title))
summary(host.stringProvider.getString(R.string.create_room_public_description))
switchChecked(viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public)
listener { value -> settingsSectionTitleItem {
host.listener?.setIsPublic(value) id("settingsSection")
titleResId(R.string.create_room_settings_section)
} }
}
if (viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) { if (viewState.roomJoinRules == RoomJoinRules.PUBLIC) {
// Room alias for public room // Room alias for public room
formEditTextItem { formEditTextItem {
id("alias") id("alias")
enabled(enableFormElement) enabled(enableFormElement)
value(viewState.roomVisibilityType.aliasLocalPart) value(viewState.aliasLocalPart)
suffixText(":" + viewState.homeServerName) suffixText(":" + viewState.homeServerName)
prefixText("#") prefixText("#")
hint(host.stringProvider.getString(R.string.room_alias_address_hint)) hint(host.stringProvider.getString(R.string.room_alias_address_hint))
@ -144,9 +178,9 @@ class CreateRoomController @Inject constructor(
} }
} }
} }
dividerItem { // dividerItem {
id("divider1") // id("divider1")
} // }
formAdvancedToggleItem { formAdvancedToggleItem {
id("showAdvanced") id("showAdvanced")
title(host.stringProvider.getString(if (viewState.showAdvanced) R.string.hide_advanced else R.string.show_advanced)) title(host.stringProvider.getString(if (viewState.showAdvanced) R.string.hide_advanced else R.string.show_advanced))
@ -177,7 +211,7 @@ class CreateRoomController @Inject constructor(
fun onAvatarChange() fun onAvatarChange()
fun onNameChange(newName: String) fun onNameChange(newName: String)
fun onTopicChange(newTopic: String) fun onTopicChange(newTopic: String)
fun setIsPublic(isPublic: Boolean) fun selectVisibility()
fun setAliasLocalPart(aliasLocalPart: String) fun setAliasLocalPart(aliasLocalPart: String)
fun setIsEncrypted(isEncrypted: Boolean) fun setIsEncrypted(isEncrypted: Boolean)
fun toggleShowAdvanced() fun toggleShowAdvanced()

View file

@ -40,9 +40,12 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentCreateRoomBinding import im.vector.app.databinding.FragmentCreateRoomBinding
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.toOption
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject import javax.inject.Inject
@Parcelize @Parcelize
@ -64,6 +67,8 @@ class CreateRoomFragment @Inject constructor(
private val viewModel: CreateRoomViewModel by fragmentViewModel() private val viewModel: CreateRoomViewModel by fragmentViewModel()
private val args: CreateRoomArgs by args() private val args: CreateRoomArgs by args()
private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreateRoomBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreateRoomBinding {
@ -74,6 +79,7 @@ class CreateRoomFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
vectorBaseActivity.setSupportActionBar(views.createRoomToolbar) vectorBaseActivity.setSupportActionBar(views.createRoomToolbar)
sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRoomJoinRuleSharedActionViewModel()
setupWaitingView() setupWaitingView()
setupRecyclerView() setupRecyclerView()
views.createRoomClose.debouncedClicks { views.createRoomClose.debouncedClicks {
@ -87,6 +93,16 @@ class CreateRoomFragment @Inject constructor(
} }
} }
private fun setupRoomJoinRuleSharedActionViewModel() {
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
roomJoinRuleSharedActionViewModel
.observe()
.subscribe { action ->
viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule))
}
.disposeOnDestroyView()
}
override fun showFailure(throwable: Throwable) { override fun showFailure(throwable: Throwable) {
// Note: RoomAliasError are displayed directly in the form // Note: RoomAliasError are displayed directly in the form
if (throwable !is CreateRoomFailure.AliasError) { if (throwable !is CreateRoomFailure.AliasError) {
@ -130,9 +146,19 @@ class CreateRoomFragment @Inject constructor(
viewModel.handle(CreateRoomAction.SetTopic(newTopic)) viewModel.handle(CreateRoomAction.SetTopic(newTopic))
} }
override fun setIsPublic(isPublic: Boolean) { override fun selectVisibility() = withState(viewModel) { state ->
viewModel.handle(CreateRoomAction.SetIsPublic(isPublic))
val allowed = if (state.supportsRestricted) {
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC, RoomJoinRules.RESTRICTED)
} else {
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC)
} }
RoomJoinRuleBottomSheet.newInstance(state.roomJoinRules, allowed.map { it.toOption(false) })
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
}
// override fun setIsPublic(isPublic: Boolean) {
// viewModel.handle(CreateRoomAction.SetIsPublic(isPublic))
// }
override fun setAliasLocalPart(aliasLocalPart: String) { override fun setAliasLocalPart(aliasLocalPart: String) {
viewModel.handle(CreateRoomAction.SetRoomAliasLocalPart(aliasLocalPart)) viewModel.handle(CreateRoomAction.SetRoomAliasLocalPart(aliasLocalPart))

View file

@ -32,22 +32,28 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
import timber.log.Timber import timber.log.Timber
class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState, class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState,
private val session: Session, private val session: Session,
private val rawService: RawService private val rawService: RawService,
private val vectorPreferences: VectorPreferences
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) { ) : VectorViewModel<CreateRoomViewState, CreateRoomAction, CreateRoomViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -58,6 +64,27 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
init { init {
initHomeServerName() initHomeServerName()
initAdminE2eByDefault() initAdminE2eByDefault()
val restrictedSupport = session.getHomeServerCapabilities().isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
val createRestricted = when (restrictedSupport) {
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
else -> false
}
val defaultJoinRules = if (initialState.parentSpaceId != null && createRestricted) {
RoomJoinRules.RESTRICTED
} else {
RoomJoinRules.INVITE
}
setState {
copy(
supportsRestricted = createRestricted,
roomJoinRules = defaultJoinRules,
parentSpaceSummary = initialState.parentSpaceId?.let { session.getRoomSummary(it) }
)
}
} }
private fun initHomeServerName() { private fun initHomeServerName() {
@ -80,7 +107,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
setState { setState {
copy( copy(
isEncrypted = roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Private && adminE2EByDefault, isEncrypted = RoomJoinRules.INVITE == roomJoinRules && adminE2EByDefault,
hsAdminHasDisabledE2E = !adminE2EByDefault hsAdminHasDisabledE2E = !adminE2EByDefault
) )
} }
@ -102,7 +129,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
is CreateRoomAction.SetAvatar -> setAvatar(action) is CreateRoomAction.SetAvatar -> setAvatar(action)
is CreateRoomAction.SetName -> setName(action) is CreateRoomAction.SetName -> setName(action)
is CreateRoomAction.SetTopic -> setTopic(action) is CreateRoomAction.SetTopic -> setTopic(action)
is CreateRoomAction.SetIsPublic -> setIsPublic(action) is CreateRoomAction.SetVisibility -> setVisibility(action)
is CreateRoomAction.SetRoomAliasLocalPart -> setRoomAliasLocalPart(action) is CreateRoomAction.SetRoomAliasLocalPart -> setRoomAliasLocalPart(action)
is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action) is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action)
is CreateRoomAction.Create -> doCreateRoom() is CreateRoomAction.Create -> doCreateRoom()
@ -149,36 +176,46 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
private fun setTopic(action: CreateRoomAction.SetTopic) = setState { copy(roomTopic = action.topic) } private fun setTopic(action: CreateRoomAction.SetTopic) = setState { copy(roomTopic = action.topic) }
private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState { private fun setVisibility(action: CreateRoomAction.SetVisibility) = setState {
if (action.isPublic) { when (action.rule) {
RoomJoinRules.PUBLIC -> {
copy( copy(
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(""), roomJoinRules = RoomJoinRules.PUBLIC,
// Reset any error in the form about alias // Reset any error in the form about alias
asyncCreateRoomRequest = Uninitialized, asyncCreateRoomRequest = Uninitialized,
isEncrypted = false isEncrypted = false
) )
} else { }
RoomJoinRules.RESTRICTED -> {
copy( copy(
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Private, roomJoinRules = RoomJoinRules.RESTRICTED,
// Reset any error in the form about alias
asyncCreateRoomRequest = Uninitialized,
isEncrypted = adminE2EByDefault
)
}
// RoomJoinRules.INVITE,
// RoomJoinRules.KNOCK,
// RoomJoinRules.PRIVATE,
else -> {
// default to invite
copy(
roomJoinRules = RoomJoinRules.INVITE,
isEncrypted = adminE2EByDefault isEncrypted = adminE2EByDefault
) )
} }
} }
}
private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) { private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) {
withState { state ->
if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) {
setState { setState {
copy( copy(
roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(action.aliasLocalPart), aliasLocalPart = action.aliasLocalPart,
// Reset any error in the form about alias // Reset any error in the form about alias
asyncCreateRoomRequest = Uninitialized asyncCreateRoomRequest = Uninitialized
) )
} }
} }
}
// Else ignore
}
private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) } private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) }
@ -187,8 +224,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
return@withState return@withState
} }
if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public if (state.roomJoinRules == RoomJoinRules.PUBLIC && state.aliasLocalPart.isNullOrBlank()) {
&& state.roomVisibilityType.aliasLocalPart.isBlank()) {
// we require an alias for public rooms // we require an alias for public rooms
setState { setState {
copy(asyncCreateRoomRequest = Fail(CreateRoomFailure.AliasError(RoomAliasError.AliasIsBlank))) copy(asyncCreateRoomRequest = Fail(CreateRoomFailure.AliasError(RoomAliasError.AliasIsBlank)))
@ -205,15 +241,30 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init
name = state.roomName.takeIf { it.isNotBlank() } name = state.roomName.takeIf { it.isNotBlank() }
topic = state.roomTopic.takeIf { it.isNotBlank() } topic = state.roomTopic.takeIf { it.isNotBlank() }
avatarUri = state.avatarUri avatarUri = state.avatarUri
when (state.roomVisibilityType) { when (state.roomJoinRules) {
is CreateRoomViewState.RoomVisibilityType.Public -> { RoomJoinRules.PUBLIC -> {
// Directory visibility // Directory visibility
visibility = RoomDirectoryVisibility.PUBLIC visibility = RoomDirectoryVisibility.PUBLIC
// Preset // Preset
preset = CreateRoomPreset.PRESET_PUBLIC_CHAT preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
roomAliasName = state.roomVisibilityType.aliasLocalPart roomAliasName = state.aliasLocalPart
} }
is CreateRoomViewState.RoomVisibilityType.Private -> { RoomJoinRules.RESTRICTED -> {
state.parentSpaceId?.let {
featurePreset = RestrictedRoomPreset(
session.getHomeServerCapabilities(),
listOf(RoomJoinRulesAllowEntry(
state.parentSpaceId,
listOf(state.homeServerName)
))
)
}
}
// RoomJoinRules.KNOCK ->
// RoomJoinRules.PRIVATE ->
// RoomJoinRules.INVITE
else -> {
// by default create invite only
// Directory visibility // Directory visibility
visibility = RoomDirectoryVisibility.PRIVATE visibility = RoomDirectoryVisibility.PRIVATE
// Preset // Preset

View file

@ -20,20 +20,24 @@ import android.net.Uri
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomSummary
data class CreateRoomViewState( data class CreateRoomViewState(
val avatarUri: Uri? = null, val avatarUri: Uri? = null,
val roomName: String = "", val roomName: String = "",
val roomTopic: String = "", val roomTopic: String = "",
val roomVisibilityType: RoomVisibilityType = RoomVisibilityType.Private, val roomJoinRules: RoomJoinRules = RoomJoinRules.INVITE,
val isEncrypted: Boolean = false, val isEncrypted: Boolean = false,
val showAdvanced: Boolean = false, val showAdvanced: Boolean = false,
val disableFederation: Boolean = false, val disableFederation: Boolean = false,
val homeServerName: String = "", val homeServerName: String = "",
val hsAdminHasDisabledE2E: Boolean = false, val hsAdminHasDisabledE2E: Boolean = false,
val asyncCreateRoomRequest: Async<String> = Uninitialized, val asyncCreateRoomRequest: Async<String> = Uninitialized,
val parentSpaceId: String? val parentSpaceId: String?,
val parentSpaceSummary: RoomSummary? = null,
val supportsRestricted: Boolean = false,
val aliasLocalPart: String? = null
) : MvRxState { ) : MvRxState {
constructor(args: CreateRoomArgs) : this( constructor(args: CreateRoomArgs) : this(
@ -47,10 +51,5 @@ data class CreateRoomViewState(
fun isEmpty() = avatarUri == null fun isEmpty() = avatarUri == null
&& roomName.isEmpty() && roomName.isEmpty()
&& roomTopic.isEmpty() && roomTopic.isEmpty()
&& (roomVisibilityType as? RoomVisibilityType.Public)?.aliasLocalPart?.isEmpty().orTrue() && aliasLocalPart.isNullOrEmpty()
sealed class RoomVisibilityType {
object Private : RoomVisibilityType()
data class Public(val aliasLocalPart: String) : RoomVisibilityType()
}
} }

View file

@ -44,7 +44,7 @@ import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -179,10 +179,8 @@ class RoomSettingsFragment @Inject constructor(
.show(childFragmentManager, "RoomHistoryVisibilityBottomSheet") .show(childFragmentManager, "RoomHistoryVisibilityBottomSheet")
} }
override fun onJoinRuleClicked() = withState(viewModel) { state -> override fun onJoinRuleClicked() {
val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules startActivity(RoomJoinRuleActivity.newIntent(requireContext(), roomProfileArgs.roomId))
RoomJoinRuleBottomSheet.newInstance(currentJoinRule)
.show(childFragmentManager, "RoomJoinRuleBottomSheet")
} }
override fun onToggleGuestAccess() = withState(viewModel) { state -> override fun onToggleGuestAccess() = withState(viewModel) { state ->

View file

@ -27,6 +27,7 @@ import dagger.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.settings.VectorPreferences
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
@ -34,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
@ -44,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap import org.matrix.android.sdk.rx.unwrap
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
private val vectorPreferences: VectorPreferences,
private val session: Session) private val session: Session)
: VectorViewModel<RoomSettingsViewState, RoomSettingsAction, RoomSettingsViewEvents>(initialState) { : VectorViewModel<RoomSettingsViewState, RoomSettingsAction, RoomSettingsViewEvents>(initialState) {
@ -73,6 +76,24 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
observeGuestAccess() observeGuestAccess()
observeRoomAvatar() observeRoomAvatar()
observeState() observeState()
val homeServerCapabilities = session.getHomeServerCapabilities()
val canUseRestricted = homeServerCapabilities
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion())
val restrictedSupport = homeServerCapabilities.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
val couldUpgradeToRestricted = when (restrictedSupport) {
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
else -> false
}
setState {
copy(
supportsRestricted = canUseRestricted,
canUpgradeToRestricted = couldUpgradeToRestricted
)
}
} }
private fun observeState() { private fun observeState() {

View file

@ -43,7 +43,9 @@ data class RoomSettingsViewState(
val newHistoryVisibility: RoomHistoryVisibility? = null, val newHistoryVisibility: RoomHistoryVisibility? = null,
val newRoomJoinRules: NewJoinRule = NewJoinRule(), val newRoomJoinRules: NewJoinRule = NewJoinRule(),
val showSaveAction: Boolean = false, val showSaveAction: Boolean = false,
val actionPermissions: ActionPermissions = ActionPermissions() val actionPermissions: ActionPermissions = ActionPermissions(),
val supportsRestricted: Boolean = false,
val canUpgradeToRestricted: Boolean = false
) : MvRxState { ) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId) constructor(args: RoomProfileArgs) : this(roomId = args.roomId)

View file

@ -0,0 +1,149 @@
/*
* Copyright (c) 2021 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.settings.joinrule
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.view.isVisible
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.viewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivitySimpleBinding
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedEvents
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
import javax.inject.Inject
class RoomJoinRuleActivity : VectorBaseActivity<ActivitySimpleBinding>(),
RoomJoinRuleChooseRestrictedViewModel.Factory {
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
private lateinit var roomProfileArgs: RoomProfileArgs
@Inject
lateinit var allowListViewModelFactory: RoomJoinRuleChooseRestrictedViewModel.Factory
@Inject
lateinit var errorFormatter: ErrorFormatter
val viewModel: RoomJoinRuleChooseRestrictedViewModel by viewModel()
override fun create(initialState: RoomJoinRuleChooseRestrictedState) = allowListViewModelFactory.create(initialState)
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun initUiAndData() {
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
if (isFirstCreation()) {
addFragment(
R.id.simpleFragmentContainer,
RoomJoinRuleFragment::class.java,
roomProfileArgs
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.selectSubscribe(this, RoomJoinRuleChooseRestrictedState::updatingStatus) {
when (it) {
Uninitialized -> {
// nop
}
is Loading -> {
views.simpleActivityWaitingView.isVisible = true
}
is Success -> {
withState(viewModel) { state ->
if (state.didSwitchToReplacementRoom) {
// we should navigate to new room
navigator.openRoom(this, state.roomId, null, true)
}
finish()
}
}
is Fail -> {
views.simpleActivityWaitingView.isVisible = false
toast(errorFormatter.toHumanReadable(it.error))
}
}
}
viewModel.observeViewEvents {
when (it) {
RoomJoinRuleChooseRestrictedEvents.NavigateToChooseRestricted -> navigateToChooseRestricted()
is RoomJoinRuleChooseRestrictedEvents.NavigateToUpgradeRoom -> navigateToUpgradeRoom(it)
}
}
supportFragmentManager.setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY, this) { _, bundle ->
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
viewModel.handle(RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration(replacementRoomId))
}
}
}
private fun navigateToUpgradeRoom(events: RoomJoinRuleChooseRestrictedEvents.NavigateToUpgradeRoom) {
MigrateRoomBottomSheet.newInstance(
events.roomId,
events.toVersion,
MigrateRoomBottomSheet.MigrationReason.FOR_RESTRICTED,
events.description
).show(supportFragmentManager, "migrate")
}
private fun navigateToChooseRestricted() {
supportFragmentManager.commitTransaction {
setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
val tag = RoomJoinRuleChooseRestrictedFragment::class.simpleName
replace(R.id.simpleFragmentContainer,
RoomJoinRuleChooseRestrictedFragment::class.java,
this@RoomJoinRuleActivity.roomProfileArgs.toMvRxBundle(),
tag
).addToBackStack(tag)
}
}
companion object {
fun newIntent(context: Context, roomId: String): Intent {
val roomProfileArgs = RoomProfileArgs(roomId)
return Intent(context, RoomJoinRuleActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, roomProfileArgs)
}
}
}
}

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2021 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.settings.joinrule
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.ItemStyle
import im.vector.app.core.ui.list.genericButtonItem
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import timber.log.Timber
import javax.inject.Inject
class RoomJoinRuleAdvancedController @Inject constructor(
private val stringProvider: StringProvider,
private val colorProvider: ColorProvider,
private val avatarRenderer: AvatarRenderer
) : TypedEpoxyController<RoomJoinRuleChooseRestrictedState>() {
interface InteractionListener {
fun didSelectRule(rules: RoomJoinRules)
}
var interactionListener: InteractionListener? = null
override fun buildModels(state: RoomJoinRuleChooseRestrictedState?) {
state ?: return
val choices = state.choices ?: return
val host = this
genericFooterItem {
id("header")
text(host.stringProvider.getString(R.string.room_settings_room_access_title))
centered(false)
style(ItemStyle.TITLE)
textColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
}
genericFooterItem {
id("desc")
text(host.stringProvider.getString(R.string.decide_who_can_find_and_join))
centered(false)
}
// invite only
RoomJoinRuleRadioAction(
roomJoinRule = RoomJoinRules.INVITE,
description = stringProvider.getString(R.string.room_settings_room_access_private_description),
title = stringProvider.getString(R.string.room_settings_room_access_private_invite_only_title),
isSelected = state.currentRoomJoinRules == RoomJoinRules.INVITE
).toRadioBottomSheetItem().let {
it.listener {
interactionListener?.didSelectRule(RoomJoinRules.INVITE)
// listener?.didSelectAction(action)
}
add(it)
}
if (choices.firstOrNull { it.rule == RoomJoinRules.RESTRICTED } != null) {
val restrictedRule = choices.first { it.rule == RoomJoinRules.RESTRICTED }
Timber.w("##@@ ${state.updatedAllowList}")
spaceJoinRuleItem {
id("restricted")
avatarRenderer(host.avatarRenderer)
needUpgrade(restrictedRule.needUpgrade)
selected(state.currentRoomJoinRules == RoomJoinRules.RESTRICTED)
restrictedList(state.updatedAllowList)
listener { host.interactionListener?.didSelectRule(RoomJoinRules.RESTRICTED) }
}
}
// Public
RoomJoinRuleRadioAction(
roomJoinRule = RoomJoinRules.PUBLIC,
description = stringProvider.getString(R.string.room_settings_room_access_public_description),
title = stringProvider.getString(R.string.room_settings_room_access_public_title),
isSelected = state.currentRoomJoinRules == RoomJoinRules.PUBLIC
).toRadioBottomSheetItem().let {
it.listener {
interactionListener?.didSelectRule(RoomJoinRules.PUBLIC)
}
add(it)
}
genericButtonItem {
id("save")
text("")
}
}
}

View file

@ -28,9 +28,18 @@ import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject import javax.inject.Inject
@Parcelize
data class JoinRulesOptionSupport(
val rule: RoomJoinRules,
val needUpgrade: Boolean = false
) : Parcelable
fun RoomJoinRules.toOption(needUpgrade: Boolean) = JoinRulesOptionSupport(this, needUpgrade)
@Parcelize @Parcelize
data class RoomJoinRuleBottomSheetArgs( data class RoomJoinRuleBottomSheetArgs(
val currentRoomJoinRule: RoomJoinRules val currentRoomJoinRule: RoomJoinRules,
val allowedJoinedRules: List<JoinRulesOptionSupport>
) : Parcelable ) : Parcelable
class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRuleRadioAction>() { class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRuleRadioAction>() {
@ -61,9 +70,15 @@ class RoomJoinRuleBottomSheet : BottomSheetGeneric<RoomJoinRuleState, RoomJoinRu
} }
companion object { companion object {
fun newInstance(currentRoomJoinRule: RoomJoinRules): RoomJoinRuleBottomSheet { fun newInstance(currentRoomJoinRule: RoomJoinRules,
allowedJoinedRules: List<JoinRulesOptionSupport> = listOf(
RoomJoinRules.INVITE, RoomJoinRules.PUBLIC
).map { it.toOption(true) }
): RoomJoinRuleBottomSheet {
return RoomJoinRuleBottomSheet().apply { return RoomJoinRuleBottomSheet().apply {
setArguments(RoomJoinRuleBottomSheetArgs(currentRoomJoinRule)) setArguments(
RoomJoinRuleBottomSheetArgs(currentRoomJoinRule, allowedJoinedRules)
)
} }
} }
} }

View file

@ -51,7 +51,7 @@ class RoomJoinRuleController @Inject constructor(
description = stringProvider.getString(R.string.room_settings_room_access_restricted_description), description = stringProvider.getString(R.string.room_settings_room_access_restricted_description),
title = span { title = span {
+stringProvider.getString(R.string.room_settings_room_access_restricted_title) +stringProvider.getString(R.string.room_settings_room_access_restricted_title)
+ " " +" "
image( image(
drawableProvider.getDrawable(R.drawable.ic_beta_pill)!!, drawableProvider.getDrawable(R.drawable.ic_beta_pill)!!,
"bottom" "bottom"
@ -59,6 +59,6 @@ class RoomJoinRuleController @Inject constructor(
}, },
isSelected = state.currentRoomJoinRule == RoomJoinRules.RESTRICTED isSelected = state.currentRoomJoinRule == RoomJoinRules.RESTRICTED
) )
) ).filter { state.allowedJoinedRules.map { it.rule }.contains(it.roomJoinRule) }
} }
} }

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2021 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.settings.joinrule
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.Loading
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentJoinRulesRecyclerBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import javax.inject.Inject
class RoomJoinRuleFragment @Inject constructor(
val controller: RoomJoinRuleAdvancedController,
val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentJoinRulesRecyclerBinding>(),
OnBackPressed, RoomJoinRuleAdvancedController.InteractionListener {
private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
FragmentJoinRulesRecyclerBinding.inflate(inflater, container, false)
override fun onBackPressed(toolbarButton: Boolean): Boolean {
val hasUnsavedChanges = withState(viewModel) { it.hasUnsavedChanges }
val isLoading = withState(viewModel) { it.updatingStatus is Loading }
if (!hasUnsavedChanges || isLoading) {
requireActivity().finish()
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.dialog_title_warning)
.setMessage(R.string.warning_unsaved_change)
.setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ ->
requireActivity().finish()
}
.setNegativeButton(R.string.cancel, null)
.show()
return true
}
return true
}
override fun invalidate() = withState(viewModel) { state ->
super.invalidate()
controller.setData(state)
if (state.hasUnsavedChanges) {
// show discard and save
views.cancelButton.isVisible = true
views.positiveButton.text = getString(R.string.warning_unsaved_change_discard)
views.positiveButton.isVisible = true
views.positiveButton.text = getString(R.string.save)
views.positiveButton.debouncedClicks {
viewModel.handle(RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules)
}
} else {
views.cancelButton.isVisible = false
views.positiveButton.isVisible = true
views.positiveButton.text = getString(R.string.ok)
views.positiveButton.debouncedClicks { requireActivity().finish() }
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
views.genericRecyclerView.configureWith(controller, hasFixedSize = true)
controller.interactionListener = this
views.cancelButton.debouncedClicks { requireActivity().finish() }
}
override fun onDestroyView() {
views.genericRecyclerView.cleanup()
super.onDestroyView()
}
override fun didSelectRule(rules: RoomJoinRules) {
val isLoading = withState(viewModel) { it.updatingStatus is Loading }
if (isLoading) return
viewModel.handle(RoomJoinRuleChooseRestrictedActions.SelectJoinRules(rules))
}
}

View file

@ -22,10 +22,13 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
data class RoomJoinRuleState( data class RoomJoinRuleState(
val currentRoomJoinRule: RoomJoinRules = RoomJoinRules.INVITE, val currentRoomJoinRule: RoomJoinRules = RoomJoinRules.INVITE,
val allowedJoinedRules: List<JoinRulesOptionSupport> =
listOf(RoomJoinRules.INVITE, RoomJoinRules.PUBLIC).map { it.toOption(true) },
val currentGuestAccess: GuestAccess? = null val currentGuestAccess: GuestAccess? = null
) : BottomSheetGenericState() { ) : BottomSheetGenericState() {
constructor(args: RoomJoinRuleBottomSheetArgs) : this( constructor(args: RoomJoinRuleBottomSheetArgs) : this(
currentRoomJoinRule = args.currentRoomJoinRule currentRoomJoinRule = args.currentRoomJoinRule,
allowedJoinedRules = args.allowedJoinedRules
) )
} }

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2021 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.settings.joinrule
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
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.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_bottom_sheet_joinrule_restricted)
abstract class SpaceJoinRuleItem : VectorEpoxyModel<SpaceJoinRuleItem.Holder>() {
@EpoxyAttribute
var selected: Boolean = false
@EpoxyAttribute
var needUpgrade: Boolean = false
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var restrictedList: List<MatrixItem> = emptyList()
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
lateinit var listener: ClickListener
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.onClick(listener)
holder.upgradeRequiredButton.setOnClickListener(DebouncedClickListener(listener))
if (selected) {
holder.radioImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_radio_on))
holder.radioImage.contentDescription = holder.view.context.getString(R.string.a11y_checked)
} else {
holder.radioImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_radio_off))
holder.radioImage.contentDescription = holder.view.context.getString(R.string.a11y_unchecked)
}
holder.upgradeRequiredButton.isVisible = needUpgrade
holder.helperText.isVisible = selected
val items = listOf(holder.space1, holder.space2, holder.space3, holder.space4, holder.space5)
holder.spaceMore.isVisible = false
items.onEach { it.isVisible = false }
if (!needUpgrade) {
if (restrictedList.isEmpty()) {
holder.listTitle.isVisible = false
} else {
holder.listTitle.isVisible = true
restrictedList.forEachIndexed { index, matrixItem ->
if (index < items.size) {
items[index].isVisible = true
avatarRenderer.render(matrixItem, items[index])
} else if (index == items.size) {
holder.spaceMore.isVisible = true
}
}
}
} else {
holder.listTitle.isVisible = false
holder.helperText.isVisible = false
}
}
class Holder : VectorEpoxyHolder() {
val radioImage by bind<ImageView>(R.id.radioIcon)
val actionTitle by bind<TextView>(R.id.actionTitle)
val actionDescription by bind<TextView>(R.id.actionDescription)
val upgradeRequiredButton by bind<Button>(R.id.upgradeRequiredButton)
val listTitle by bind<TextView>(R.id.listTitle)
val space1 by bind<ImageView>(R.id.rest1)
val space2 by bind<ImageView>(R.id.rest2)
val space3 by bind<ImageView>(R.id.rest3)
val space4 by bind<ImageView>(R.id.rest4)
val space5 by bind<ImageView>(R.id.rest5)
val spaceMore by bind<ImageView>(R.id.rest6)
val helperText by bind<TextView>(R.id.helperText)
}
}

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2021 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.settings.joinrule.advanced
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.spaces.manage.roomSelectionItem
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject
class ChooseRestrictedController @Inject constructor(
private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer
) : TypedEpoxyController<RoomJoinRuleChooseRestrictedState>() {
interface Listener {
fun onItemSelected(matrixItem: MatrixItem)
}
var listener: Listener? = null
override fun buildModels(data: RoomJoinRuleChooseRestrictedState?) {
data ?: return
val host = this
if (data.filter.isNotEmpty()) {
when (val results = data.filteredResults) {
Uninitialized,
is Fail -> return
is Loading -> loadingItem { id("filter_load") }
is Success -> {
if (results.invoke().isEmpty()) {
noResultItem {
id("empty")
text(host.stringProvider.getString(R.string.no_result_placeholder))
}
} else {
results.invoke().forEach { matrixItem ->
roomSelectionItem {
id(matrixItem.id)
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
itemClickListener { host.listener?.onItemSelected(matrixItem) }
}
}
}
}
}
return
}
// when no filters
genericFooterItem {
id("h1")
text(host.stringProvider.getString(R.string.space_you_know_that_contains_this_room))
centered(false)
}
data.possibleSpaceCandidate.forEach { matrixItem ->
roomSelectionItem {
id(matrixItem.id)
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
itemClickListener { host.listener?.onItemSelected(matrixItem) }
}
}
if (data.unknownRestricted.isNotEmpty()) {
genericFooterItem {
id("others")
text(host.stringProvider.getString(R.string.other_spaces_or_rooms_you_might_not_know))
centered(false)
}
data.unknownRestricted.forEach { matrixItem ->
roomSelectionItem {
id(matrixItem.id)
matrixItem(matrixItem)
avatarRenderer(host.avatarRenderer)
selected(data.updatedAllowList.firstOrNull { it.id == matrixItem.id } != null)
itemClickListener { host.listener?.onItemSelected(matrixItem) }
}
}
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 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.settings.joinrule.advanced
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.util.MatrixItem
sealed class RoomJoinRuleChooseRestrictedActions : VectorViewModelAction {
data class FilterWith(val filter: String) : RoomJoinRuleChooseRestrictedActions()
data class ToggleSelection(val matrixItem: MatrixItem) : RoomJoinRuleChooseRestrictedActions()
data class SelectJoinRules(val rules: RoomJoinRules) : RoomJoinRuleChooseRestrictedActions()
object DoUpdateJoinRules : RoomJoinRuleChooseRestrictedActions()
data class SwitchToRoomAfterMigration(val roomId: String) : RoomJoinRuleChooseRestrictedActions()
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 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.settings.joinrule.advanced
import im.vector.app.core.platform.VectorViewEvents
sealed class RoomJoinRuleChooseRestrictedEvents : VectorViewEvents {
object NavigateToChooseRestricted : RoomJoinRuleChooseRestrictedEvents()
data class NavigateToUpgradeRoom(val roomId: String, val toVersion: String, val description: CharSequence) : RoomJoinRuleChooseRestrictedEvents()
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2021 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.settings.joinrule
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceRestrictedSelectBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.settings.joinrule.advanced.ChooseRestrictedController
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.util.MatrixItem
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
val controller: ChooseRestrictedController,
val avatarRenderer: AvatarRenderer
) : VectorBaseFragment<FragmentSpaceRestrictedSelectBinding>(),
ChooseRestrictedController.Listener,
OnBackPressed {
private val viewModel: RoomJoinRuleChooseRestrictedViewModel by activityViewModel(RoomJoinRuleChooseRestrictedViewModel::class)
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
FragmentSpaceRestrictedSelectBinding.inflate(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
controller.listener = this
views.recyclerView.configureWith(controller)
views.roomsFilter.queryTextChanges()
.debounce(500, TimeUnit.MILLISECONDS)
.subscribeBy {
viewModel.handle(RoomJoinRuleChooseRestrictedActions.FilterWith(it.toString()))
}
.disposeOnDestroyView()
views.okButton.debouncedClicks {
parentFragmentManager.popBackStack()
}
}
override fun onDestroyView() {
controller.listener = null
views.recyclerView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state ->
super.invalidate()
controller.setData(state)
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
val filter = views.roomsFilter.query
if (filter.isEmpty()) {
parentFragmentManager.popBackStack()
} else {
views.roomsFilter.setQuery("", true)
}
return true
}
override fun onItemSelected(matrixItem: MatrixItem) {
viewModel.handle(RoomJoinRuleChooseRestrictedActions.ToggleSelection(matrixItem))
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2021 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.settings.joinrule.advanced
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.settings.joinrule.JoinRulesOptionSupport
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem
data class RoomJoinRuleChooseRestrictedState(
// the currentRoomId
val roomId: String,
val roomSummary: Async<RoomSummary> = Uninitialized,
val initialRoomJoinRules: RoomJoinRules? = null,
val currentRoomJoinRules: RoomJoinRules? = null,
val updatedAllowList: List<MatrixItem> = emptyList(),
val choices: List<JoinRulesOptionSupport>? = null,
val initialAllowList: List<RoomJoinRulesAllowEntry> = emptyList(),
val possibleSpaceCandidate: List<MatrixItem> = emptyList(),
val unknownRestricted: List<MatrixItem> = emptyList(),
val filter: String = "",
val filteredResults: Async<List<MatrixItem>> = Uninitialized,
val hasUnsavedChanges: Boolean = false,
val updatingStatus: Async<Unit> = Uninitialized,
val upgradeNeededForRestricted: Boolean = false,
val restrictedSupportedByThisVersion: Boolean = false,
val restrictedVersionNeeded: String? = null,
val didSwitchToReplacementRoom: Boolean = false
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}

View file

@ -0,0 +1,398 @@
/*
* Copyright (c) 2021 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.settings.joinrule.advanced
import android.graphics.Typeface
import androidx.core.text.toSpannable
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.styleMatchingText
import im.vector.app.features.roomprofile.settings.joinrule.toOption
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor(
@Assisted initialState: RoomJoinRuleChooseRestrictedState,
private val session: Session,
private val vectorPreferences: VectorPreferences,
private val stringProvider: StringProvider
) : VectorViewModel<RoomJoinRuleChooseRestrictedState, RoomJoinRuleChooseRestrictedActions, RoomJoinRuleChooseRestrictedEvents>(initialState) {
var room = session.getRoom(initialState.roomId)!!
init {
viewModelScope.launch {
initializeForRoom(initialState.roomId)
}
}
private fun initializeForRoom(roomId: String) {
room = session.getRoom(roomId)!!
session.getRoomSummary(roomId)?.let { roomSummary ->
val joinRulesContent = room.getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
?.content
?.toModel<RoomJoinRulesContent>()
val initialAllowList = joinRulesContent?.allowList
val knownParentSpacesAllowed = mutableListOf<MatrixItem>()
val unknownAllowedOrRooms = mutableListOf<MatrixItem>()
initialAllowList.orEmpty().forEach { entry ->
val summary = session.getRoomSummary(entry.spaceID)
if (summary == null // it's not known by me
|| summary.roomType != RoomType.SPACE // it's not a space
|| !roomSummary.flattenParentIds.contains(summary.roomId) // it's not a parent space
) {
unknownAllowedOrRooms.add(
summary?.toMatrixItem() ?: MatrixItem.RoomItem(entry.spaceID, null, null)
)
} else {
knownParentSpacesAllowed.add(summary.toMatrixItem())
}
}
val possibleSpaceCandidate = knownParentSpacesAllowed.toMutableList()
roomSummary.flattenParentIds.mapNotNull {
session.getRoomSummary(it)?.toMatrixItem()
}.forEach {
if (!possibleSpaceCandidate.contains(it)) {
possibleSpaceCandidate.add(it)
}
}
val homeServerCapabilities = session.getHomeServerCapabilities()
var safeRule: RoomJoinRules = joinRulesContent?.joinRules ?: RoomJoinRules.INVITE
// server is not really checking that, just to be sure let's check
val restrictedSupportedByThisVersion = homeServerCapabilities
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED, room.getRoomVersion())
if (safeRule == RoomJoinRules.RESTRICTED
&& !restrictedSupportedByThisVersion) {
safeRule = RoomJoinRules.INVITE
}
val restrictedSupport = homeServerCapabilities.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
val couldUpgradeToRestricted = when (restrictedSupport) {
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
else -> false
}
val choices = if (restrictedSupportedByThisVersion || couldUpgradeToRestricted) {
listOf(
RoomJoinRules.INVITE.toOption(false),
RoomJoinRules.RESTRICTED.toOption(!restrictedSupportedByThisVersion),
RoomJoinRules.PUBLIC.toOption(false)
)
} else {
listOf(
RoomJoinRules.INVITE.toOption(false),
RoomJoinRules.PUBLIC.toOption(false)
)
}
setState {
copy(
roomSummary = Success(roomSummary),
initialRoomJoinRules = safeRule,
currentRoomJoinRules = safeRule,
choices = choices,
initialAllowList = initialAllowList.orEmpty(),
updatedAllowList = initialAllowList.orEmpty().map {
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
},
possibleSpaceCandidate = possibleSpaceCandidate,
unknownRestricted = unknownAllowedOrRooms,
restrictedSupportedByThisVersion = restrictedSupportedByThisVersion,
upgradeNeededForRestricted = !restrictedSupportedByThisVersion && couldUpgradeToRestricted,
restrictedVersionNeeded = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
)
}
}
}
fun checkForChanges() = withState { state ->
if (state.initialRoomJoinRules != state.currentRoomJoinRules) {
setState {
copy(hasUnsavedChanges = true)
}
return@withState
}
if (state.currentRoomJoinRules == RoomJoinRules.RESTRICTED) {
val allowDidChange = state.initialAllowList.map { it.spaceID } != state.updatedAllowList.map { it.id }
setState {
copy(hasUnsavedChanges = allowDidChange)
}
return@withState
}
setState {
copy(hasUnsavedChanges = false)
}
}
@AssistedFactory
interface Factory {
fun create(initialState: RoomJoinRuleChooseRestrictedState): RoomJoinRuleChooseRestrictedViewModel
}
override fun handle(action: RoomJoinRuleChooseRestrictedActions) {
when (action) {
is RoomJoinRuleChooseRestrictedActions.FilterWith -> handleFilter(action)
is RoomJoinRuleChooseRestrictedActions.ToggleSelection -> handleToggleSelection(action)
is RoomJoinRuleChooseRestrictedActions.SelectJoinRules -> handleSelectRule(action)
is RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration -> handleSwitchToRoom(action)
RoomJoinRuleChooseRestrictedActions.DoUpdateJoinRules -> handleSubmit()
}.exhaustive
checkForChanges()
}
fun handleSubmit() = withState { state ->
setState { copy(updatingStatus = Loading()) }
viewModelScope.launch {
try {
when (state.currentRoomJoinRules) {
RoomJoinRules.PUBLIC -> room.setJoinRulePublic()
RoomJoinRules.INVITE -> room.setJoinRuleInviteOnly()
RoomJoinRules.RESTRICTED -> room.setJoinRuleRestricted(state.updatedAllowList.map { it.id })
RoomJoinRules.KNOCK,
RoomJoinRules.PRIVATE,
null -> {
throw UnsupportedOperationException()
}
}
setState { copy(updatingStatus = Success(Unit)) }
} catch (failure: Throwable) {
setState { copy(updatingStatus = Fail(failure)) }
}
}
}
fun handleSelectRule(action: RoomJoinRuleChooseRestrictedActions.SelectJoinRules) = withState { state ->
val currentRoomJoinRules = state.currentRoomJoinRules
val candidate = session.getRoomSummary(state.roomId)
?.flattenParentIds
?.filter {
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == state.roomId } != null
}?.mapNotNull {
session.getRoomSummary(it)?.toMatrixItem()
}?.firstOrNull()
val description = if (candidate != null) {
stringProvider.getString(R.string.upgrade_room_for_restricted, candidate.getBestName()).toSpannable().let {
it.styleMatchingText(candidate.getBestName(), Typeface.BOLD)
}
} else {
stringProvider.getString(R.string.upgrade_room_for_restricted_no_param)
}
if (action.rules == RoomJoinRules.RESTRICTED && state.upgradeNeededForRestricted) {
// let's show the room upgrade bottom sheet
_viewEvents.post(
RoomJoinRuleChooseRestrictedEvents.NavigateToUpgradeRoom(
state.roomId,
state.restrictedVersionNeeded ?: "",
description
)
)
return@withState
}
if (action.rules == RoomJoinRules.RESTRICTED && currentRoomJoinRules != RoomJoinRules.RESTRICTED) {
// switching to restricted
// if allow list is empty, then default to current space parents
if (state.updatedAllowList.isEmpty()) {
val candidates = session.getRoomSummary(state.roomId)
?.flattenParentIds
?.filter {
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == state.roomId } != null
}?.mapNotNull {
session.getRoomSummary(it)?.toMatrixItem()
}.orEmpty()
setState {
copy(updatedAllowList = candidates)
}
}
}
setState {
copy(
currentRoomJoinRules = action.rules
)
}
if (action.rules == RoomJoinRules.RESTRICTED && currentRoomJoinRules == RoomJoinRules.RESTRICTED) {
_viewEvents.post(RoomJoinRuleChooseRestrictedEvents.NavigateToChooseRestricted)
}
}
private fun handleSwitchToRoom(action: RoomJoinRuleChooseRestrictedActions.SwitchToRoomAfterMigration) = withState { state ->
viewModelScope.launch {
val oldRoomSummary = session.getRoomSummary(state.roomId)
val replacementRoomSummary = session.getRoomSummary(action.roomId)
setState {
copy(
roomId = action.roomId,
roomSummary = replacementRoomSummary?.let { Success(it) } ?: Uninitialized,
didSwitchToReplacementRoom = true
)
}
initializeForRoom(action.roomId)
// set as restricted now
val candidates = oldRoomSummary
?.flattenParentIds
?.filter {
session.getRoomSummary(it)?.spaceChildren?.firstOrNull { it.childRoomId == state.roomId } != null
}?.mapNotNull {
session.getRoomSummary(it)?.toMatrixItem()
}.orEmpty()
setState {
copy(
currentRoomJoinRules = RoomJoinRules.RESTRICTED,
updatedAllowList = candidates
)
}
setState { copy(updatingStatus = Loading()) }
viewModelScope.launch {
try {
room.setJoinRuleRestricted(candidates.map { it.id })
setState { copy(updatingStatus = Success(Unit)) }
} catch (failure: Throwable) {
setState { copy(updatingStatus = Fail(failure)) }
}
}
}
}
private fun handleToggleSelection(action: RoomJoinRuleChooseRestrictedActions.ToggleSelection) = withState { state ->
val selection = state.updatedAllowList.toMutableList()
if (selection.indexOfFirst { action.matrixItem.id == it.id } != -1) {
selection.removeAll { it.id == action.matrixItem.id }
} else {
selection.add(action.matrixItem)
}
val unknownAllowedOrRooms = mutableListOf<MatrixItem>()
// we would like to keep initial allowed here to show them unchecked
// to make it easier for users to spot the changes
val union = mutableListOf<MatrixItem>().apply {
addAll(
state.initialAllowList.map {
session.getRoomSummary(it.spaceID)?.toMatrixItem() ?: MatrixItem.RoomItem(it.spaceID, null, null)
}
)
addAll(selection)
}.distinctBy { it.id }.sortedBy { it.id }
union.forEach { entry ->
val summary = session.getRoomSummary(entry.id)
if (summary == null) {
unknownAllowedOrRooms.add(
entry
)
} else if (summary.roomType != RoomType.SPACE) {
unknownAllowedOrRooms.add(entry)
} else if (!state.roomSummary.invoke()!!.flattenParentIds.contains(entry.id)) {
// it's a space but not a direct parent
unknownAllowedOrRooms.add(entry)
} else {
// nop
}
}
setState {
copy(
updatedAllowList = selection.toList(),
unknownRestricted = unknownAllowedOrRooms
)
}
}
private fun handleFilter(action: RoomJoinRuleChooseRestrictedActions.FilterWith) = withState { state ->
setState {
copy(filter = action.filter, filteredResults = Loading())
}
viewModelScope.launch {
if (vectorPreferences.developerMode()) {
// in developer mode we let you choose any room or space to restrict to
val filteredCandidates = session.getRoomSummaries(
roomSummaryQueryParams {
excludeType = null
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
memberships = listOf(Membership.JOIN)
}
).map { it.toMatrixItem() }
setState {
copy(
filteredResults = Success(filteredCandidates)
)
}
} else {
// in normal mode you can only restrict to space parents
setState {
copy(
filteredResults = Success(
session.getRoomSummary(state.roomId)?.flattenParentIds?.mapNotNull {
session.getRoomSummary(it)?.toMatrixItem()
}?.filter {
it.displayName?.contains(filter, true) == true
}.orEmpty()
)
)
}
}
}
}
companion object : MvRxViewModelFactory<RoomJoinRuleChooseRestrictedViewModel, RoomJoinRuleChooseRestrictedState> {
override fun create(viewModelContext: ViewModelContext, state: RoomJoinRuleChooseRestrictedState)
: RoomJoinRuleChooseRestrictedViewModel? {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
}

View file

@ -24,11 +24,13 @@ import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -93,15 +95,28 @@ class CreateSpaceViewModelTask @Inject constructor(
} }
) )
} else { } else {
if (vectorPreferences.labsUseExperimentalRestricted()) { val homeServerCapabilities = session
.getHomeServerCapabilities()
val restrictedSupport = homeServerCapabilities
.isFeatureSupported(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
val createRestricted = when (restrictedSupport) {
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED -> true
HomeServerCapabilities.RoomCapabilitySupport.SUPPORTED_UNSTABLE -> vectorPreferences.labsUseExperimentalRestricted()
else -> false
}
if (createRestricted) {
session.createRoom(CreateRoomParams().apply { session.createRoom(CreateRoomParams().apply {
this.name = roomName this.name = roomName
this.joinRuleRestricted = listOf( this.featurePreset = RestrictedRoomPreset(
homeServerCapabilities,
listOf(
RoomJoinRulesAllowEntry( RoomJoinRulesAllowEntry(
spaceID = spaceID, spaceID = spaceID,
via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList() via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList()
) )
) )
)
if (e2eByDefault) { if (e2eByDefault) {
this.enableEncryption() this.enableEncryption()
} }

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