Merge branch 'release/1.1.12' into main
This commit is contained in:
commit
2da0379505
CHANGES.mdCONTRIBUTING.mdbuild.gradle
fastlane/metadata/android
cs-CZ/changelogs
en-US/changelogs
fr-FR
gradle/wrapper
gradlewlibrary/ui-styles/src
debug
java/im/vector/lib/ui/styles/debug
res/layout
main
matrix-sdk-android
build.gradle
src/main/java/org/matrix/android/sdk
api/session/room
internal
multipicker
newsfragment
vector
build.gradle
src
debug/res/layout
main/java/im/vector/app
AppStateHandler.ktVectorApplication.kt
core
di
dialogs
extensions
platform
ui/views
features
auth
call
crypto
keysbackup
restore
setup
quads
SharedSecureStorageAction.ktSharedSecureStorageViewModel.ktSharedSecuredStoragePassphraseFragment.kt
recover
home
17
CHANGES.md
17
CHANGES.md
@ -1,3 +1,20 @@
|
||||
Changes in Element 1.1.12 (2021-07-05)
|
||||
======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling. ([#3545](https://github.com/vector-im/element-android/issues/3545))
|
||||
- Implements new design for Jump to unread and quick fix visibility issues. ([#3547](https://github.com/vector-im/element-android/issues/3547))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Fix some issues with timeline cache invalidation and visibility. ([#3542](https://github.com/vector-im/element-android/issues/3542))
|
||||
- Fix call invite processed after call is ended because of fastlane mode. ([#3564](https://github.com/vector-im/element-android/issues/3564))
|
||||
- Fix crash after video call. ([#3577](https://github.com/vector-im/element-android/issues/3577))
|
||||
- Fix crash out of memory ([#3583](https://github.com/vector-im/element-android/issues/3583))
|
||||
- CryptoStore migration has to be object to avoid crash ([#3605](https://github.com/vector-im/element-android/issues/3605))
|
||||
|
||||
|
||||
Changes in Element v1.1.11 (2021-06-22)
|
||||
=======================================
|
||||
|
||||
|
@ -63,7 +63,7 @@ Supported filename extensions are:
|
||||
- ``.bugfix``: Signifying a bug fix.
|
||||
- ``.doc``: Signifying a documentation improvement.
|
||||
- ``.removal``: Signifying a deprecation or removal of public API. Can be used to notifying about API change in the Matrix SDK
|
||||
- ``.misc``: A ticket has been closed, but it is not of interest to users. Note that in this case, the content of the file will not be output, but just the issue/PR number.
|
||||
- ``.misc``: Any other changes.
|
||||
|
||||
See https://github.com/twisted/towncrier#news-fragments if you need more details.
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
buildscript {
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
ext.kotlin_version = '1.5.10'
|
||||
ext.kotlin_version = '1.5.20'
|
||||
ext.kotlin_coroutines_version = "1.5.0"
|
||||
repositories {
|
||||
google()
|
||||
|
2
fastlane/metadata/android/cs-CZ/changelogs/40101070.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40101070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavní změny v této verzi: beta podpora pro Spaces. Komprimace videa před odesláním.
|
||||
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.7
|
2
fastlane/metadata/android/cs-CZ/changelogs/40101080.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40101080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavní změny v této verzi: vylepšení pro Spaces
|
||||
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.8
|
2
fastlane/metadata/android/cs-CZ/changelogs/40101090.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40101090.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Hlavní změny v této verzi: doplněna podpora pro síť gitter.im
|
||||
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.9
|
2
fastlane/metadata/android/en-US/changelogs/40101120.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40101120.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Main changes in this version: theme and style update and fix a crash after video call
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.12
|
2
fastlane/metadata/android/fr-FR/changelogs/40101070.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40101070.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : prise en charge des espaces en bêta. Compression des vidéos avant envoi.
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.7
|
2
fastlane/metadata/android/fr-FR/changelogs/40101080.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40101080.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : amélioration des espaces.
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.8
|
2
fastlane/metadata/android/fr-FR/changelogs/40101090.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40101090.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Principaux changements pour cette version : ajout de la prise en charge de gitter.im
|
||||
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.9
|
@ -1,30 +1,39 @@
|
||||
Element est une nouvelle application de messagerie et de collaboration qui :
|
||||
Element est à la fois une messagerie sécurisée et une application de collaboration en équipe, idéale pour les conversations de groupe en télétravail. Cette application utilise le chiffrement de bout en bout. Elle permet de mettre en place des téléconférences vidéo, du partage de fichier et des appels vocaux.
|
||||
|
||||
1. Vous permet de préserver votre vie privée
|
||||
2. Vous permet de communiquer avec n’importe qui sur réseau Matrix, et plus encore grâce aux intégrations d’autres applications telles que Slack ou Discord
|
||||
3. Vous protège de la publicité et de la collecte de données
|
||||
4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs
|
||||
<b>Les fonctionnalités d’Element incluent :</b>
|
||||
- Outils de communication en ligne avancés
|
||||
- Communication d’entreprise sécurisée par le chiffrement de bout en bout des messages, même pour les travailleurs à distance
|
||||
- Messagerie décentralisée basée sur le framework open source Matrix
|
||||
- Partage sécurisé de fichiers avec chiffrement des données lors de la gestion de projet
|
||||
- Conversations vidéo par voix sur IP et partage d’écran
|
||||
- Intégration facile avec vos outils de collaboration, de gestion de projet, services de VoIP et autres applications de messagerie
|
||||
|
||||
Element est complètement différente des autres applications de messagerie et de collaboration puisque l’application est décentralisée et open-source.
|
||||
Element est complètement différente des autres applications de messagerie et de collaboration. Elle s’appuie sur Matrix, un réseau ouvert de communication décentralisée. Elle permet l’auto-hébergement pour que ses utilisateurs restent le plus en contrôle possible de leurs données et leurs messages.
|
||||
|
||||
Element vous permet d’héberger vous-même ou de choisir un hôte vous permettant d’assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous donne accès à un réseau ouvert. Vous n’êtes donc pas condamné à parler à d’autres utilisateurs d’Element seulement. Et c'est très sécurisé.
|
||||
<b>Confidentialité et messagerie chiffrée</b>
|
||||
Element vous protège des publicités non désirées, du minage de données et des prisons dorées. Elle protègé vos données et vos communications vocales grâce au chiffrement de bout en bout et à la vérification de signature croisée entre appareils.
|
||||
|
||||
Element peut faire tout ça car elle est basée sur Matrix, le protocole standard pour la communication ouverte et décentralisée.
|
||||
Element vous donne la main sur votre confidentialité en vous permettant de communiquer de manière sécurisée avec tout le réseau Matrix ou d’autres applications de communication d’entreprise au travers d’intégrations d’applications comme Slack.
|
||||
|
||||
Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières :
|
||||
<b>Element peut être auto-hébergé</b>
|
||||
Pour une meilleure souveraineté sur vos données et conversations, Element peut être auto-hébergé ou vous pouvez choisir votre hôte Matrix - la norme open source pour les communications décentralisées. Element garantit votre confidentialité, conformité aux normes de sécurité, tout en proposant une intégration souple.
|
||||
|
||||
1. Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parmi les milliers de serveurs public hébergés par des bénévoles
|
||||
2. Héberger vous-même votre compte en installant un serveur sur votre propre machine
|
||||
3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
|
||||
<b>Vos données vous appartiennent</b>
|
||||
Vous décidez où stocker vos données et messages. Aucun risque de minage de données où d’accès par des tierce parties.
|
||||
|
||||
<b>Pourquoi choisir Element ?</b>
|
||||
Element vous place aux commandes de différente manières :
|
||||
1. Inscrivez vous sur le serveur public matrix.org hébergé par les développeurs de Matrix ou choisissez parmi des milliers de serveurs publics hébergés par des bénévoles
|
||||
2. Auto-hébergez votre compte sur un serveur de votre proper infrastructure informatique
|
||||
3. Inscrivez vous à la plateforme d’hébergement Element Matrix Services
|
||||
|
||||
<b>VOS DONNÉES VOUS APPARTIENNENT</b> : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant.
|
||||
<b>Messagerie et collaboration ouvertes</b>
|
||||
Vous pouvez discuter avec tout le réseau Matrix, que vos interlocuteurs utilisent Element, une autre application Matrix, ou même s’ils utilisent une application complètement différente.
|
||||
|
||||
<b>MESSAGERIE ET COLLABORATION OUVERTES</b> : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP.
|
||||
<b>Ultra sécurisé</b>
|
||||
Chiffrement de bout en bout (seules les personnes dans la conversation peuvent déchiffrer les messages) et vérification de signature croisée entre appareils.
|
||||
|
||||
<b>ULTRA SÉCURISÉ</b> : chiffrement de bout en bout (seuls les membres d’une conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs.
|
||||
<b>Communication et intégration parfaites</b>
|
||||
Messagerie instantannée, appels audio et vidéo, partage de fichier, partage d’écran et bien d’autres intégrations, bots et widgets. Lancez des salons, des communautés, restez en contact et menez vos projets à bien.
|
||||
|
||||
<b>TOUTES VOS COMMUNICATIONS</b> : messagerie, appels audio et vidéo, partage de fichier, partage d’écran et un grand nombre d’intégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets.
|
||||
|
||||
<b>PARTOUT AVEC VOUS</b> : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.
|
||||
<b>Reprenez où vous vous êtes arrêté</b>
|
||||
Restez en contact où que vous soyez grâce à l’historique des messages synchronisé entre tous vos appareils et sur le web sur https://app.element.io
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||
distributionSha256Sum=a9e356a21595348b6f04b024ed0b08ac8aea6b2ac37e6c0ef58e51549cd7b9cb
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
2
gradlew
vendored
2
gradlew
vendored
@ -72,7 +72,7 @@ case "`uname`" in
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
|
@ -24,6 +24,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import im.vector.lib.ui.styles.R
|
||||
import im.vector.lib.ui.styles.databinding.ActivityDebugMaterialThemeBinding
|
||||
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
|
||||
|
||||
// Rendering is not the same with VectorBaseActivity
|
||||
abstract class DebugMaterialThemeActivity : AppCompatActivity() {
|
||||
@ -50,14 +51,20 @@ abstract class DebugMaterialThemeActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
views.debugShowDialog.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Dialog title")
|
||||
.setMessage("Dialog content")
|
||||
.setIcon(R.drawable.ic_debug_icon)
|
||||
.setPositiveButton("Positive", null)
|
||||
.setNegativeButton("Negative", null)
|
||||
.setNeutralButton("Neutral", null)
|
||||
.show()
|
||||
showTestDialog(0)
|
||||
}
|
||||
|
||||
views.debugShowDialogDestructive.setOnClickListener {
|
||||
showTestDialog(R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
|
||||
}
|
||||
|
||||
views.debugShowDialogNegativeDestructive.setOnClickListener {
|
||||
showTestDialog(R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
||||
}
|
||||
|
||||
views.debugShowProgressDialog.setOnClickListener {
|
||||
MaterialProgressDialog(this)
|
||||
.show(message = "Progress Dialog\nLine 2", cancellable = true)
|
||||
}
|
||||
|
||||
views.debugShowBottomSheet.setOnClickListener {
|
||||
@ -65,6 +72,17 @@ abstract class DebugMaterialThemeActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showTestDialog(theme: Int) {
|
||||
MaterialAlertDialogBuilder(this, theme)
|
||||
.setTitle("Dialog title")
|
||||
.setMessage("Dialog content\nLine 2")
|
||||
.setIcon(R.drawable.ic_debug_icon)
|
||||
.setPositiveButton("Positive", null)
|
||||
.setNegativeButton("Negative", null)
|
||||
.setNeutralButton("Neutral", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_debug, menu)
|
||||
return true
|
||||
|
@ -17,6 +17,8 @@
|
||||
package im.vector.lib.ui.styles.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import im.vector.lib.ui.styles.databinding.ActivityDebugTextViewBinding
|
||||
|
||||
@ -27,5 +29,20 @@ abstract class DebugVectorTextViewActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
val views = ActivityDebugTextViewBinding.inflate(layoutInflater)
|
||||
setContentView(views.root)
|
||||
|
||||
views.debugShowPassword.setOnClickListener {
|
||||
views.debugTextInputEditText.showPassword(true)
|
||||
}
|
||||
views.debugHidePassword.setOnClickListener {
|
||||
views.debugTextInputEditText.showPassword(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun EditText.showPassword(visible: Boolean) {
|
||||
if (visible) {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
} else {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -452,6 +452,27 @@
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="Show Dialog" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debugShowDialogDestructive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="Show Dialog Destructive" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debugShowDialogNegativeDestructive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="Show Dialog Neg Destructive" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debugShowProgressDialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="Show Progress Dialog" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debugShowBottomSheet"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<TextView
|
||||
@ -65,4 +67,34 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Default (TextAppearance.Vector.Body)\nline 2" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/debugTextInputLayout"
|
||||
style="@style/Widget.Vector.TextInputLayout.Password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Password"
|
||||
app:errorEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/debugTextInputEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/debugShowPassword"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Show password" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debugHidePassword"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hide password" />
|
||||
|
||||
</LinearLayout>
|
37
library/ui-styles/src/main/java/MaterialProgressDialog.kt
Normal file
37
library/ui-styles/src/main/java/MaterialProgressDialog.kt
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.lib.ui.styles.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.lib.ui.styles.R
|
||||
import im.vector.lib.ui.styles.databinding.DialogProgressMaterialBinding
|
||||
|
||||
class MaterialProgressDialog(val context: Context) {
|
||||
fun show(message: CharSequence, cancellable: Boolean = false): AlertDialog {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_progress_material, null)
|
||||
val views = DialogProgressMaterialBinding.bind(view)
|
||||
views.message.text = message
|
||||
|
||||
return MaterialAlertDialogBuilder(context)
|
||||
.setCancelable(cancellable)
|
||||
.setView(view)
|
||||
.show()
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Inspired from https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/layout/progress_dialog.xml -->
|
||||
<LinearLayout
|
||||
android:id="@+id/body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@android:id/progress"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:max="10000" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message"
|
||||
style="@style/Widget.Vector.TextView.Body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:text="Content\nLine 2" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
13
library/ui-styles/src/main/res/values/dimens_font.xml
Normal file
13
library/ui-styles/src/main/res/values/dimens_font.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="text_size_title">24sp</dimen>
|
||||
<dimen name="text_size_headline">18sp</dimen>
|
||||
<dimen name="text_size_subtitle">16sp</dimen>
|
||||
<dimen name="text_size_body">14sp</dimen>
|
||||
<dimen name="text_size_caption">12sp</dimen>
|
||||
<dimen name="text_size_micro">10sp</dimen>
|
||||
|
||||
<dimen name="text_size_button">14sp</dimen>
|
||||
|
||||
</resources>
|
@ -1,28 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="AlertDialog.Vector.Light" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
||||
<item name="colorPrimary">@color/palette_element_green</item>
|
||||
<item name="colorSecondary">@color/palette_element_green</item>
|
||||
<item name="colorSurface">@color/element_system_light</item>
|
||||
<item name="colorOnSurface">@color/element_content_primary_light</item>
|
||||
<item name="colorError">@color/element_alert_light</item>
|
||||
<!--item name="alertDialogStyle">@style/MaterialAlertDialog.App</item>
|
||||
<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.App.Title.Text</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.App.Button</item>
|
||||
<item name="buttonBarNeutralButtonStyle">@style/Widget.App.Button</item-->
|
||||
<style name="ThemeOverlay.Vector.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
||||
<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.Vector.Title.Text</item>
|
||||
<item name="materialAlertDialogBodyTextStyle">@style/MaterialAlertDialog.Vector.Body.Text</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog</item>
|
||||
<item name="buttonBarNegativeButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog</item>
|
||||
<item name="buttonBarNeutralButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog</item>
|
||||
</style>
|
||||
|
||||
<style name="AlertDialog.Vector.Dark" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
||||
<item name="colorPrimary">@color/palette_element_green</item>
|
||||
<item name="colorSecondary">@color/palette_element_green</item>
|
||||
<item name="colorSurface">@color/element_system_dark</item>
|
||||
<item name="colorOnSurface">@color/element_content_primary_dark</item>
|
||||
<item name="colorError">@color/element_alert_dark</item>
|
||||
<!--item name="alertDialogStyle">@style/MaterialAlertDialog.App</item>
|
||||
<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.App.Title.Text</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.App.Button</item>
|
||||
<item name="buttonBarNeutralButtonStyle">@style/Widget.App.Button</item-->
|
||||
<style name="ThemeOverlay.Vector.MaterialAlertDialog.Destructive">
|
||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog.Destructive</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.Vector.MaterialAlertDialog.NegativeDestructive">
|
||||
<item name="buttonBarNegativeButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog.Destructive</item>
|
||||
</style>
|
||||
|
||||
<!-- Title -->
|
||||
<style name="MaterialAlertDialog.Vector.Title.Text" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Subtitle</item>
|
||||
</style>
|
||||
|
||||
<!-- Body -->
|
||||
<style name="MaterialAlertDialog.Vector.Body.Text" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Body</item>
|
||||
<item name="lineHeight">20sp</item>
|
||||
</style>
|
||||
|
||||
<!-- Buttons -->
|
||||
<style name="Widget.Vector.Button.TextButton.Dialog" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.Button.TextButton.Dialog.Destructive">
|
||||
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayDestructive</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -6,6 +6,7 @@
|
||||
<item name="android:paddingRight">16dp</item>
|
||||
<item name="android:minWidth">94dp</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
|
||||
<item name="cornerRadius">8dp</item>
|
||||
<item name="lineHeight">24sp</item>
|
||||
</style>
|
||||
|
||||
@ -32,6 +33,7 @@
|
||||
<item name="android:paddingRight">16dp</item>
|
||||
<item name="android:minWidth">94dp</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
|
||||
<item name="cornerRadius">8dp</item>
|
||||
<item name="lineHeight">24sp</item>
|
||||
</style>
|
||||
|
||||
@ -48,6 +50,7 @@
|
||||
<item name="colorControlHighlight">?colorSecondary</item>
|
||||
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayPositive</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
|
||||
<item name="cornerRadius">8dp</item>
|
||||
<item name="lineHeight">24sp</item>
|
||||
</style>
|
||||
|
||||
@ -61,6 +64,7 @@
|
||||
<item name="strokeColor">@color/button_background_tint_selector</item>
|
||||
<item name="strokeWidth">1dp</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
|
||||
<item name="cornerRadius">8dp</item>
|
||||
<item name="lineHeight">24sp</item>
|
||||
</style>
|
||||
|
||||
|
@ -4,6 +4,11 @@
|
||||
<!-- Default style for TextInputLayout -->
|
||||
<style name="Widget.Vector.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
||||
|
||||
<style name="Widget.Vector.TextInputLayout.Password">
|
||||
<item name="endIconMode">password_toggle</item>
|
||||
<item name="endIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
<item name="android:inputType">textCapSentences|textMultiLine</item>
|
||||
|
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<attr name="vctr_jump_to_unread_style" format="reference" />
|
||||
|
||||
<style name="Widget.Vector.JumpToUnread.Base" parent="Widget.MaterialComponents.Chip.Action">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item>
|
||||
<item name="chipEndPadding">12dp</item>
|
||||
<item name="chipIconSize">24dp</item>
|
||||
<item name="chipMinHeight">44dp</item>
|
||||
<item name="chipStartPadding">12dp</item>
|
||||
<item name="closeIconVisible">true</item>
|
||||
<item name="android:elevation">6dp</item>
|
||||
<item name="closeIconSize">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.JumpToUnread.Light" parent="Widget.Vector.JumpToUnread.Base">
|
||||
<item name="chipBackgroundColor">@color/element_background_light</item>
|
||||
<item name="closeIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.JumpToUnread.Dark" parent="Widget.Vector.JumpToUnread.Base">
|
||||
<item name="chipBackgroundColor">@color/element_system_dark</item>
|
||||
<item name="closeIconTint">?vctr_content_quaternary</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -14,7 +14,7 @@
|
||||
<style name="TextAppearance.Vector.Title" parent="TextAppearance.MaterialComponents.Headline3">
|
||||
<item name="fontFamily">sans-serif</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">24sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_title</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textColor">?vctr_content_primary</item>
|
||||
</style>
|
||||
@ -27,7 +27,7 @@
|
||||
<style name="TextAppearance.Vector.Headline.Medium" parent="TextAppearance.MaterialComponents.Headline1">
|
||||
<item name="fontFamily">sans-serif-medium</item>
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="android:textSize">18sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_headline</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textColor">?vctr_content_primary</item>
|
||||
</style>
|
||||
@ -35,7 +35,7 @@
|
||||
<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.MaterialComponents.Subtitle1">
|
||||
<item name="fontFamily">sans-serif</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_subtitle</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textColor">?vctr_content_secondary</item>
|
||||
</style>
|
||||
@ -49,7 +49,7 @@
|
||||
<style name="TextAppearance.Vector.Body" parent="TextAppearance.MaterialComponents.Body1">
|
||||
<item name="fontFamily">sans-serif</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_body</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textColor">?vctr_content_primary</item>
|
||||
</style>
|
||||
@ -62,7 +62,7 @@
|
||||
<style name="TextAppearance.Vector.Caption" parent="TextAppearance.MaterialComponents.Caption">
|
||||
<item name="fontFamily">sans-serif</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_caption</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
<item name="android:textColor">?vctr_content_secondary</item>
|
||||
</style>
|
||||
@ -70,14 +70,14 @@
|
||||
<style name="TextAppearance.Vector.Micro" parent="TextAppearance.MaterialComponents.Caption">
|
||||
<item name="fontFamily">sans-serif</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">10sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_micro</item>
|
||||
<item name="android:letterSpacing">0</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearance.Vector.Button" parent="TextAppearance.MaterialComponents.Button">
|
||||
<item name="fontFamily">sans-serif-medium</item>
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:textSize">@dimen/text_size_button</item>
|
||||
<item name="android:letterSpacing">0.02</item>
|
||||
</style>
|
||||
|
||||
|
@ -76,7 +76,6 @@
|
||||
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
||||
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
||||
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</item>
|
||||
<item name="materialAlertDialogTheme">@style/AlertDialog.Vector.Dark</item>
|
||||
<item name="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
|
||||
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
||||
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
|
||||
@ -89,6 +88,7 @@
|
||||
|
||||
<!-- Default theme -->
|
||||
<item name="bottomSheetDialogTheme">@style/Theme.Vector.BottomSheetDialog.Dark</item>
|
||||
<item name="materialAlertDialogTheme">@style/ThemeOverlay.Vector.MaterialAlertDialog</item>
|
||||
|
||||
<item name="android:textColorLink">@color/element_link_dark</item>
|
||||
|
||||
@ -132,6 +132,9 @@
|
||||
<item name="vctr_social_login_button_twitter_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Dark</item>
|
||||
<item name="vctr_social_login_button_apple_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Apple.Dark</item>
|
||||
<item name="vctr_social_login_button_gitlab_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Gitlab.Dark</item>
|
||||
|
||||
<item name="vctr_jump_to_unread_style">@style/Widget.Vector.JumpToUnread.Dark</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
||||
|
@ -76,7 +76,6 @@
|
||||
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
|
||||
<item name="materialButtonStyle">@style/Widget.Vector.Button</item>
|
||||
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</item>
|
||||
<item name="materialAlertDialogTheme">@style/AlertDialog.Vector.Light</item>
|
||||
<item name="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
|
||||
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
|
||||
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
|
||||
@ -89,6 +88,7 @@
|
||||
|
||||
<!-- Default theme -->
|
||||
<item name="bottomSheetDialogTheme">@style/Theme.Vector.BottomSheetDialog.Light</item>
|
||||
<item name="materialAlertDialogTheme">@style/ThemeOverlay.Vector.MaterialAlertDialog</item>
|
||||
|
||||
<item name="android:textColorLink">@color/element_link_light</item>
|
||||
|
||||
@ -134,6 +134,9 @@
|
||||
<item name="vctr_social_login_button_twitter_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Light</item>
|
||||
<item name="vctr_social_login_button_apple_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Apple.Light</item>
|
||||
<item name="vctr_social_login_button_gitlab_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Gitlab.Light</item>
|
||||
|
||||
<item name="vctr_jump_to_unread_style">@style/Widget.Vector.JumpToUnread.Light</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
||||
|
@ -9,7 +9,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.realm:realm-gradle-plugin:10.5.0"
|
||||
classpath "io.realm:realm-gradle-plugin:10.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,14 +169,14 @@ dependencies {
|
||||
implementation 'com.otaliastudios:transcoder:0.10.3'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
testImplementation 'io.mockk:mockk:1.11.0'
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.65'
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.67'
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
// Plant Timber tree for test
|
||||
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||
|
@ -125,6 +125,12 @@ interface RoomService {
|
||||
*/
|
||||
suspend fun deleteRoomAlias(roomAlias: String)
|
||||
|
||||
/**
|
||||
* Return the current local changes membership for the given room.
|
||||
* see [getChangeMembershipsLive] for more details.
|
||||
*/
|
||||
fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState
|
||||
|
||||
/**
|
||||
* Return a live data of all local changes membership that happened since the session has been opened.
|
||||
* It allows you to track this in your client to known what is currently being processed by the SDK.
|
||||
|
@ -112,7 +112,6 @@ internal abstract class CryptoModule {
|
||||
@SessionScope
|
||||
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
|
||||
@UserMd5 userMd5: String,
|
||||
realmCryptoStoreMigration: RealmCryptoStoreMigration,
|
||||
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.directory(directory)
|
||||
@ -123,7 +122,7 @@ internal abstract class CryptoModule {
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.allowWritesOnUiThread(true)
|
||||
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
||||
.migration(realmCryptoStoreMigration)
|
||||
.migration(RealmCryptoStoreMigration)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import io.realm.RealmObjectSchema
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
||||
@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.di.SerializeNulls
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import io.realm.RealmObjectSchema
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
|
||||
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
|
||||
|
||||
internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration {
|
||||
internal object RealmCryptoStoreMigration : RealmMigration {
|
||||
|
||||
companion object {
|
||||
// 0, 1, 2: legacy Riot-Android
|
||||
// 3: migrate to RiotX schema
|
||||
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 12L
|
||||
}
|
||||
// 0, 1, 2: legacy Riot-Android
|
||||
// 3: migrate to RiotX schema
|
||||
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 12L
|
||||
|
||||
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
|
||||
if (!hasField(fieldName)) {
|
||||
@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||
private fun migrateTo7(realm: DynamicRealm) {
|
||||
Timber.d("Step 6 -> 7")
|
||||
Timber.d("Updating KeyInfoEntity table")
|
||||
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
|
||||
|
||||
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
|
||||
try {
|
||||
keyInfoEntities.forEach {
|
||||
|
@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie
|
||||
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
internal object RealmSessionStoreMigration : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 14L
|
||||
}
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 15L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
|
||||
@ -66,6 +65,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
if (oldVersion <= 11) migrateTo12(realm)
|
||||
if (oldVersion <= 12) migrateTo13(realm)
|
||||
if (oldVersion <= 13) migrateTo14(realm)
|
||||
if (oldVersion <= 14) migrateTo15(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
@ -292,7 +292,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
Timber.d("Step 13 -> 14")
|
||||
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
|
||||
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
|
||||
.addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
|
||||
.addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
|
||||
|
||||
realm.schema.get("RoomEntity")
|
||||
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
|
||||
@ -306,4 +306,16 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
roomAccountDataSchema.isEmbedded = true
|
||||
}
|
||||
|
||||
private fun migrateTo15(realm: DynamicRealm) {
|
||||
// fix issue with flattenParentIds on DM that kept growing with duplicate
|
||||
// so we reset it, will be updated next sync
|
||||
realm.where("RoomSummaryEntity")
|
||||
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
.findAll()
|
||||
.onEach {
|
||||
it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
@SessionFilesDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
val migration: RealmSessionStoreMigration,
|
||||
context: Context) {
|
||||
|
||||
// Keep legacy preferences name for compatibility reason
|
||||
@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(SessionRealmModule())
|
||||
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
||||
.migration(migration)
|
||||
.migration(RealmSessionStoreMigration)
|
||||
.build()
|
||||
|
||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
||||
|
@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
|
||||
internal class DefaultLegacySessionImporter @Inject constructor(
|
||||
private val context: Context,
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
|
||||
private val realmKeysUtils: RealmKeysUtils
|
||||
) : LegacySessionImporter {
|
||||
|
||||
@ -172,7 +171,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
|
||||
.name("crypto_store.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
||||
.migration(realmCryptoStoreMigration)
|
||||
.migration(RealmCryptoStoreMigration)
|
||||
.build()
|
||||
|
||||
Timber.d("Migration: copy DB to encrypted DB")
|
||||
|
8
matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
8
matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@ -59,9 +59,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
|
||||
return eventType == EventType.CALL_INVITE
|
||||
}
|
||||
|
||||
suspend fun processFastLane(event: Event) {
|
||||
eventsToPostProcess.add(event)
|
||||
onPostProcess()
|
||||
fun processFastLane(event: Event) {
|
||||
dispatchToCallSignalingHandlerIfNeeded(event)
|
||||
}
|
||||
|
||||
override suspend fun onPostProcess() {
|
||||
@ -73,13 +72,12 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
|
||||
|
||||
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
|
||||
val now = System.currentTimeMillis()
|
||||
// TODO might check if an invite is not closed (hangup/answered) in the same event batch?
|
||||
event.roomId ?: return Unit.also {
|
||||
Timber.w("Event with no room id ${event.eventId}")
|
||||
}
|
||||
val age = now - (event.ageLocalTs ?: now)
|
||||
if (age > 40_000) {
|
||||
// To old to ring?
|
||||
// Too old to ring?
|
||||
return
|
||||
}
|
||||
callSignalingHandler.onCallEvent(event)
|
||||
|
@ -41,6 +41,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
|
||||
private val mxCallFactory: MxCallFactory,
|
||||
@UserId private val userId: String) {
|
||||
|
||||
private val invitedCallIds = mutableSetOf<String>()
|
||||
private val callListeners = mutableSetOf<CallListener>()
|
||||
private val callListenersDispatcher = CallListenersDispatcher(callListeners)
|
||||
|
||||
@ -182,17 +183,17 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
|
||||
val content = event.getClearContent().toModel<CallInviteContent>() ?: return
|
||||
|
||||
content.callId ?: return
|
||||
if (activeCallHandler.getCallWithId(content.callId) != null) {
|
||||
if (invitedCallIds.contains(content.callId)) {
|
||||
// Call is already known, maybe due to fast lane. Ignore
|
||||
Timber.d("Ignoring already known call invite")
|
||||
return
|
||||
}
|
||||
|
||||
val incomingCall = mxCallFactory.createIncomingCall(
|
||||
roomId = event.roomId,
|
||||
opponentUserId = event.senderId,
|
||||
content = content
|
||||
) ?: return
|
||||
invitedCallIds.add(content.callId)
|
||||
activeCallHandler.addCall(incomingCall)
|
||||
callListenersDispatcher.onCallInviteReceived(incomingCall, content)
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ internal class DefaultEventService @Inject constructor(
|
||||
|
||||
override suspend fun getEvent(roomId: String, eventId: String): Event {
|
||||
val event = getEventTask.execute(GetEventTask.Params(roomId, eventId))
|
||||
|
||||
event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
|
||||
// Fast lane to the call event processors: try to make the incoming call ring faster
|
||||
if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
|
||||
callEventProcessor.processFastLane(event)
|
||||
|
3
matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
3
matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
@ -60,7 +60,6 @@ internal abstract class IdentityModule {
|
||||
@SessionScope
|
||||
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||
@SessionFilesDirectory directory: File,
|
||||
migration: RealmIdentityStoreMigration,
|
||||
@UserMd5 userMd5: String): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.directory(directory)
|
||||
@ -69,7 +68,7 @@ internal abstract class IdentityModule {
|
||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
|
||||
.migration(migration)
|
||||
.migration(RealmIdentityStoreMigration)
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(IdentityRealmModule())
|
||||
.build()
|
||||
|
@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
|
||||
internal object RealmIdentityStoreMigration : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val IDENTITY_STORE_SCHEMA_VERSION = 1L
|
||||
}
|
||||
const val IDENTITY_STORE_SCHEMA_VERSION = 1L
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
|
||||
|
4
matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
4
matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor(
|
||||
deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
|
||||
}
|
||||
|
||||
override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState {
|
||||
return roomChangeMembershipStateDataSource.getState(roomIdOrAlias)
|
||||
}
|
||||
|
||||
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
|
||||
return roomChangeMembershipStateDataSource.getLiveStates()
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
@ -30,7 +31,7 @@ import javax.inject.Inject
|
||||
internal class RoomChangeMembershipStateDataSource @Inject constructor() {
|
||||
|
||||
private val mutableLiveStates = MutableLiveData<Map<String, ChangeMembershipState>>(emptyMap())
|
||||
private val states = HashMap<String, ChangeMembershipState>()
|
||||
private val states = ConcurrentHashMap<String, ChangeMembershipState>()
|
||||
|
||||
/**
|
||||
* This will update local states to be synced with the server.
|
||||
|
@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor(
|
||||
) : JoinRoomTask {
|
||||
|
||||
override suspend fun execute(params: JoinRoomTask.Params) {
|
||||
val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias)
|
||||
if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) {
|
||||
return
|
||||
}
|
||||
roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
|
||||
val joinRoomResponse = try {
|
||||
executeRequest(globalErrorReceiver) {
|
||||
|
@ -199,7 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
measureTimeMillis {
|
||||
val lookupMap = realm.where(RoomSummaryEntity::class.java)
|
||||
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||
// we order by roomID to be consistent when breaking parent/child cycles
|
||||
.sort(RoomSummaryEntityFields.ROOM_ID)
|
||||
.findAll().map {
|
||||
|
@ -46,7 +46,7 @@ internal object WorkerParamsFactory {
|
||||
|
||||
inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data)
|
||||
|
||||
fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull("Unable to parse work parameters") {
|
||||
fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull<T?>("Unable to parse work parameters") {
|
||||
val json = data.getString(KEY)
|
||||
return if (json == null) {
|
||||
null
|
||||
|
@ -43,7 +43,7 @@ android {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.4"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.5"
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.2'
|
||||
|
||||
// Log
|
||||
|
1
newsfragment/3207.bugfix
Normal file
1
newsfragment/3207.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Space Explore Rooms no feedback on failed to join
|
1
newsfragment/3520.misc
Normal file
1
newsfragment/3520.misc
Normal file
@ -0,0 +1 @@
|
||||
VoIP: Merge virtual room timeline in corresponding native room (call events only).
|
1
newsfragment/3531.feature
Normal file
1
newsfragment/3531.feature
Normal file
@ -0,0 +1 @@
|
||||
Introduces AutoAcceptInvites which can be enabled at compile time.
|
@ -14,7 +14,7 @@ kapt {
|
||||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 1
|
||||
ext.versionMinor = 1
|
||||
ext.versionPatch = 11
|
||||
ext.versionPatch = 12
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
@ -246,6 +246,11 @@ android {
|
||||
|
||||
productFlavors {
|
||||
gplay {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
afterEvaluate {
|
||||
tasks.matching { it.name.contains("GoogleServices") && !it.name.contains("Gplay") }*.enabled = false
|
||||
}
|
||||
|
||||
dimension "store"
|
||||
isDefault = true
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
|
||||
@ -300,7 +305,7 @@ android {
|
||||
dependencies {
|
||||
|
||||
def epoxy_version = '4.6.2'
|
||||
def fragment_version = '1.3.4'
|
||||
def fragment_version = '1.3.5'
|
||||
def arrow_version = "0.8.2"
|
||||
def markwon_version = '4.1.2'
|
||||
def big_image_viewer_version = '1.8.0'
|
||||
@ -315,7 +320,7 @@ dependencies {
|
||||
def jjwt_version = '0.11.2'
|
||||
|
||||
// Tests
|
||||
def kluent_version = '1.65'
|
||||
def kluent_version = '1.67'
|
||||
def androidxTest_version = '1.3.0'
|
||||
def espresso_version = '3.3.0'
|
||||
|
||||
@ -355,7 +360,7 @@ dependencies {
|
||||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
|
||||
|
||||
// rx
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
|
||||
@ -506,7 +511,3 @@ dependencies {
|
||||
exclude group: 'org.jetbrains.kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Gplay")) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
|
@ -114,6 +114,12 @@
|
||||
android:text="Vector" />
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_open_button_styles_dark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="See button dark" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_test_text_view_dark"
|
||||
android:layout_width="wrap_content"
|
||||
@ -122,12 +128,6 @@
|
||||
android:layout_weight="1"
|
||||
android:text="Text Views Dark" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_open_button_styles_dark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="See button dark" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_show_sas_emoji"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -49,13 +49,12 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr
|
||||
// TODO Keep this class for now, will maybe be used fro Space
|
||||
@Singleton
|
||||
class AppStateHandler @Inject constructor(
|
||||
sessionDataSource: ActiveSessionDataSource,
|
||||
private val sessionDataSource: ActiveSessionDataSource,
|
||||
private val uiStateRepository: UiStateRepository,
|
||||
private val activeSessionHolder: ActiveSessionHolder
|
||||
) : LifecycleObserver {
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
|
||||
|
||||
val selectedRoomGroupingObservable = selectedSpaceDataSource.observe()
|
||||
@ -92,11 +91,11 @@ class AppStateHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
private fun observeActiveSession() {
|
||||
sessionDataSource.observe()
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
// sessionDataSource could already return a session while acitveSession holder still returns null
|
||||
// sessionDataSource could already return a session while activeSession holder still returns null
|
||||
it.orNull()?.let { session ->
|
||||
if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) {
|
||||
setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session)
|
||||
@ -119,6 +118,7 @@ class AppStateHandler @Inject constructor(
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
observeActiveSession()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
@ -126,7 +126,7 @@ class AppStateHandler @Inject constructor(
|
||||
compositeDisposable.clear()
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) {
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
uiStateRepository.storeGroupingMethod(true, session.sessionId)
|
||||
uiStateRepository.storeSelectedSpace(currentMethod.spaceSummary?.roomId, session.sessionId)
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import im.vector.app.core.rx.RxConfig
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.configuration.VectorConfiguration
|
||||
import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog
|
||||
import im.vector.app.features.invite.InvitesAcceptor
|
||||
import im.vector.app.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import im.vector.app.features.notifications.NotificationUtils
|
||||
@ -95,6 +96,7 @@ class VectorApplication :
|
||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||
@Inject lateinit var pinLocker: PinLocker
|
||||
@Inject lateinit var callManager: WebRtcCallManager
|
||||
@Inject lateinit var invitesAcceptor: InvitesAcceptor
|
||||
|
||||
lateinit var vectorComponent: VectorComponent
|
||||
|
||||
@ -116,6 +118,7 @@ class VectorApplication :
|
||||
appContext = this
|
||||
vectorComponent = DaggerVectorComponent.factory().create(this)
|
||||
vectorComponent.inject(this)
|
||||
invitesAcceptor.initialize()
|
||||
vectorUncaughtExceptionHandler.activate(this)
|
||||
rxConfig.setupRxPlugin()
|
||||
|
||||
|
@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
||||
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
|
||||
import im.vector.app.features.home.room.list.RoomListModule
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.InviteUsersToRoomActivity
|
||||
import im.vector.app.features.invite.VectorInviteView
|
||||
import im.vector.app.features.link.LinkHandlerActivity
|
||||
@ -122,6 +123,7 @@ interface ScreenComponent {
|
||||
fun errorFormatter(): ErrorFormatter
|
||||
fun uiStateRepository(): UiStateRepository
|
||||
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
||||
fun autoAcceptInvites(): AutoAcceptInvites
|
||||
|
||||
/* ==========================================================================================
|
||||
* Activities
|
||||
|
@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorPr
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||
import im.vector.app.features.html.EventHtmlRenderer
|
||||
import im.vector.app.features.html.VectorHtmlCompressor
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.login.ReAuthHelper
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.app.features.notifications.NotifiableEventResolver
|
||||
@ -160,6 +161,8 @@ interface VectorComponent {
|
||||
|
||||
fun pinLocker(): PinLocker
|
||||
|
||||
fun autoAcceptInvites(): AutoAcceptInvites
|
||||
|
||||
fun webRtcCallManager(): WebRtcCallManager
|
||||
|
||||
fun roomSummaryHolder(): RoomSummariesHolder
|
||||
|
@ -25,6 +25,8 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.app.core.error.DefaultErrorFormatter
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
|
||||
import im.vector.app.features.navigation.DefaultNavigator
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.app.features.pin.PinCodeStore
|
||||
@ -105,4 +107,7 @@ abstract class VectorModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindPinCodeStore(store: SharedPrefPinCodeStore): PinCodeStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites
|
||||
}
|
||||
|
@ -20,14 +20,11 @@ import android.app.Activity
|
||||
import android.text.Editable
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
import im.vector.app.databinding.DialogExportE2eKeysBinding
|
||||
|
||||
class ExportKeysDialog {
|
||||
|
||||
private var passwordVisible = false
|
||||
|
||||
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
|
||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)
|
||||
val views = DialogExportE2eKeysBinding.bind(dialogLayout)
|
||||
@ -57,13 +54,6 @@ class ExportKeysDialog {
|
||||
views.exportDialogEt.addTextChangedListener(textWatcher)
|
||||
views.exportDialogEtConfirm.addTextChangedListener(textWatcher)
|
||||
|
||||
views.exportDialogShowPassword.setOnClickListener {
|
||||
passwordVisible = !passwordVisible
|
||||
views.exportDialogEt.showPassword(passwordVisible)
|
||||
views.exportDialogEtConfirm.showPassword(passwordVisible)
|
||||
views.exportDialogShowPassword.render(passwordVisible)
|
||||
}
|
||||
|
||||
val exportDialog = builder.show()
|
||||
|
||||
views.exportDialogSubmit.setOnClickListener {
|
||||
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.dialogs
|
||||
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
fun AlertDialog.withColoredButton(whichButton: Int, @AttrRes color: Int = R.attr.colorError): AlertDialog {
|
||||
getButton(whichButton)?.setTextColor(ThemeUtils.getColor(context, color))
|
||||
return this
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.text.Editable
|
||||
import android.view.KeyEvent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.SimpleTextWatcher
|
||||
import im.vector.app.databinding.DialogPromptPasswordBinding
|
||||
|
||||
class PromptPasswordDialog {
|
||||
|
||||
private var passwordVisible = false
|
||||
|
||||
fun show(activity: Activity, listener: (String) -> Unit) {
|
||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null)
|
||||
val views = DialogPromptPasswordBinding.bind(dialogLayout)
|
||||
val textWatcher = object : SimpleTextWatcher() {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
views.promptPasswordTil.error = null
|
||||
}
|
||||
}
|
||||
views.promptPassword.addTextChangedListener(textWatcher)
|
||||
|
||||
views.promptPasswordPasswordReveal.setOnClickListener {
|
||||
passwordVisible = !passwordVisible
|
||||
views.promptPassword.showPassword(passwordVisible)
|
||||
views.promptPasswordPasswordReveal.render(passwordVisible)
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.devices_delete_dialog_title)
|
||||
.setView(dialogLayout)
|
||||
.setPositiveButton(R.string.auth_submit, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
dialog.cancel()
|
||||
return@OnKeyListener true
|
||||
}
|
||||
false
|
||||
})
|
||||
.setOnDismissListener {
|
||||
dialogLayout.hideKeyboard()
|
||||
}
|
||||
.create()
|
||||
.apply {
|
||||
setOnShowListener {
|
||||
getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
.setOnClickListener {
|
||||
if (views.promptPassword.text.toString().isEmpty()) {
|
||||
views.promptPasswordTil.error = activity.getString(R.string.error_empty_field_your_password)
|
||||
} else {
|
||||
listener.invoke(views.promptPassword.text.toString())
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
@ -40,13 +40,8 @@ fun SearchView.withoutLeftMargin() {
|
||||
}
|
||||
}
|
||||
|
||||
fun EditText.showPassword(visible: Boolean, updateCursor: Boolean = true) {
|
||||
if (visible) {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
} else {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
if (updateCursor) setSelection(text?.length ?: 0)
|
||||
fun EditText.hidePassword() {
|
||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
}
|
||||
|
||||
fun View.getMeasurements(): Pair<Int, Int> {
|
||||
|
@ -14,11 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package im.vector.app.core.platform
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
@ -29,11 +26,12 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.MainThread
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.BaseMvRxFragment
|
||||
import com.bumptech.glide.util.Util.assertMainThread
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
import im.vector.app.R
|
||||
@ -44,14 +42,14 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScreenInjector {
|
||||
abstract class VectorBaseFragment<VB : ViewBinding> : BaseMvRxFragment(), HasScreenInjector {
|
||||
|
||||
protected val vectorBaseActivity: VectorBaseActivity<*> by lazy {
|
||||
activity as VectorBaseActivity<*>
|
||||
@ -67,7 +65,7 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||
protected lateinit var errorFormatter: ErrorFormatter
|
||||
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog
|
||||
|
||||
private var progress: ProgressDialog? = null
|
||||
private var progress: AlertDialog? = null
|
||||
|
||||
/* ==========================================================================================
|
||||
* View model
|
||||
@ -203,14 +201,10 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||
vectorBaseActivity.getCoordinatorLayout()?.showOptimizedSnackbar(errorFormatter.toHumanReadable(throwable))
|
||||
}
|
||||
|
||||
protected fun showLoadingDialog(message: CharSequence? = null, cancelable: Boolean = false) {
|
||||
protected fun showLoadingDialog(message: CharSequence? = null) {
|
||||
progress?.dismiss()
|
||||
progress = ProgressDialog(requireContext()).apply {
|
||||
setCancelable(cancelable)
|
||||
setMessage(message ?: getString(R.string.please_wait))
|
||||
setProgressStyle(ProgressDialog.STYLE_SPINNER)
|
||||
show()
|
||||
}
|
||||
progress = MaterialProgressDialog(requireContext())
|
||||
.show(message ?: getString(R.string.please_wait))
|
||||
}
|
||||
|
||||
protected fun dismissLoadingDialog() {
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import im.vector.app.R
|
||||
import im.vector.app.databinding.ViewJumpToReadMarkerBinding
|
||||
|
||||
class JumpToReadMarkerView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : RelativeLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
interface Callback {
|
||||
fun onJumpToReadMarkerClicked()
|
||||
fun onClearReadMarkerClicked()
|
||||
}
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
init {
|
||||
setupView()
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
inflate(context, R.layout.view_jump_to_read_marker, this)
|
||||
val views = ViewJumpToReadMarkerBinding.bind(this)
|
||||
setBackgroundColor(ContextCompat.getColor(context, R.color.notification_accent_color))
|
||||
views.jumpToReadMarkerLabelView.setOnClickListener {
|
||||
callback?.onJumpToReadMarkerClicked()
|
||||
}
|
||||
views.closeJumpToReadMarkerView.setOnClickListener {
|
||||
visibility = View.INVISIBLE
|
||||
callback?.onClearReadMarkerClicked()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.core.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import im.vector.app.R
|
||||
|
||||
class RevealPasswordImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
|
||||
init {
|
||||
render(false)
|
||||
}
|
||||
|
||||
fun render(isPasswordShown: Boolean) {
|
||||
if (isPasswordShown) {
|
||||
contentDescription = context.getString(R.string.a11y_hide_password)
|
||||
setImageResource(R.drawable.ic_eye_closed)
|
||||
} else {
|
||||
contentDescription = context.getString(R.string.a11y_show_password)
|
||||
setImageResource(R.drawable.ic_eye)
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentReauthConfirmBinding
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
@ -41,13 +40,6 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
|
||||
views.reAuthConfirmButton.debouncedClicks {
|
||||
onButtonClicked()
|
||||
}
|
||||
views.passwordReveal.debouncedClicks {
|
||||
viewModel.handle(ReAuthActions.StartSSOFallback)
|
||||
}
|
||||
|
||||
views.passwordReveal.debouncedClicks {
|
||||
viewModel.handle(ReAuthActions.TogglePassVisibility)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onButtonClicked() = withState(viewModel) { state ->
|
||||
@ -74,11 +66,11 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
when (it.flowType) {
|
||||
LoginFlowTypes.SSO -> {
|
||||
views.passwordContainer.isVisible = false
|
||||
views.passwordFieldTil.isVisible = false
|
||||
views.reAuthConfirmButton.text = getString(R.string.auth_login_sso)
|
||||
}
|
||||
LoginFlowTypes.PASSWORD -> {
|
||||
views.passwordContainer.isVisible = true
|
||||
views.passwordFieldTil.isVisible = true
|
||||
views.reAuthConfirmButton.text = getString(R.string._continue)
|
||||
}
|
||||
else -> {
|
||||
@ -86,9 +78,6 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
views.passwordField.showPassword(it.passwordVisible)
|
||||
views.passwordReveal.render(it.passwordVisible)
|
||||
|
||||
if (it.lastErrorCode != null) {
|
||||
when (it.flowType) {
|
||||
LoginFlowTypes.SSO -> {
|
||||
|
@ -22,6 +22,5 @@ sealed class ReAuthActions : VectorViewModelAction {
|
||||
object StartSSOFallback : ReAuthActions()
|
||||
object FallBackPageLoaded : ReAuthActions()
|
||||
object FallBackPageClosed : ReAuthActions()
|
||||
object TogglePassVisibility : ReAuthActions()
|
||||
data class ReAuthWithPass(val password: String) : ReAuthActions()
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ data class ReAuthState(
|
||||
val session: String? = null,
|
||||
val flowType: String? = null,
|
||||
val ssoFallbackPageWasShown: Boolean = false,
|
||||
val passwordVisible: Boolean = false,
|
||||
val lastErrorCode: String? = null,
|
||||
val resultKeyStoreAlias: String = ""
|
||||
) : MvRxState {
|
||||
|
@ -65,13 +65,6 @@ class ReAuthViewModel @AssistedInject constructor(
|
||||
ReAuthActions.FallBackPageClosed -> {
|
||||
// Should we do something here?
|
||||
}
|
||||
ReAuthActions.TogglePassVisibility -> {
|
||||
setState {
|
||||
copy(
|
||||
passwordVisible = !state.passwordVisible
|
||||
)
|
||||
}
|
||||
}
|
||||
is ReAuthActions.ReAuthWithPass -> {
|
||||
val safeForIntentCypher = ByteArrayOutputStream().also {
|
||||
it.use {
|
||||
|
@ -27,11 +27,21 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
class CallUserMapper(private val session: Session, private val protocolsChecker: CallProtocolsChecker) {
|
||||
|
||||
fun nativeRoomForVirtualRoom(roomId: String): String? {
|
||||
if (!protocolsChecker.supportVirtualRooms) return null
|
||||
val virtualRoom = session.getRoom(roomId) ?: return null
|
||||
val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)
|
||||
return virtualRoomEvent?.content?.toModel<RoomVirtualContent>()?.nativeRoomId
|
||||
}
|
||||
|
||||
fun virtualRoomForNativeRoom(roomId: String): String? {
|
||||
if (!protocolsChecker.supportVirtualRooms) return null
|
||||
val virtualRoomEvents = session.accountDataService().getRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM))
|
||||
return virtualRoomEvents.firstOrNull {
|
||||
val virtualRoomContent = it.content.toModel<RoomVirtualContent>()
|
||||
virtualRoomContent?.nativeRoomId == roomId
|
||||
}?.roomId
|
||||
}
|
||||
|
||||
suspend fun getOrCreateVirtualRoomForRoom(roomId: String, opponentUserId: String): String? {
|
||||
protocolsChecker.awaitCheckProtocols()
|
||||
if (!protocolsChecker.supportVirtualRooms) return null
|
||||
@ -57,10 +67,6 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
|
||||
// will make sure we know where how to map calls and also allow us know not to display
|
||||
// it in the future.
|
||||
invitedRoom.markVirtual(nativeRoomId)
|
||||
// also auto-join the virtual room if we have a matching native room
|
||||
// (possibly we should only join if we've also joined the native room, then we'd also have
|
||||
// to make sure we joined virtual rooms on joining a native one)
|
||||
session.joinRoom(invitedRoomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,9 +83,9 @@ import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Provider
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
private const val STREAM_ID = "ARDAMS"
|
||||
private const val AUDIO_TRACK_ID = "ARDAMSa0"
|
||||
private const val VIDEO_TRACK_ID = "ARDAMSv0"
|
||||
private const val STREAM_ID = "userMedia"
|
||||
private const val AUDIO_TRACK_ID = "${STREAM_ID}a0"
|
||||
private const val VIDEO_TRACK_ID = "${STREAM_ID}v0"
|
||||
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
|
||||
|
||||
class WebRtcCall(
|
||||
@ -274,12 +274,77 @@ class WebRtcCall(
|
||||
peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this))
|
||||
}
|
||||
|
||||
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
|
||||
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
|
||||
localSurfaceRenderers.addIfNeeded(localViewRenderer)
|
||||
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
|
||||
|
||||
/**
|
||||
* Without consultation
|
||||
*/
|
||||
fun transferToUser(targetUserId: String, targetRoomId: String?) {
|
||||
sessionScope?.launch(dispatcher) {
|
||||
mxCall.transfer(
|
||||
targetUserId = targetUserId,
|
||||
targetRoomId = targetRoomId,
|
||||
createCallId = CallIdGenerator.generate(),
|
||||
awaitCallId = null
|
||||
)
|
||||
endCall(sendEndSignaling = false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With consultation
|
||||
*/
|
||||
fun transferToCall(transferTargetCall: WebRtcCall) {
|
||||
sessionScope?.launch(dispatcher) {
|
||||
val newCallId = CallIdGenerator.generate()
|
||||
transferTargetCall.mxCall.transfer(
|
||||
targetUserId = mxCall.opponentUserId,
|
||||
targetRoomId = null,
|
||||
createCallId = null,
|
||||
awaitCallId = newCallId
|
||||
)
|
||||
mxCall.transfer(
|
||||
targetUserId = transferTargetCall.mxCall.opponentUserId,
|
||||
targetRoomId = null,
|
||||
createCallId = newCallId,
|
||||
awaitCallId = null
|
||||
)
|
||||
endCall(sendEndSignaling = false)
|
||||
transferTargetCall.endCall(sendEndSignaling = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun acceptIncomingCall() {
|
||||
sessionScope?.launch {
|
||||
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
|
||||
if (mxCall.state == CallState.LocalRinging) {
|
||||
internalAcceptIncomingCall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a DTMF digit to the other party
|
||||
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
|
||||
*/
|
||||
fun sendDtmfDigit(digit: String) {
|
||||
sessionScope?.launch {
|
||||
for (sender in peerConnection?.senders.orEmpty()) {
|
||||
if (sender.track()?.kind() == "audio" && sender.dtmf()?.canInsertDtmf() == true) {
|
||||
try {
|
||||
sender.dtmf()?.insertDtmf(digit, 100, 70)
|
||||
return@launch
|
||||
} catch (failure: Throwable) {
|
||||
Timber.v("Fail to send Dtmf digit")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
|
||||
sessionScope?.launch(dispatcher) {
|
||||
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
|
||||
localSurfaceRenderers.addIfNeeded(localViewRenderer)
|
||||
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
|
||||
when (mode) {
|
||||
VectorCallActivity.INCOMING_ACCEPT -> {
|
||||
internalAcceptIncomingCall()
|
||||
@ -299,67 +364,31 @@ class WebRtcCall(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Without consultation
|
||||
*/
|
||||
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) {
|
||||
mxCall.transfer(
|
||||
targetUserId = targetUserId,
|
||||
targetRoomId = targetRoomId,
|
||||
createCallId = CallIdGenerator.generate(),
|
||||
awaitCallId = null
|
||||
)
|
||||
endCall(sendEndSignaling = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* With consultation
|
||||
*/
|
||||
suspend fun transferToCall(transferTargetCall: WebRtcCall) {
|
||||
val newCallId = CallIdGenerator.generate()
|
||||
transferTargetCall.mxCall.transfer(
|
||||
targetUserId = mxCall.opponentUserId,
|
||||
targetRoomId = null,
|
||||
createCallId = null,
|
||||
awaitCallId = newCallId
|
||||
)
|
||||
mxCall.transfer(
|
||||
targetUserId = transferTargetCall.mxCall.opponentUserId,
|
||||
targetRoomId = null,
|
||||
createCallId = newCallId,
|
||||
awaitCallId = null
|
||||
)
|
||||
endCall(sendEndSignaling = false)
|
||||
transferTargetCall.endCall(sendEndSignaling = false)
|
||||
}
|
||||
|
||||
fun acceptIncomingCall() {
|
||||
sessionScope?.launch {
|
||||
Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
|
||||
if (mxCall.state == CallState.LocalRinging) {
|
||||
internalAcceptIncomingCall()
|
||||
private suspend fun attachViewRenderersInternal() = withContext(dispatcher) {
|
||||
// render local video in pip view
|
||||
localSurfaceRenderers.forEach { renderer ->
|
||||
renderer.get()?.let { pipSurface ->
|
||||
pipSurface.setMirror(cameraInUse?.type == CameraType.FRONT)
|
||||
// no need to check if already added, addSink is checking that
|
||||
localVideoTrack?.addSink(pipSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a DTMF digit to the other party
|
||||
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
|
||||
*/
|
||||
fun sendDtmfDigit(digit: String) {
|
||||
for (sender in peerConnection?.senders.orEmpty()) {
|
||||
if (sender.track()?.kind() == "audio" && sender.dtmf()?.canInsertDtmf() == true) {
|
||||
try {
|
||||
sender.dtmf()?.insertDtmf(digit, 100, 70)
|
||||
return
|
||||
} catch (failure: Throwable) {
|
||||
Timber.v("Fail to send Dtmf digit")
|
||||
}
|
||||
// If remote track exists, then sink it to surface
|
||||
remoteSurfaceRenderers.forEach { renderer ->
|
||||
renderer.get()?.let { participantSurface ->
|
||||
remoteVideoTrack?.addSink(participantSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
|
||||
sessionScope?.launch(dispatcher) {
|
||||
detachRenderersInternal(renderers)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun detachRenderersInternal(renderers: List<SurfaceViewRenderer>?) = withContext(dispatcher) {
|
||||
Timber.v("## VOIP detachRenderers")
|
||||
if (renderers.isNullOrEmpty()) {
|
||||
// remove all sinks
|
||||
@ -452,24 +481,6 @@ class WebRtcCall(
|
||||
})
|
||||
}
|
||||
|
||||
private fun attachViewRenderersInternal() {
|
||||
// render local video in pip view
|
||||
localSurfaceRenderers.forEach { renderer ->
|
||||
renderer.get()?.let { pipSurface ->
|
||||
pipSurface.setMirror(this.cameraInUse?.type == CameraType.FRONT)
|
||||
// no need to check if already added, addSink is checking that
|
||||
localVideoTrack?.addSink(pipSurface)
|
||||
}
|
||||
}
|
||||
|
||||
// If remote track exists, then sink it to surface
|
||||
remoteSurfaceRenderers.forEach { renderer ->
|
||||
renderer.get()?.let { participantSurface ->
|
||||
remoteVideoTrack?.addSink(participantSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getTurnServer(): TurnServerResponse? {
|
||||
return tryOrNull {
|
||||
sessionProvider.get()?.callSignalingService()?.getTurnServer()
|
||||
@ -580,9 +591,11 @@ class WebRtcCall(
|
||||
}
|
||||
|
||||
fun setCaptureFormat(format: CaptureFormat) {
|
||||
Timber.v("## VOIP setCaptureFormat $format")
|
||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||
currentCaptureFormat = format
|
||||
sessionScope?.launch(dispatcher) {
|
||||
Timber.v("## VOIP setCaptureFormat $format")
|
||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||
currentCaptureFormat = format
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMuteStatus() {
|
||||
@ -645,13 +658,17 @@ class WebRtcCall(
|
||||
}
|
||||
|
||||
fun muteCall(muted: Boolean) {
|
||||
micMuted = muted
|
||||
updateMuteStatus()
|
||||
sessionScope?.launch(dispatcher) {
|
||||
micMuted = muted
|
||||
updateMuteStatus()
|
||||
}
|
||||
}
|
||||
|
||||
fun enableVideo(enabled: Boolean) {
|
||||
videoMuted = !enabled
|
||||
updateMuteStatus()
|
||||
sessionScope?.launch(dispatcher) {
|
||||
videoMuted = !enabled
|
||||
updateMuteStatus()
|
||||
}
|
||||
}
|
||||
|
||||
fun canSwitchCamera(): Boolean {
|
||||
@ -668,28 +685,30 @@ class WebRtcCall(
|
||||
}
|
||||
|
||||
fun switchCamera() {
|
||||
Timber.v("## VOIP switchCamera")
|
||||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
||||
val oppositeCamera = getOppositeCameraIfAny() ?: return
|
||||
videoCapturer?.switchCamera(
|
||||
object : CameraVideoCapturer.CameraSwitchHandler {
|
||||
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
||||
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
||||
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
||||
cameraInUse = oppositeCamera
|
||||
localSurfaceRenderers.forEach {
|
||||
it.get()?.setMirror(isFrontCamera)
|
||||
sessionScope?.launch(dispatcher) {
|
||||
Timber.v("## VOIP switchCamera")
|
||||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
|
||||
val oppositeCamera = getOppositeCameraIfAny() ?: return@launch
|
||||
videoCapturer?.switchCamera(
|
||||
object : CameraVideoCapturer.CameraSwitchHandler {
|
||||
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
|
||||
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
||||
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
||||
cameraInUse = oppositeCamera
|
||||
localSurfaceRenderers.forEach {
|
||||
it.get()?.setMirror(isFrontCamera)
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChanged() }
|
||||
}
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { it.onCameraChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCameraSwitchError(errorDescription: String?) {
|
||||
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
|
||||
}
|
||||
}, oppositeCamera.name
|
||||
)
|
||||
override fun onCameraSwitchError(errorDescription: String?) {
|
||||
Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
|
||||
}
|
||||
}, oppositeCamera.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -718,11 +737,12 @@ class WebRtcCall(
|
||||
return currentCaptureFormat
|
||||
}
|
||||
|
||||
private fun release() {
|
||||
private suspend fun release() {
|
||||
listeners.clear()
|
||||
mxCall.removeListener(this)
|
||||
timer.stop()
|
||||
timer.tickListener = null
|
||||
detachRenderersInternal(null)
|
||||
videoCapturer?.stopCapture()
|
||||
videoCapturer?.dispose()
|
||||
videoCapturer = null
|
||||
@ -736,6 +756,8 @@ class WebRtcCall(
|
||||
localAudioTrack = null
|
||||
localVideoSource = null
|
||||
localVideoTrack = null
|
||||
remoteAudioTrack = null
|
||||
remoteVideoTrack = null
|
||||
cameraAvailabilityCallback = null
|
||||
}
|
||||
|
||||
@ -745,7 +767,7 @@ class WebRtcCall(
|
||||
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
|
||||
Timber.e("## VOIP StreamObserver weird looking stream: $stream")
|
||||
// TODO maybe do something more??
|
||||
mxCall.hangUp()
|
||||
endCall(true)
|
||||
return@launch
|
||||
}
|
||||
if (stream.audioTracks.size == 1) {
|
||||
@ -774,27 +796,27 @@ class WebRtcCall(
|
||||
}
|
||||
|
||||
fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
|
||||
if (mxCall.state == CallState.Terminated) {
|
||||
return
|
||||
}
|
||||
// Close tracks ASAP
|
||||
localVideoTrack?.setEnabled(false)
|
||||
localVideoTrack?.setEnabled(false)
|
||||
cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
||||
val cameraManager = context.getSystemService<CameraManager>()!!
|
||||
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
||||
}
|
||||
val wasRinging = mxCall.state is CallState.LocalRinging
|
||||
mxCall.state = CallState.Terminated
|
||||
sessionScope?.launch(dispatcher) {
|
||||
if (mxCall.state == CallState.Terminated) {
|
||||
return@launch
|
||||
}
|
||||
// Close tracks ASAP
|
||||
localVideoTrack?.setEnabled(false)
|
||||
localVideoTrack?.setEnabled(false)
|
||||
cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
|
||||
val cameraManager = context.getSystemService<CameraManager>()!!
|
||||
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
|
||||
}
|
||||
val wasRinging = mxCall.state is CallState.LocalRinging
|
||||
mxCall.state = CallState.Terminated
|
||||
release()
|
||||
onCallEnded(callId)
|
||||
}
|
||||
if (sendEndSignaling) {
|
||||
if (wasRinging) {
|
||||
mxCall.reject()
|
||||
} else {
|
||||
mxCall.hangUp(reason)
|
||||
if (sendEndSignaling) {
|
||||
if (wasRinging) {
|
||||
mxCall.reject()
|
||||
} else {
|
||||
mxCall.hangUp(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.text.set
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentKeysBackupRestoreFromPassphraseBinding
|
||||
|
||||
@ -40,10 +39,6 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
|
||||
private lateinit var viewModel: KeysBackupRestoreFromPassphraseViewModel
|
||||
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
|
||||
|
||||
private fun toggleVisibilityMode() {
|
||||
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
@ -56,12 +51,6 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
|
||||
|
||||
views.helperTextWithLink.text = spannableStringForHelperText()
|
||||
|
||||
viewModel.showPasswordMode.observe(viewLifecycleOwner) {
|
||||
val shouldBeVisible = it ?: false
|
||||
views.keysBackupPassphraseEnterEdittext.showPassword(shouldBeVisible)
|
||||
views.keysBackupViewShowPassword.render(shouldBeVisible)
|
||||
}
|
||||
|
||||
views.keysBackupPassphraseEnterEdittext.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
onRestoreBackup()
|
||||
@ -70,7 +59,6 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
|
||||
views.keysBackupViewShowPassword.setOnClickListener { toggleVisibilityMode() }
|
||||
views.helperTextWithLink.setOnClickListener { onUseRecoveryKey() }
|
||||
views.keysBackupRestoreWithPassphraseSubmit.setOnClickListener { onRestoreBackup() }
|
||||
views.keysBackupPassphraseEnterEdittext.doOnTextChanged { text, _, _, _ -> onPassphraseTextEditChange(text) }
|
||||
|
@ -30,12 +30,10 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor(
|
||||
|
||||
var passphrase: MutableLiveData<String> = MutableLiveData()
|
||||
var passphraseErrorText: MutableLiveData<String> = MutableLiveData()
|
||||
var showPasswordMode: MutableLiveData<Boolean> = MutableLiveData()
|
||||
|
||||
init {
|
||||
passphrase.value = null
|
||||
passphraseErrorText.value = null
|
||||
showPasswordMode.value = false
|
||||
}
|
||||
|
||||
// ========= Actions =========
|
||||
|
@ -64,7 +64,6 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
|
||||
var confirmPassphraseError: MutableLiveData<String> = MutableLiveData()
|
||||
|
||||
var passwordStrength: MutableLiveData<Strength> = MutableLiveData()
|
||||
var showPasswordMode: MutableLiveData<Boolean> = MutableLiveData()
|
||||
|
||||
// Step 3
|
||||
// Var to ignore events from previous request(s) to generate a recovery key
|
||||
@ -80,7 +79,6 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
|
||||
var loadingStatus: MutableLiveData<WaitingViewData> = MutableLiveData()
|
||||
|
||||
init {
|
||||
showPasswordMode.value = false
|
||||
recoveryKey.value = null
|
||||
isCreatingBackupVersion.value = false
|
||||
prepareRecoverFailError.value = null
|
||||
@ -97,9 +95,6 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
|
||||
currentRequestId.value = System.currentTimeMillis()
|
||||
isCreatingBackupVersion.value = true
|
||||
|
||||
// Ensure passphrase is hidden during the process
|
||||
showPasswordMode.value = false
|
||||
|
||||
recoveryKey.value = null
|
||||
prepareRecoverFailError.value = null
|
||||
session.let { mxSession ->
|
||||
|
20
vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
20
vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupStep2Fragment.kt
@ -25,7 +25,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.transition.TransitionManager
|
||||
import com.nulabinc.zxcvbn.Zxcvbn
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
@ -113,13 +113,6 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
|
||||
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.setText(viewModel.confirmPassphrase.value)
|
||||
|
||||
viewModel.showPasswordMode.observe(viewLifecycleOwner) {
|
||||
val shouldBeVisible = it ?: false
|
||||
views.keysBackupSetupStep2PassphraseEnterEdittext.showPassword(shouldBeVisible)
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.showPassword(shouldBeVisible)
|
||||
views.keysBackupSetupStep2ShowPassword.render(shouldBeVisible)
|
||||
}
|
||||
|
||||
viewModel.confirmPassphraseError.observe(viewLifecycleOwner) {
|
||||
TransitionManager.beginDelayedTransition(views.keysBackupRoot)
|
||||
views.keysBackupSetupStep2PassphraseConfirmTil.error = it
|
||||
@ -135,7 +128,6 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
views.keysBackupSetupStep2ShowPassword.setOnClickListener { toggleVisibilityMode() }
|
||||
views.keysBackupSetupStep2Button.setOnClickListener { doNext() }
|
||||
views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() }
|
||||
|
||||
@ -143,10 +135,6 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }
|
||||
}
|
||||
|
||||
private fun toggleVisibilityMode() {
|
||||
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
|
||||
}
|
||||
|
||||
private fun doNext() {
|
||||
when {
|
||||
viewModel.passphrase.value.isNullOrEmpty() -> {
|
||||
@ -161,6 +149,9 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
|
||||
else -> {
|
||||
viewModel.megolmBackupCreationInfo = null
|
||||
|
||||
// Ensure passphrase is hidden during the process
|
||||
views.keysBackupSetupStep2PassphraseEnterEdittext.hidePassword()
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.hidePassword()
|
||||
viewModel.prepareRecoveryKey(requireActivity(), viewModel.passphrase.value)
|
||||
}
|
||||
}
|
||||
@ -172,6 +163,9 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
|
||||
// Generate a recovery key for the user
|
||||
viewModel.megolmBackupCreationInfo = null
|
||||
|
||||
// Ensure passphrase is hidden during the process
|
||||
views.keysBackupSetupStep2PassphraseEnterEdittext.hidePassword()
|
||||
views.keysBackupSetupStep2PassphraseConfirmEditText.hidePassword()
|
||||
viewModel.prepareRecoveryKey(requireActivity(), null)
|
||||
}
|
||||
else -> {
|
||||
|
@ -21,8 +21,6 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.core.platform.WaitingViewData
|
||||
|
||||
sealed class SharedSecureStorageAction : VectorViewModelAction {
|
||||
|
||||
object TogglePasswordVisibility : SharedSecureStorageAction()
|
||||
object UseKey : SharedSecureStorageAction()
|
||||
object Back : SharedSecureStorageAction()
|
||||
object Cancel : SharedSecureStorageAction()
|
||||
|
@ -50,7 +50,6 @@ import java.io.ByteArrayOutputStream
|
||||
data class SharedSecureStorageViewState(
|
||||
val ready: Boolean = false,
|
||||
val hasPassphrase: Boolean = true,
|
||||
val passphraseVisible: Boolean = false,
|
||||
val checkingSSSSAction: Async<Unit> = Uninitialized,
|
||||
val step: Step = Step.EnterPassphrase,
|
||||
val activeDeviceCount: Int = 0,
|
||||
@ -128,7 +127,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
||||
|
||||
override fun handle(action: SharedSecureStorageAction) = withState {
|
||||
when (action) {
|
||||
is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility()
|
||||
is SharedSecureStorageAction.Cancel -> handleCancel()
|
||||
is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action)
|
||||
SharedSecureStorageAction.UseKey -> handleUseKey()
|
||||
@ -319,14 +317,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
|
||||
}
|
||||
|
||||
private fun handleTogglePasswordVisibility() {
|
||||
setState {
|
||||
copy(
|
||||
passphraseVisible = !passphraseVisible
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SharedSecureStorageViewModel, SharedSecureStorageViewState> {
|
||||
|
||||
@JvmStatic
|
||||
|
9
vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
9
vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt
@ -23,11 +23,9 @@ import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.text.toSpannable
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
|
||||
@ -92,7 +90,6 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
|
||||
|
||||
views.ssssPassphraseSubmit.debouncedClicks { submit() }
|
||||
views.ssssPassphraseUseKey.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) }
|
||||
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) }
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
@ -101,10 +98,4 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
|
||||
views.ssssPassphraseSubmit.isEnabled = false
|
||||
sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text))
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||
val shouldBeVisible = state.passphraseVisible
|
||||
views.ssssPassphraseEnterEdittext.showPassword(shouldBeVisible)
|
||||
views.ssssViewShowPassword.render(shouldBeVisible)
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ sealed class BootstrapActions : VectorViewModelAction {
|
||||
|
||||
data class DoInitialize(val passphrase: String) : BootstrapActions()
|
||||
object DoInitializeGeneratedKey : BootstrapActions()
|
||||
object TogglePasswordVisibility : BootstrapActions()
|
||||
data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions()
|
||||
data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions()
|
||||
// data class ReAuth(val pass: String) : BootstrapActions()
|
||||
|
@ -28,7 +28,6 @@ import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
@ -84,7 +83,6 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
|
||||
// }
|
||||
}
|
||||
|
||||
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
|
||||
views.bootstrapSubmit.debouncedClicks { submit() }
|
||||
}
|
||||
|
||||
@ -104,12 +102,4 @@ class BootstrapConfirmPassphraseFragment @Inject constructor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||
if (state.step is BootstrapStep.ConfirmPassphrase) {
|
||||
val isPasswordVisible = state.step.isPasswordVisible
|
||||
views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
|
||||
views.ssssViewShowPassword.render(isPasswordVisible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
@ -80,7 +79,6 @@ class BootstrapEnterPassphraseFragment @Inject constructor()
|
||||
// }
|
||||
}
|
||||
|
||||
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
|
||||
views.bootstrapSubmit.debouncedClicks { submit() }
|
||||
}
|
||||
|
||||
@ -101,10 +99,6 @@ class BootstrapEnterPassphraseFragment @Inject constructor()
|
||||
|
||||
override fun invalidate() = withState(sharedViewModel) { state ->
|
||||
if (state.step is BootstrapStep.SetupPassphrase) {
|
||||
val isPasswordVisible = state.step.isPasswordVisible
|
||||
views.ssssPassphraseEnterEdittext.showPassword(isPasswordVisible, updateCursor = false)
|
||||
views.ssssViewShowPassword.render(isPasswordVisible)
|
||||
|
||||
state.passphraseStrength.invoke()?.let { strength ->
|
||||
val score = strength.score
|
||||
views.ssssPassphraseSecurityProgress.strength = score
|
||||
|
@ -34,7 +34,6 @@ import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.extensions.showPassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
@ -84,7 +83,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
||||
|
||||
// sharedViewModel.observeViewEvents {}
|
||||
views.bootstrapMigrateContinueButton.debouncedClicks { submit() }
|
||||
views.bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
|
||||
views.bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
|
||||
views.bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
|
||||
}
|
||||
@ -116,7 +114,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
||||
val isEnteringKey = getBackupSecretForMigration.useKey()
|
||||
|
||||
if (isEnteringKey) {
|
||||
views.bootstrapMigrateShowPassword.isVisible = false
|
||||
views.bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE
|
||||
|
||||
val recKey = getString(R.string.bootstrap_migration_backup_recovery_key)
|
||||
@ -128,14 +125,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
||||
views.bootstrapMigrateForgotPassphrase.isVisible = false
|
||||
views.bootstrapMigrateUseFile.isVisible = true
|
||||
} else {
|
||||
views.bootstrapMigrateShowPassword.isVisible = true
|
||||
|
||||
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
|
||||
val isPasswordVisible = state.step.isPasswordVisible
|
||||
views.bootstrapMigrateEditText.showPassword(isPasswordVisible, updateCursor = false)
|
||||
views.bootstrapMigrateShowPassword.render(isPasswordVisible)
|
||||
}
|
||||
|
||||
views.bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password)
|
||||
|
||||
views.bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase)
|
||||
|
@ -139,7 +139,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
private fun handleStartMigratingKeyBackup() {
|
||||
if (isBackupCreatedFromPassphrase) {
|
||||
setState {
|
||||
copy(step = BootstrapStep.GetBackupSecretPassForMigration(isPasswordVisible = false, useKey = false))
|
||||
copy(step = BootstrapStep.GetBackupSecretPassForMigration(useKey = false))
|
||||
}
|
||||
} else {
|
||||
setState {
|
||||
@ -151,29 +151,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
override fun handle(action: BootstrapActions) = withState { state ->
|
||||
when (action) {
|
||||
is BootstrapActions.GoBack -> queryBack()
|
||||
BootstrapActions.TogglePasswordVisibility -> {
|
||||
when (state.step) {
|
||||
is BootstrapStep.SetupPassphrase -> {
|
||||
setState {
|
||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
||||
}
|
||||
}
|
||||
is BootstrapStep.ConfirmPassphrase -> {
|
||||
setState {
|
||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
||||
}
|
||||
}
|
||||
is BootstrapStep.AccountReAuth -> {
|
||||
// nop
|
||||
}
|
||||
is BootstrapStep.GetBackupSecretPassForMigration -> {
|
||||
setState {
|
||||
copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
BootstrapActions.StartKeyBackupMigration -> {
|
||||
handleStartMigratingKeyBackup()
|
||||
}
|
||||
@ -193,9 +170,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
setState {
|
||||
copy(
|
||||
passphrase = action.passphrase,
|
||||
step = BootstrapStep.ConfirmPassphrase(
|
||||
isPasswordVisible = (state.step as? BootstrapStep.SetupPassphrase)?.isPasswordVisible ?: false
|
||||
)
|
||||
step = BootstrapStep.ConfirmPassphrase
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -255,7 +230,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
BootstrapActions.HandleForgotBackupPassphrase -> {
|
||||
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
|
||||
setState {
|
||||
copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true))
|
||||
copy(step = BootstrapStep.GetBackupSecretPassForMigration(true))
|
||||
}
|
||||
} else return@withState
|
||||
}
|
||||
@ -293,7 +268,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
if (action.userWantsToEnterPassphrase) {
|
||||
setState {
|
||||
copy(
|
||||
step = BootstrapStep.SetupPassphrase(isPasswordVisible = false)
|
||||
step = BootstrapStep.SetupPassphrase
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -493,7 +468,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
setState {
|
||||
copy(
|
||||
step = BootstrapStep.GetBackupSecretPassForMigration(
|
||||
isPasswordVisible = state.step.isPasswordVisible,
|
||||
useKey = false
|
||||
)
|
||||
)
|
||||
@ -524,9 +498,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
is BootstrapStep.ConfirmPassphrase -> {
|
||||
setState {
|
||||
copy(
|
||||
step = BootstrapStep.SetupPassphrase(
|
||||
isPasswordVisible = state.step.isPasswordVisible
|
||||
)
|
||||
step = BootstrapStep.SetupPassphrase
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -91,13 +91,13 @@ sealed class BootstrapStep {
|
||||
// Use will be asked to choose between passphrase or recovery key, or to start process if a key backup exists
|
||||
data class FirstForm(val keyBackUpExist: Boolean, val reset: Boolean = false) : BootstrapStep()
|
||||
|
||||
data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
|
||||
data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
|
||||
object SetupPassphrase : BootstrapStep()
|
||||
object ConfirmPassphrase : BootstrapStep()
|
||||
|
||||
data class AccountReAuth(val failure: String? = null) : BootstrapStep()
|
||||
|
||||
abstract class GetBackupSecretForMigration : BootstrapStep()
|
||||
data class GetBackupSecretPassForMigration(val isPasswordVisible: Boolean, val useKey: Boolean) : GetBackupSecretForMigration()
|
||||
data class GetBackupSecretPassForMigration(val useKey: Boolean) : GetBackupSecretForMigration()
|
||||
object GetBackupSecretKeyForMigration : GetBackupSecretForMigration()
|
||||
|
||||
object Initializing : BootstrapStep()
|
||||
|
@ -31,6 +31,8 @@ import im.vector.app.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.showInvites
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -56,7 +58,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
private val uiStateRepository: UiStateRepository,
|
||||
private val callManager: WebRtcCallManager,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val appStateHandler: AppStateHandler)
|
||||
private val appStateHandler: AppStateHandler,
|
||||
private val autoAcceptInvites: AutoAcceptInvites)
|
||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
|
||||
CallProtocolsChecker.Listener {
|
||||
|
||||
@ -204,21 +207,25 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId
|
||||
val dmInvites = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None
|
||||
}
|
||||
).size
|
||||
var dmInvites = 0
|
||||
var roomsInvite = 0
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
dmInvites = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None
|
||||
}
|
||||
).size
|
||||
|
||||
val roomsInvite = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId)
|
||||
}
|
||||
).size
|
||||
roomsInvite = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId)
|
||||
}
|
||||
).size
|
||||
}
|
||||
|
||||
val dmRooms = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
|
@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod
|
||||
import im.vector.app.core.platform.EmptyAction
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
@ -54,7 +55,8 @@ data class CountInfo(
|
||||
class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState,
|
||||
session: Session,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
appStateHandler: AppStateHandler)
|
||||
appStateHandler: AppStateHandler,
|
||||
private val autoAcceptInvites: AutoAcceptInvites)
|
||||
: VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
@ -92,12 +94,17 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
|
||||
}
|
||||
)
|
||||
val invites = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.INVITE)
|
||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
|
||||
}
|
||||
).size
|
||||
val invites = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.INVITE)
|
||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
|
||||
}
|
||||
).size
|
||||
}
|
||||
|
||||
copy(
|
||||
homeSpaceUnread = RoomAggregateNotificationCount(
|
||||
counts.notificationCount + invites,
|
||||
@ -129,10 +136,13 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
val selectedSpace = appStateHandler.safeActiveSpaceId()
|
||||
|
||||
val inviteCount = session.getRoomSummaries(
|
||||
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
|
||||
).size
|
||||
|
||||
val inviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.getRoomSummaries(
|
||||
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
|
||||
).size
|
||||
}
|
||||
val totalCount = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
|
@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
@ -75,7 +74,6 @@ import com.vanniktech.emoji.EmojiPopup
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
||||
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
||||
import im.vector.app.core.dialogs.withColoredButton
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
@ -94,7 +92,6 @@ import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.ui.views.ActiveConferenceView
|
||||
import im.vector.app.core.ui.views.CurrentCallsView
|
||||
import im.vector.app.core.ui.views.FailedMessagesWarningView
|
||||
import im.vector.app.core.ui.views.JumpToReadMarkerView
|
||||
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
||||
import im.vector.app.core.ui.views.NotificationAreaView
|
||||
import im.vector.app.core.utils.Debouncer
|
||||
@ -239,7 +236,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
VectorBaseFragment<FragmentRoomDetailBinding>(),
|
||||
TimelineEventController.Callback,
|
||||
VectorInviteView.Callback,
|
||||
JumpToReadMarkerView.Callback,
|
||||
AttachmentTypeSelectorView.Callback,
|
||||
AttachmentsHelper.Callback,
|
||||
GalleryOrCameraDialogHelper.Listener,
|
||||
@ -356,6 +352,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
renderTombstoneEventHandling(it)
|
||||
}
|
||||
|
||||
roomDetailViewModel.selectSubscribe(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ ->
|
||||
updateJumpToReadMarkerViewVisibility()
|
||||
}
|
||||
|
||||
roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode, RoomDetailViewState::canSendMessage) { mode, canSend ->
|
||||
if (!canSend) {
|
||||
return@selectSubscribe
|
||||
@ -725,7 +725,12 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
private fun setupJumpToReadMarkerView() {
|
||||
views.jumpToReadMarkerView.callback = this
|
||||
views.jumpToReadMarkerView.setOnClickListener {
|
||||
onJumpToReadMarkerClicked()
|
||||
}
|
||||
views.jumpToReadMarkerView.setOnCloseIconClickListener {
|
||||
roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupActiveCallView() {
|
||||
@ -1054,7 +1059,13 @@ class RoomDetailFragment @Inject constructor(
|
||||
timelineEventController.timeline = roomDetailViewModel.timeline
|
||||
|
||||
views.timelineRecyclerView.trackItemsVisibilityChange()
|
||||
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||
layoutManager = object : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
|
||||
override fun onLayoutCompleted(state: RecyclerView.State?) {
|
||||
super.onLayoutCompleted(state)
|
||||
updateJumpToReadMarkerViewVisibility()
|
||||
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
|
||||
}
|
||||
}
|
||||
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController)
|
||||
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController)
|
||||
@ -1065,8 +1076,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
it.dispatchTo(stateRestorer)
|
||||
it.dispatchTo(scrollOnNewMessageCallback)
|
||||
it.dispatchTo(scrollOnHighlightedEventCallback)
|
||||
updateJumpToReadMarkerViewVisibility()
|
||||
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
|
||||
}
|
||||
timelineEventController.addModelBuildListener(modelBuildListener)
|
||||
views.timelineRecyclerView.adapter = timelineEventController.adapter
|
||||
@ -1122,7 +1131,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
is UnreadState.ReadMarkerNotLoaded -> true
|
||||
is UnreadState.HasUnread -> {
|
||||
if (it.canShowJumpToReadMarker) {
|
||||
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
||||
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
|
||||
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
|
||||
if (positionOfReadMarker == null) {
|
||||
false
|
||||
@ -1410,7 +1419,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
is RoomDetailAction.ReportContent -> {
|
||||
when {
|
||||
data.spam -> {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
||||
.setTitle(R.string.content_reported_as_spam_title)
|
||||
.setMessage(R.string.content_reported_as_spam_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@ -1418,10 +1427,9 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
data.inappropriate -> {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
||||
.setTitle(R.string.content_reported_as_inappropriate_title)
|
||||
.setMessage(R.string.content_reported_as_inappropriate_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@ -1429,10 +1437,9 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
else -> {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
||||
.setTitle(R.string.content_reported_title)
|
||||
.setMessage(R.string.content_reported_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
@ -1440,7 +1447,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1502,7 +1508,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
.subscribe { managed ->
|
||||
if (!managed) {
|
||||
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
|
||||
.setTitle(R.string.external_link_confirmation_title)
|
||||
.setMessage(
|
||||
getString(R.string.external_link_confirmation_message, title, url)
|
||||
@ -1515,7 +1521,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
} else {
|
||||
// Open in external browser, in a new Tab
|
||||
openUrlInExternalBrowser(requireContext(), url)
|
||||
@ -1614,8 +1619,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
|
||||
override fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
val roomId = roomDetailArgs.roomId
|
||||
|
||||
val roomId = roomDetailViewModel.timeline.getTimelineEventWithId(informationData.eventId)?.roomId ?: return false
|
||||
this.view?.hideKeyboard()
|
||||
|
||||
MessageActionsBottomSheet
|
||||
@ -1699,7 +1703,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onReadMarkerVisible() {
|
||||
updateJumpToReadMarkerViewVisibility()
|
||||
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
|
||||
}
|
||||
|
||||
@ -1868,7 +1871,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
private fun askConfirmationToIgnoreUser(senderId: String) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
|
||||
.setTitle(R.string.room_participants_action_ignore_title)
|
||||
.setMessage(R.string.room_participants_action_ignore_prompt_msg)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
@ -1876,7 +1879,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId))
|
||||
}
|
||||
.show()
|
||||
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1959,10 +1961,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.RejectInvite)
|
||||
}
|
||||
|
||||
// JumpToReadMarkerView.Callback
|
||||
|
||||
override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
|
||||
views.jumpToReadMarkerView.isVisible = false
|
||||
private fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
|
||||
if (it.unreadState is UnreadState.HasUnread) {
|
||||
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false))
|
||||
}
|
||||
@ -1971,10 +1970,6 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClearReadMarkerClicked() {
|
||||
roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead)
|
||||
}
|
||||
|
||||
// AttachmentTypeSelectorView.Callback
|
||||
|
||||
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
|
||||
|
@ -48,8 +48,8 @@ import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrate
|
||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||
import im.vector.app.features.home.room.typing.TypingHelper
|
||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||
@ -118,7 +118,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
private val chatEffectManager: ChatEffectManager,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val jitsiService: JitsiService,
|
||||
timelineSettingsFactory: TimelineSettingsFactory
|
||||
timelineFactory: TimelineFactory
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
|
||||
|
||||
@ -126,9 +126,8 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
private val eventId = initialState.eventId
|
||||
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>()
|
||||
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||
private val timelineSettings = timelineSettingsFactory.create()
|
||||
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
||||
val timeline = room.createTimeline(eventId, timelineSettings)
|
||||
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId)
|
||||
|
||||
// Same lifecycle than the ViewModel (survive to screen rotation)
|
||||
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
|
||||
@ -1244,6 +1243,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun handleMarkAllAsRead() {
|
||||
setState { copy(unreadState = UnreadState.HasNoUnread) }
|
||||
viewModelScope.launch {
|
||||
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) }
|
||||
}
|
||||
@ -1380,7 +1380,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
.subscribe {
|
||||
Timber.v("Unread state: $it")
|
||||
setState { copy(unreadState = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
|
@ -20,7 +20,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import im.vector.app.core.platform.DefaultListUpdateCallback
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
@ -42,19 +41,10 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView,
|
||||
|
||||
private fun scrollIfNeeded() {
|
||||
val eventId = scheduledEventId.get() ?: return
|
||||
val positionToScroll = timelineEventController.searchPositionOfEvent(eventId)
|
||||
if (positionToScroll != null) {
|
||||
val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition()
|
||||
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
|
||||
|
||||
// Do not scroll it item is already visible
|
||||
if (positionToScroll !in firstVisibleItem..lastVisibleItem) {
|
||||
Timber.v("Scroll to $positionToScroll")
|
||||
recyclerView.stopScroll()
|
||||
layoutManager.scrollToPosition(positionToScroll)
|
||||
}
|
||||
scheduledEventId.set(null)
|
||||
}
|
||||
val positionToScroll = timelineEventController.searchPositionOfEvent(eventId) ?: return
|
||||
recyclerView.stopScroll()
|
||||
layoutManager.scrollToPosition(positionToScroll)
|
||||
scheduledEventId.set(null)
|
||||
}
|
||||
|
||||
fun scheduleScrollTo(eventId: String?) {
|
||||
|
@ -39,13 +39,13 @@ import im.vector.app.features.home.room.detail.UnreadState
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
||||
@ -163,10 +163,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
synchronized(modelCache) {
|
||||
assertUpdateCallbacksAllowed()
|
||||
(position until (position + count)).forEach {
|
||||
(position until position + count).forEach {
|
||||
// Invalidate cache
|
||||
modelCache[it] = null
|
||||
}
|
||||
// Also invalidate the first previous displayable event if
|
||||
// it's sent by the same user so we are sure we have up to date information.
|
||||
val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
|
||||
val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
|
||||
timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
|
||||
}
|
||||
if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
|
||||
modelCache[prevDisplayableEventIndex] = null
|
||||
}
|
||||
requestModelBuild()
|
||||
}
|
||||
}
|
||||
@ -340,10 +349,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
val event = currentSnapshot[position]
|
||||
val nextEvent = currentSnapshot.nextOrNull(position)
|
||||
val prevEvent = currentSnapshot.prevOrNull(position)
|
||||
val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
|
||||
timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
|
||||
}
|
||||
val params = TimelineItemFactoryParams(
|
||||
event = event,
|
||||
prevEvent = prevEvent,
|
||||
nextEvent = nextEvent,
|
||||
nextDisplayableEvent = nextDisplayableEvent,
|
||||
highlightedEventId = eventIdToHighlight,
|
||||
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
|
||||
callback = callback
|
||||
|
@ -16,6 +16,7 @@
|
||||
package im.vector.app.features.home.room.detail.timeline.factory
|
||||
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.features.call.vectorCallService
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||
@ -26,6 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHold
|
||||
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
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.room.model.call.CallAnswerContent
|
||||
@ -38,6 +40,7 @@ import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class CallItemFactory @Inject constructor(
|
||||
private val session: Session,
|
||||
private val messageColorProvider: MessageColorProvider,
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
||||
@ -132,7 +135,8 @@ class CallItemFactory @Inject constructor(
|
||||
isStillActive: Boolean,
|
||||
callback: TimelineEventController.Callback?
|
||||
): CallTileTimelineItem? {
|
||||
val userOfInterest = roomSummariesHolder.get(roomId)?.toMatrixItem() ?: return null
|
||||
val correctedRoomId = session.vectorCallService.userMapper.nativeRoomForVirtualRoom(roomId) ?: roomId
|
||||
val userOfInterest = roomSummariesHolder.get(correctedRoomId)?.toMatrixItem() ?: return null
|
||||
val attributes = messageItemAttributesFactory.create(null, informationData, callback).let {
|
||||
CallTileTimelineItem.Attributes(
|
||||
callId = callId,
|
||||
|
59
vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
Normal file
59
vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.home.room.detail.timeline.factory
|
||||
|
||||
import im.vector.app.features.call.vectorCallService
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory
|
||||
import im.vector.app.features.home.room.detail.timeline.merged.MergedTimelines
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import javax.inject.Inject
|
||||
|
||||
private val secondaryTimelineAllowedTypes = listOf(
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_REJECT,
|
||||
EventType.CALL_ANSWER
|
||||
)
|
||||
|
||||
class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) {
|
||||
|
||||
fun createTimeline(coroutineScope: CoroutineScope, mainRoom: Room, eventId: String?): Timeline {
|
||||
val settings = timelineSettingsFactory.create()
|
||||
if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
|
||||
return mainRoom.createTimeline(eventId, settings)
|
||||
}
|
||||
val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(mainRoom.roomId)
|
||||
return if (virtualRoomId == null) {
|
||||
mainRoom.createTimeline(eventId, settings)
|
||||
} else {
|
||||
val virtualRoom = session.getRoom(virtualRoomId)!!
|
||||
MergedTimelines(
|
||||
coroutineScope = coroutineScope,
|
||||
mainTimeline = mainRoom.createTimeline(eventId, settings),
|
||||
secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams(
|
||||
timeline = virtualRoom.createTimeline(null, settings),
|
||||
shouldFilterTypes = true,
|
||||
allowedTypes = secondaryTimelineAllowedTypes
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ data class TimelineItemFactoryParams(
|
||||
val event: TimelineEvent,
|
||||
val prevEvent: TimelineEvent? = null,
|
||||
val nextEvent: TimelineEvent? = null,
|
||||
val nextDisplayableEvent: TimelineEvent? = null,
|
||||
val highlightedEventId: String? = null,
|
||||
val lastSentEventIdWithoutReadReceipts: String? = null,
|
||||
val callback: TimelineEventController.Callback? = null
|
||||
|
@ -50,27 +50,28 @@ import javax.inject.Inject
|
||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||
private val roomSummariesHolder: RoomSummariesHolder,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val visibilityHelper: TimelineEventVisibilityHelper,
|
||||
private val vectorPreferences: VectorPreferences) {
|
||||
|
||||
fun create(params: TimelineItemFactoryParams): MessageInformationData {
|
||||
val event = params.event
|
||||
val nextEvent = params.nextEvent
|
||||
val nextDisplayableEvent = params.nextDisplayableEvent
|
||||
val eventId = event.eventId
|
||||
|
||||
val date = event.root.localDateTime()
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val nextDate = nextDisplayableEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||
?: false
|
||||
|
||||
val showInformation =
|
||||
addDaySeparator
|
||||
|| event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl
|
||||
|| event.senderInfo.disambiguatedDisplayName != nextEvent?.senderInfo?.disambiguatedDisplayName
|
||||
|| nextEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
|
||||
|| event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl
|
||||
|| event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName
|
||||
|| nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
|
||||
|| isNextMessageReceivedMoreThanOneHourAgo
|
||||
|| isTileTypeMessage(nextEvent)
|
||||
|| nextEvent.isEdition()
|
||||
|| isTileTypeMessage(nextDisplayableEvent)
|
||||
|| nextDisplayableEvent.isEdition()
|
||||
|
||||
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
|
||||
val e2eDecoration = getE2EDecoration(event)
|
||||
|
223
vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt
Normal file
223
vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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.home.room.detail.timeline.merged
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
|
||||
/**
|
||||
* This can be use to merge timeline tiles from 2 different rooms.
|
||||
* Be aware it wont work properly with permalink.
|
||||
*/
|
||||
class MergedTimelines(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val mainTimeline: Timeline,
|
||||
private val secondaryTimelineParams: SecondaryTimelineParams) : Timeline by mainTimeline {
|
||||
|
||||
data class SecondaryTimelineParams(
|
||||
val timeline: Timeline,
|
||||
val disableReadReceipts: Boolean = true,
|
||||
val shouldFilterTypes: Boolean = false,
|
||||
val allowedTypes: List<String> = emptyList()
|
||||
)
|
||||
|
||||
private var mainIsInit = false
|
||||
private var secondaryIsInit = false
|
||||
private val secondaryTimeline = secondaryTimelineParams.timeline
|
||||
|
||||
private val listenersMapping = HashMap<Timeline.Listener, List<ListenerInterceptor>>()
|
||||
private val mainTimelineEvents = ArrayList<TimelineEvent>()
|
||||
private val secondaryTimelineEvents = ArrayList<TimelineEvent>()
|
||||
private val positionsMapping = HashMap<String, Int>()
|
||||
private val mergedEvents = ArrayList<TimelineEvent>()
|
||||
|
||||
private val processingSemaphore = Semaphore(1)
|
||||
|
||||
private class ListenerInterceptor(
|
||||
var timeline: Timeline?,
|
||||
private val wrappedListener: Timeline.Listener,
|
||||
private val shouldFilterTypes: Boolean,
|
||||
private val allowedTypes: List<String>,
|
||||
private val onTimelineUpdate: (List<TimelineEvent>) -> Unit
|
||||
) : Timeline.Listener by wrappedListener {
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val filteredEvents = if (shouldFilterTypes) {
|
||||
snapshot.filter {
|
||||
allowedTypes.contains(it.root.getClearType())
|
||||
}
|
||||
} else {
|
||||
snapshot
|
||||
}
|
||||
onTimelineUpdate(filteredEvents)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addListener(listener: Timeline.Listener): Boolean {
|
||||
val mainTimelineListener = ListenerInterceptor(
|
||||
timeline = mainTimeline,
|
||||
wrappedListener = listener,
|
||||
shouldFilterTypes = false,
|
||||
allowedTypes = emptyList()) {
|
||||
processTimelineUpdates(::mainIsInit, mainTimelineEvents, it)
|
||||
}
|
||||
val secondaryTimelineListener = ListenerInterceptor(
|
||||
timeline = secondaryTimeline,
|
||||
wrappedListener = listener,
|
||||
shouldFilterTypes = secondaryTimelineParams.shouldFilterTypes,
|
||||
allowedTypes = secondaryTimelineParams.allowedTypes) {
|
||||
processTimelineUpdates(::secondaryIsInit, secondaryTimelineEvents, it)
|
||||
}
|
||||
listenersMapping[listener] = listOf(mainTimelineListener, secondaryTimelineListener)
|
||||
return mainTimeline.addListener(mainTimelineListener) && secondaryTimeline.addListener(secondaryTimelineListener)
|
||||
}
|
||||
|
||||
override fun removeListener(listener: Timeline.Listener): Boolean {
|
||||
return listenersMapping.remove(listener)?.let {
|
||||
it.forEach { listener ->
|
||||
listener.timeline?.removeListener(listener)
|
||||
listener.timeline = null
|
||||
}
|
||||
true
|
||||
} ?: false
|
||||
}
|
||||
|
||||
override fun removeAllListeners() {
|
||||
mainTimeline.removeAllListeners()
|
||||
secondaryTimeline.removeAllListeners()
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
mainTimeline.start()
|
||||
secondaryTimeline.start()
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
mainTimeline.dispose()
|
||||
secondaryTimeline.dispose()
|
||||
}
|
||||
|
||||
override fun restartWithEventId(eventId: String?) {
|
||||
mainTimeline.restartWithEventId(eventId)
|
||||
}
|
||||
|
||||
override fun hasMoreToLoad(direction: Timeline.Direction): Boolean {
|
||||
return mainTimeline.hasMoreToLoad(direction) || secondaryTimeline.hasMoreToLoad(direction)
|
||||
}
|
||||
|
||||
override fun paginate(direction: Timeline.Direction, count: Int) {
|
||||
mainTimeline.paginate(direction, count)
|
||||
secondaryTimeline.paginate(direction, count)
|
||||
}
|
||||
|
||||
override fun pendingEventCount(): Int {
|
||||
return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount()
|
||||
}
|
||||
|
||||
override fun failedToDeliverEventCount(): Int {
|
||||
return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount()
|
||||
}
|
||||
|
||||
override fun getTimelineEventAtIndex(index: Int): TimelineEvent? {
|
||||
return mergedEvents.getOrNull(index)
|
||||
}
|
||||
|
||||
override fun getIndexOfEvent(eventId: String?): Int? {
|
||||
return positionsMapping[eventId]
|
||||
}
|
||||
|
||||
override fun getTimelineEventWithId(eventId: String?): TimelineEvent? {
|
||||
return positionsMapping[eventId]?.let {
|
||||
getTimelineEventAtIndex(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processTimelineUpdates(isInit: KMutableProperty0<Boolean>, eventsRef: MutableList<TimelineEvent>, newData: List<TimelineEvent>) {
|
||||
coroutineScope.launch(Dispatchers.Default) {
|
||||
processingSemaphore.withPermit {
|
||||
isInit.set(true)
|
||||
eventsRef.apply {
|
||||
clear()
|
||||
addAll(newData)
|
||||
}
|
||||
mergeTimeline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mergeTimeline() {
|
||||
val merged = mutableListOf<TimelineEvent>()
|
||||
val mainItr = mainTimelineEvents.toList().listIterator()
|
||||
val secondaryItr = secondaryTimelineEvents.toList().listIterator()
|
||||
var index = 0
|
||||
var correctedSenderInfo: SenderInfo? = mainTimelineEvents.firstOrNull()?.senderInfo
|
||||
if (!mainIsInit || !secondaryIsInit) {
|
||||
return
|
||||
}
|
||||
while (merged.size < mainTimelineEvents.size + secondaryTimelineEvents.size) {
|
||||
if (mainItr.hasNext()) {
|
||||
val nextMain = mainItr.next()
|
||||
correctedSenderInfo = nextMain.senderInfo
|
||||
if (secondaryItr.hasNext()) {
|
||||
val nextSecondary = secondaryItr.next()
|
||||
if (nextSecondary.root.originServerTs ?: 0 > nextMain.root.originServerTs ?: 0) {
|
||||
positionsMapping[nextSecondary.eventId] = index
|
||||
merged.add(nextSecondary.correctBeforeMerging(correctedSenderInfo))
|
||||
mainItr.previous()
|
||||
} else {
|
||||
positionsMapping[nextMain.eventId] = index
|
||||
merged.add(nextMain)
|
||||
secondaryItr.previous()
|
||||
}
|
||||
} else {
|
||||
positionsMapping[nextMain.eventId] = index
|
||||
merged.add(nextMain)
|
||||
}
|
||||
} else if (secondaryItr.hasNext()) {
|
||||
val nextSecondary = secondaryItr.next()
|
||||
positionsMapping[nextSecondary.eventId] = index
|
||||
merged.add(nextSecondary.correctBeforeMerging(correctedSenderInfo))
|
||||
}
|
||||
index++
|
||||
}
|
||||
mergedEvents.apply {
|
||||
clear()
|
||||
addAll(merged)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
listenersMapping.keys.forEach { listener ->
|
||||
tryOrNull { listener.onTimelineUpdated(merged) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TimelineEvent.correctBeforeMerging(correctedSenderInfo: SenderInfo?): TimelineEvent {
|
||||
return copy(
|
||||
senderInfo = correctedSenderInfo ?: senderInfo,
|
||||
readReceipts = if (secondaryTimelineParams.disableReadReceipts) emptyList() else readReceipts
|
||||
)
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@ import im.vector.app.R
|
||||
import im.vector.app.RoomGroupingMethod
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.showInvites
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -38,6 +40,7 @@ class GroupRoomListSectionBuilder(
|
||||
val stringProvider: StringProvider,
|
||||
val viewModelScope: CoroutineScope,
|
||||
val appStateHandler: AppStateHandler,
|
||||
private val autoAcceptInvites: AutoAcceptInvites,
|
||||
val onDisposable: (Disposable) -> Unit,
|
||||
val onUdpatable: (UpdatableLivePageResult) -> Unit
|
||||
) : RoomListSectionBuilder {
|
||||
@ -48,15 +51,15 @@ class GroupRoomListSectionBuilder(
|
||||
val actualGroupId = appStateHandler.safeActiveGroupId()
|
||||
|
||||
when (mode) {
|
||||
RoomListDisplayMode.PEOPLE -> {
|
||||
RoomListDisplayMode.PEOPLE -> {
|
||||
// 3 sections Invites / Fav / Dms
|
||||
buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId)
|
||||
}
|
||||
RoomListDisplayMode.ROOMS -> {
|
||||
RoomListDisplayMode.ROOMS -> {
|
||||
// 5 sections invites / Fav / Rooms / Low Priority / Server notice
|
||||
buildRoomsSections(sections, activeGroupAwareQueries, actualGroupId)
|
||||
}
|
||||
RoomListDisplayMode.FILTERED -> {
|
||||
RoomListDisplayMode.FILTERED -> {
|
||||
// Used when searching for rooms
|
||||
withQueryParams(
|
||||
{
|
||||
@ -73,17 +76,18 @@ class GroupRoomListSectionBuilder(
|
||||
)
|
||||
}
|
||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||
addSection(
|
||||
sections,
|
||||
activeGroupAwareQueries,
|
||||
R.string.invitations_header,
|
||||
true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
it.activeGroupId = actualGroupId
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(
|
||||
sections,
|
||||
activeGroupAwareQueries,
|
||||
R.string.invitations_header,
|
||||
true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
it.activeGroupId = actualGroupId
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
sections,
|
||||
activeGroupAwareQueries,
|
||||
@ -115,15 +119,17 @@ class GroupRoomListSectionBuilder(
|
||||
private fun buildRoomsSections(sections: MutableList<RoomsSection>,
|
||||
activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>,
|
||||
actualGroupId: String?) {
|
||||
addSection(
|
||||
sections,
|
||||
activeSpaceAwareQueries,
|
||||
R.string.invitations_header,
|
||||
true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
it.activeGroupId = actualGroupId
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(
|
||||
sections,
|
||||
activeSpaceAwareQueries,
|
||||
R.string.invitations_header,
|
||||
true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
it.activeGroupId = actualGroupId
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
@ -180,14 +186,16 @@ class GroupRoomListSectionBuilder(
|
||||
activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>,
|
||||
actualGroupId: String?
|
||||
) {
|
||||
addSection(sections,
|
||||
activeSpaceAwareQueries,
|
||||
R.string.invitations_header,
|
||||
true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
it.activeGroupId = actualGroupId
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(sections,
|
||||
activeSpaceAwareQueries,
|
||||
R.string.invitations_header,
|
||||
true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
it.activeGroupId = actualGroupId
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
@ -34,7 +33,6 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.dialogs.withColoredButton
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
@ -386,7 +384,7 @@ class RoomListFragment @Inject constructor(
|
||||
append(getString(R.string.room_participants_leave_private_warning))
|
||||
}
|
||||
}
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
MaterialAlertDialogBuilder(requireContext(), if (isPublicRoom) 0 else R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
|
||||
.setTitle(R.string.room_participants_leave_prompt_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.leave) { _, _ ->
|
||||
@ -394,11 +392,6 @@ class RoomListFragment @Inject constructor(
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
.apply {
|
||||
if (!isPublicRoom) {
|
||||
withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||
|
@ -30,6 +30,7 @@ import im.vector.app.RoomGroupingMethod
|
||||
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.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -49,7 +50,8 @@ class RoomListViewModel @Inject constructor(
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider,
|
||||
private val appStateHandler: AppStateHandler,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val autoAcceptInvites: AutoAcceptInvites
|
||||
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
||||
|
||||
interface Factory {
|
||||
@ -126,6 +128,7 @@ class RoomListViewModel @Inject constructor(
|
||||
appStateHandler,
|
||||
viewModelScope,
|
||||
suggestedRoomJoiningState,
|
||||
autoAcceptInvites,
|
||||
{
|
||||
it.disposeOnClear()
|
||||
},
|
||||
@ -140,6 +143,7 @@ class RoomListViewModel @Inject constructor(
|
||||
stringProvider,
|
||||
viewModelScope,
|
||||
appStateHandler,
|
||||
autoAcceptInvites,
|
||||
{
|
||||
it.disposeOnClear()
|
||||
},
|
||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.home.room.list
|
||||
|
||||
import im.vector.app.AppStateHandler
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
@ -26,16 +27,18 @@ import javax.inject.Provider
|
||||
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
||||
private val appStateHandler: AppStateHandler,
|
||||
private val stringProvider: StringProvider,
|
||||
private val vectorPreferences: VectorPreferences)
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val autoAcceptInvites: AutoAcceptInvites)
|
||||
: RoomListViewModel.Factory {
|
||||
|
||||
override fun create(initialState: RoomListViewState): RoomListViewModel {
|
||||
return RoomListViewModel(
|
||||
initialState,
|
||||
session.get(),
|
||||
stringProvider,
|
||||
appStateHandler,
|
||||
vectorPreferences
|
||||
initialState = initialState,
|
||||
session = session.get(),
|
||||
stringProvider = stringProvider,
|
||||
appStateHandler = appStateHandler,
|
||||
vectorPreferences = vectorPreferences,
|
||||
autoAcceptInvites = autoAcceptInvites
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,11 +17,13 @@
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||
@ -37,7 +39,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val stringProvider: StringProvider,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val avatarRenderer: AvatarRenderer) {
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val errorFormatter: ErrorFormatter) {
|
||||
|
||||
fun create(roomSummary: RoomSummary,
|
||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||
@ -55,12 +58,21 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
|
||||
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
|
||||
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||
val error = (suggestedRoomJoiningStates[spaceChildInfo.childRoomId] as? Fail)?.error
|
||||
return SpaceChildInfoItem_()
|
||||
.id("sug_${spaceChildInfo.childRoomId}")
|
||||
.matrixItem(spaceChildInfo.toMatrixItem())
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.topic(spaceChildInfo.topic)
|
||||
.buttonLabel(stringProvider.getString(R.string.join))
|
||||
.errorLabel(
|
||||
error?.let {
|
||||
stringProvider.getString(R.string.error_failed_to_join_room, errorFormatter.toHumanReadable(it))
|
||||
}
|
||||
)
|
||||
.buttonLabel(
|
||||
if (error != null) stringProvider.getString(R.string.global_retry)
|
||||
else stringProvider.getString(R.string.join)
|
||||
)
|
||||
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
|
||||
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
|
||||
.buttonClickListener { listener?.onJoinSuggestedRoom(spaceChildInfo) }
|
||||
|
@ -33,6 +33,7 @@ import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import me.gujun.android.span.image
|
||||
@ -52,6 +53,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
||||
@EpoxyAttribute var loading: Boolean = false
|
||||
|
||||
@EpoxyAttribute var buttonLabel: String? = null
|
||||
@EpoxyAttribute var errorLabel: String? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null
|
||||
@ -97,6 +99,8 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
||||
holder.joinButton.isVisible = true
|
||||
}
|
||||
|
||||
holder.errorTextView.setTextOrHide(errorLabel)
|
||||
|
||||
holder.joinButton.onClick {
|
||||
// local echo
|
||||
holder.joinButton.isEnabled = false
|
||||
@ -120,5 +124,6 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
||||
val descriptionText by bind<TextView>(R.id.suggestedRoomDescription)
|
||||
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
|
||||
val errorTextView by bind<TextView>(R.id.inlineErrorText)
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import im.vector.app.AppStateHandler
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.showInvites
|
||||
import im.vector.app.space
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.Disposable
|
||||
@ -50,6 +52,7 @@ class SpaceRoomListSectionBuilder(
|
||||
val appStateHandler: AppStateHandler,
|
||||
val viewModelScope: CoroutineScope,
|
||||
private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>,
|
||||
private val autoAcceptInvites: AutoAcceptInvites,
|
||||
val onDisposable: (Disposable) -> Unit,
|
||||
val onUdpatable: (UpdatableLivePageResult) -> Unit,
|
||||
val onlyOrphansInHome: Boolean = false
|
||||
@ -66,13 +69,13 @@ class SpaceRoomListSectionBuilder(
|
||||
val sections = mutableListOf<RoomsSection>()
|
||||
val activeSpaceAwareQueries = mutableListOf<RoomListViewModel.ActiveSpaceQueryUpdater>()
|
||||
when (mode) {
|
||||
RoomListDisplayMode.PEOPLE -> {
|
||||
RoomListDisplayMode.PEOPLE -> {
|
||||
buildDmSections(sections, activeSpaceAwareQueries)
|
||||
}
|
||||
RoomListDisplayMode.ROOMS -> {
|
||||
RoomListDisplayMode.ROOMS -> {
|
||||
buildRoomsSections(sections, activeSpaceAwareQueries)
|
||||
}
|
||||
RoomListDisplayMode.FILTERED -> {
|
||||
RoomListDisplayMode.FILTERED -> {
|
||||
withQueryParams(
|
||||
{
|
||||
it.memberships = Membership.activeMemberships()
|
||||
@ -88,20 +91,22 @@ class SpaceRoomListSectionBuilder(
|
||||
)
|
||||
}
|
||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = if (onlyOrphansInHome) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
|
||||
} else {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
|
||||
},
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = if (onlyOrphansInHome) {
|
||||
RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL
|
||||
} else {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL
|
||||
},
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
@ -136,16 +141,18 @@ class SpaceRoomListSectionBuilder(
|
||||
}
|
||||
|
||||
private fun buildRoomsSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(
|
||||
sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
}
|
||||
}
|
||||
|
||||
addSection(
|
||||
@ -253,15 +260,17 @@ class SpaceRoomListSectionBuilder(
|
||||
}
|
||||
|
||||
private fun buildDmSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
||||
addSection(sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
if (autoAcceptInvites.showInvites()) {
|
||||
addSection(sections = sections,
|
||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||
nameRes = R.string.invitations_header,
|
||||
notifyOfLocalEcho = true,
|
||||
spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL,
|
||||
countRoomAsNotif = true
|
||||
) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}
|
||||
}
|
||||
|
||||
addSection(sections,
|
||||
@ -387,7 +396,7 @@ class SpaceRoomListSectionBuilder(
|
||||
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace)
|
||||
)
|
||||
}
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
|
||||
RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> {
|
||||
if (currentSpace == null) {
|
||||
copy(
|
||||
activeSpaceFilter = ActiveSpaceFilter.None
|
||||
@ -398,7 +407,7 @@ class SpaceRoomListSectionBuilder(
|
||||
)
|
||||
}
|
||||
}
|
||||
RoomListViewModel.SpaceFilterStrategy.NONE -> this
|
||||
RoomListViewModel.SpaceFilterStrategy.NONE -> this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user