Merge tag 'v1.1.12' into sc

Change-Id: I224f427a0d65bfea7ed0b1e19a95e1f03d0d5236

Conflicts:
	library/ui-styles/src/main/res/values/text_appearances.xml
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
	vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt
	vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
	vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt
	vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
	vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt
	vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
	vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt
	vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt
	vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt
	vector/src/main/res/layout/dialog_export_e2e_keys.xml
	vector/src/main/res/layout/dialog_import_e2e_keys.xml
This commit is contained in:
SpiritCroc 2021-07-05 11:33:43 +02:00
commit e5c9c33a0f
177 changed files with 1843 additions and 1797 deletions

View File

@ -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) Changes in Element v1.1.11 (2021-06-22)
======================================= =======================================

View File

@ -63,7 +63,7 @@ Supported filename extensions are:
- ``.bugfix``: Signifying a bug fix. - ``.bugfix``: Signifying a bug fix.
- ``.doc``: Signifying a documentation improvement. - ``.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 - ``.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. See https://github.com/twisted/towncrier#news-fragments if you need more details.

View File

@ -2,7 +2,7 @@
buildscript { buildscript {
// Ref: https://kotlinlang.org/releases.html // 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" ext.kotlin_coroutines_version = "1.5.0"
repositories { repositories {
google() google()

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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 <b>Les fonctionnalités dElement incluent :</b>
2. Vous permet de communiquer avec nimporte qui sur réseau Matrix, et plus encore grâce aux intégrations dautres applications telles que Slack ou Discord - Outils de communication en ligne avancés
3. Vous protège de la publicité et de la collecte de données - Communication dentreprise sécurisée par le chiffrement de bout en bout des messages, même pour les travailleurs à distance
4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs - 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 lapplication est décentralisée et open-source. Element est complètement différente des autres applications de messagerie et de collaboration. Elle sappuie sur Matrix, un réseau ouvert de communication décentralisée. Elle permet lauto-hébergement pour que ses utilisateurs restent le plus en contrôle possible de leurs données et leurs messages.
Element vous permet dhéberger vous-même ou de choisir un hôte vous permettant dassurer 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 à dautres utilisateurs dElement 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 dautres applications de communication dentreprise au travers dintégrations dapplications 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 <b>Vos données vous appartiennent</b>
2. Héberger vous-même votre compte en installant un serveur sur votre propre machine Vous décidez où stocker vos données et messages. Aucun risque de minage de données où daccès par des tierce parties.
3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
<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 dhé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 sils utilisent une application complètement différente.
<b>MESSAGERIE ET COLLABORATION OUVERTES</b> : vous pouvez discuter avec tout le réseau Matrix, quils utilisent Element ou une autre application Matrix, même sils 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 dune 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 dautres 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 dintégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets. <b>Reprenez où vous vous êtes arrêté</b>
Restez en contact où que vous soyez grâce à lhistorique des messages synchronisé entre tous vos appareils et sur le web sur https://app.element.io
<b>PARTOUT AVEC VOUS</b> : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d distributionSha256Sum=a9e356a21595348b6f04b024ed0b08ac8aea6b2ac37e6c0ef58e51549cd7b9cb
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
gradlew vendored
View File

@ -72,7 +72,7 @@ case "`uname`" in
Darwin* ) Darwin* )
darwin=true darwin=true
;; ;;
MINGW* ) MSYS* | MINGW* )
msys=true msys=true
;; ;;
NONSTOP* ) NONSTOP* )

View File

@ -24,6 +24,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.lib.ui.styles.R import im.vector.lib.ui.styles.R
import im.vector.lib.ui.styles.databinding.ActivityDebugMaterialThemeBinding import im.vector.lib.ui.styles.databinding.ActivityDebugMaterialThemeBinding
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
// Rendering is not the same with VectorBaseActivity // Rendering is not the same with VectorBaseActivity
abstract class DebugMaterialThemeActivity : AppCompatActivity() { abstract class DebugMaterialThemeActivity : AppCompatActivity() {
@ -50,14 +51,20 @@ abstract class DebugMaterialThemeActivity : AppCompatActivity() {
} }
views.debugShowDialog.setOnClickListener { views.debugShowDialog.setOnClickListener {
MaterialAlertDialogBuilder(this) showTestDialog(0)
.setTitle("Dialog title") }
.setMessage("Dialog content")
.setIcon(R.drawable.ic_debug_icon) views.debugShowDialogDestructive.setOnClickListener {
.setPositiveButton("Positive", null) showTestDialog(R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
.setNegativeButton("Negative", null) }
.setNeutralButton("Neutral", null)
.show() 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 { 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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_debug, menu) menuInflater.inflate(R.menu.menu_debug, menu)
return true return true

View File

@ -17,6 +17,8 @@
package im.vector.lib.ui.styles.debug package im.vector.lib.ui.styles.debug
import android.os.Bundle import android.os.Bundle
import android.text.InputType
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import im.vector.lib.ui.styles.databinding.ActivityDebugTextViewBinding import im.vector.lib.ui.styles.databinding.ActivityDebugTextViewBinding
@ -27,5 +29,20 @@ abstract class DebugVectorTextViewActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val views = ActivityDebugTextViewBinding.inflate(layoutInflater) val views = ActivityDebugTextViewBinding.inflate(layoutInflater)
setContentView(views.root) 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
}
} }
} }

View File

@ -452,6 +452,27 @@
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:text="Show Dialog" /> 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 <Button
android:id="@+id/debugShowBottomSheet" android:id="@+id/debugShowBottomSheet"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp"
tools:ignore="HardcodedText"> tools:ignore="HardcodedText">
<TextView <TextView
@ -65,4 +67,34 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Default (TextAppearance.Vector.Body)\nline 2" /> 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> </LinearLayout>

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

View File

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

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

View File

@ -1,28 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="AlertDialog.Vector.Light" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <style name="ThemeOverlay.Vector.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<item name="colorPrimary">@color/palette_element_green</item> <item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.Vector.Title.Text</item>
<item name="colorSecondary">@color/palette_element_green</item> <item name="materialAlertDialogBodyTextStyle">@style/MaterialAlertDialog.Vector.Body.Text</item>
<item name="colorSurface">@color/element_system_light</item> <item name="buttonBarPositiveButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog</item>
<item name="colorOnSurface">@color/element_content_primary_light</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog</item>
<item name="colorError">@color/element_alert_light</item> <item name="buttonBarNeutralButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog</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> </style>
<style name="AlertDialog.Vector.Dark" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <style name="ThemeOverlay.Vector.MaterialAlertDialog.Destructive">
<item name="colorPrimary">@color/palette_element_green</item> <item name="buttonBarPositiveButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog.Destructive</item>
<item name="colorSecondary">@color/palette_element_green</item> </style>
<item name="colorSurface">@color/element_system_dark</item>
<item name="colorOnSurface">@color/element_content_primary_dark</item> <style name="ThemeOverlay.Vector.MaterialAlertDialog.NegativeDestructive">
<item name="colorError">@color/element_alert_dark</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.Vector.Button.TextButton.Dialog.Destructive</item>
<!--item name="alertDialogStyle">@style/MaterialAlertDialog.App</item> </style>
<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.App.Title.Text</item>
<item name="buttonBarPositiveButtonStyle">@style/Widget.App.Button</item> <!-- Title -->
<item name="buttonBarNeutralButtonStyle">@style/Widget.App.Button</item--> <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> </style>
</resources> </resources>

View File

@ -6,6 +6,7 @@
<item name="android:paddingRight">16dp</item> <item name="android:paddingRight">16dp</item>
<item name="android:minWidth">94dp</item> <item name="android:minWidth">94dp</item>
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item> <item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
<item name="cornerRadius">8dp</item>
<item name="lineHeight">24sp</item> <item name="lineHeight">24sp</item>
</style> </style>
@ -32,6 +33,7 @@
<item name="android:paddingRight">16dp</item> <item name="android:paddingRight">16dp</item>
<item name="android:minWidth">94dp</item> <item name="android:minWidth">94dp</item>
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item> <item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
<item name="cornerRadius">8dp</item>
<item name="lineHeight">24sp</item> <item name="lineHeight">24sp</item>
</style> </style>
@ -48,6 +50,7 @@
<item name="colorControlHighlight">?colorSecondary</item> <item name="colorControlHighlight">?colorSecondary</item>
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayPositive</item> <item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayPositive</item>
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item> <item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
<item name="cornerRadius">8dp</item>
<item name="lineHeight">24sp</item> <item name="lineHeight">24sp</item>
</style> </style>
@ -61,6 +64,7 @@
<item name="strokeColor">@color/button_background_tint_selector</item> <item name="strokeColor">@color/button_background_tint_selector</item>
<item name="strokeWidth">1dp</item> <item name="strokeWidth">1dp</item>
<item name="android:textAppearance">@style/TextAppearance.Vector.Button</item> <item name="android:textAppearance">@style/TextAppearance.Vector.Button</item>
<item name="cornerRadius">8dp</item>
<item name="lineHeight">24sp</item> <item name="lineHeight">24sp</item>
</style> </style>

View File

@ -4,6 +4,11 @@
<!-- Default style for TextInputLayout --> <!-- Default style for TextInputLayout -->
<style name="Widget.Vector.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox" /> <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"> <style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
<item name="android:background">@android:color/transparent</item> <item name="android:background">@android:color/transparent</item>
<item name="android:inputType">textCapSentences|textMultiLine</item> <item name="android:inputType">textCapSentences|textMultiLine</item>

View File

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

View File

@ -1,19 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="VectorAlertDialogStyleDark.SC" parent="AlertDialog.Vector.Dark">
<item name="colorPrimary">@color/accent_sc</item>
<item name="colorSecondary">@color/accent_sc</item>
<item name="colorSurface">@color/background_dark_sc</item>
<item name="colorOnSurface">@color/text_color_primary_sc</item>
<item name="colorError">@color/element_alert_dark</item>
</style>
<style name="VectorAlertDialogStyleLight.SC" parent="AlertDialog.Vector.Light">
<item name="colorPrimary">@color/accent_sc</item>
<item name="colorSecondary">@color/accent_sc</item>
</style>
<!-- custom action bar --> <!-- custom action bar -->
<style name="Vector.Styled.ActionBar.SC" parent="Widget.Vector.Toolbar"> <style name="Vector.Styled.ActionBar.SC" parent="Widget.Vector.Toolbar">
<item name="android:background">@color/background_sc</item> <item name="android:background">@color/background_sc</item>

View File

@ -14,7 +14,7 @@
<style name="TextAppearance.Vector.Title" parent="TextAppearance.MaterialComponents.Headline3"> <style name="TextAppearance.Vector.Title" parent="TextAppearance.MaterialComponents.Headline3">
<item name="fontFamily">sans-serif</item> <item name="fontFamily">sans-serif</item>
<item name="android: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:letterSpacing">0</item>
<item name="android:textColor">?vctr_content_primary</item> <item name="android:textColor">?vctr_content_primary</item>
</style> </style>
@ -27,7 +27,7 @@
<style name="TextAppearance.Vector.Headline.Medium" parent="TextAppearance.MaterialComponents.Headline1"> <style name="TextAppearance.Vector.Headline.Medium" parent="TextAppearance.MaterialComponents.Headline1">
<item name="fontFamily">sans-serif-medium</item> <item name="fontFamily">sans-serif-medium</item>
<item name="android: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:letterSpacing">0</item>
<item name="android:textColor">?vctr_content_primary</item> <item name="android:textColor">?vctr_content_primary</item>
</style> </style>
@ -35,7 +35,7 @@
<style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.MaterialComponents.Subtitle1"> <style name="TextAppearance.Vector.Subtitle" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="fontFamily">sans-serif</item> <item name="fontFamily">sans-serif</item>
<item name="android: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:letterSpacing">0</item>
<item name="android:textColor">?vctr_content_secondary</item> <item name="android:textColor">?vctr_content_secondary</item>
</style> </style>
@ -51,7 +51,7 @@
<item name="fontFamily">sans-serif</item> <item name="fontFamily">sans-serif</item>
<item name="android: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:letterSpacing">0</item>
<item name="android:textColor">?vctr_content_primary</item> <item name="android:textColor">?vctr_content_primary</item>
</style> </style>
@ -64,7 +64,7 @@
<style name="TextAppearance.Vector.Caption" parent="TextAppearance.MaterialComponents.Caption"> <style name="TextAppearance.Vector.Caption" parent="TextAppearance.MaterialComponents.Caption">
<item name="fontFamily">sans-serif</item> <item name="fontFamily">sans-serif</item>
<item name="android: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:letterSpacing">0</item>
<item name="android:textColor">?vctr_content_secondary</item> <item name="android:textColor">?vctr_content_secondary</item>
</style> </style>
@ -72,14 +72,14 @@
<style name="TextAppearance.Vector.Micro" parent="TextAppearance.MaterialComponents.Caption"> <style name="TextAppearance.Vector.Micro" parent="TextAppearance.MaterialComponents.Caption">
<item name="fontFamily">sans-serif</item> <item name="fontFamily">sans-serif</item>
<item name="android: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> <item name="android:letterSpacing">0</item>
</style> </style>
<style name="TextAppearance.Vector.Button" parent="TextAppearance.MaterialComponents.Button"> <style name="TextAppearance.Vector.Button" parent="TextAppearance.MaterialComponents.Button">
<item name="fontFamily">sans-serif-medium</item> <item name="fontFamily">sans-serif-medium</item>
<item name="android: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> <item name="android:letterSpacing">0.02</item>
</style> </style>

View File

@ -76,7 +76,6 @@
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item> <item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
<item name="materialButtonStyle">@style/Widget.Vector.Button</item> <item name="materialButtonStyle">@style/Widget.Vector.Button</item>
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</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="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item> <item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item> <item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
@ -89,6 +88,7 @@
<!-- Default theme --> <!-- Default theme -->
<item name="bottomSheetDialogTheme">@style/Theme.Vector.BottomSheetDialog.Dark</item> <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> <item name="android:textColorLink">@color/element_link_dark</item>
@ -145,6 +145,9 @@
<item name="vctr_social_login_button_twitter_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Dark</item> <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_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_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>
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" /> <style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />

View File

@ -76,7 +76,6 @@
<item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item> <item name="android:textViewStyle">@style/Widget.Vector.TextView.Body</item>
<item name="materialButtonStyle">@style/Widget.Vector.Button</item> <item name="materialButtonStyle">@style/Widget.Vector.Button</item>
<item name="toolbarStyle">@style/Widget.Vector.Toolbar</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="bottomNavigationStyle">@style/BottomNavigation.Vector</item>
<item name="searchViewStyle">@style/Widget.Vector.SearchView</item> <item name="searchViewStyle">@style/Widget.Vector.SearchView</item>
<item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item> <item name="textInputStyle">@style/Widget.Vector.TextInputLayout</item>
@ -89,6 +88,7 @@
<!-- Default theme --> <!-- Default theme -->
<item name="bottomSheetDialogTheme">@style/Theme.Vector.BottomSheetDialog.Light</item> <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> <item name="android:textColorLink">@color/element_link_light</item>
@ -147,6 +147,9 @@
<item name="vctr_social_login_button_twitter_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Light</item> <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_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_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>
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" /> <style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />

View File

@ -71,7 +71,6 @@
<!-- Default color for text View --> <!-- Default color for text View -->
<item name="android:textColorTertiary">?vctr_content_tertiary</item> <item name="android:textColorTertiary">?vctr_content_tertiary</item>
<item name="materialAlertDialogTheme">@style/VectorAlertDialogStyleDark.SC</item>
<!-- custom action bar --> <!-- custom action bar -->
<item name="android:actionBarStyle">@style/Vector.Styled.ActionBar.SC</item> <item name="android:actionBarStyle">@style/Vector.Styled.ActionBar.SC</item>

View File

@ -71,7 +71,6 @@
<!-- Default color for text View --> <!-- Default color for text View -->
<item name="android:textColorTertiary">?vctr_content_tertiary</item> <item name="android:textColorTertiary">?vctr_content_tertiary</item>
<item name="materialAlertDialogTheme">@style/VectorAlertDialogStyleLight.SC</item>
<!-- custom action bar --> <!-- custom action bar -->
<item name="android:actionBarStyle">@style/Vector.Styled.ActionBar.SC</item> <item name="android:actionBarStyle">@style/Vector.Styled.ActionBar.SC</item>

View File

@ -9,7 +9,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { 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' implementation 'com.otaliastudios:transcoder:0.10.3'
// Phone number https://github.com/google/libphonenumber // 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 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'org.robolectric:robolectric:4.5.1'
//testImplementation 'org.robolectric:shadows-support-v4:3.0' //testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.11.0' 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" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test // Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'

View File

@ -125,6 +125,12 @@ interface RoomService {
*/ */
suspend fun deleteRoomAlias(roomAlias: String) 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. * 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. * It allows you to track this in your client to known what is currently being processed by the SDK.

View File

@ -112,7 +112,6 @@ internal abstract class CryptoModule {
@SessionScope @SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File, fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String, @UserMd5 userMd5: String,
realmCryptoStoreMigration: RealmCryptoStoreMigration,
realmKeysUtils: RealmKeysUtils): RealmConfiguration { realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(directory) .directory(directory)
@ -123,7 +122,7 @@ internal abstract class CryptoModule {
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
.migration(realmCryptoStoreMigration) .migration(RealmCryptoStoreMigration)
.build() .build()
} }

View File

@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.squareup.moshi.Types 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.extensions.tryOrNull
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo 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.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields 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.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.OutgoingGossipingRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields 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.TrustLevelEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields 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.crypto.store.db.model.WithHeldSessionEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SerializeNulls 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 org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo 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 // 0, 1, 2: legacy Riot-Android
// 3: migrate to RiotX schema // 3: migrate to RiotX schema
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) // 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 const val CRYPTO_STORE_SCHEMA_VERSION = 12L
}
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema { private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
if (!hasField(fieldName)) { if (!hasField(fieldName)) {
@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
private fun migrateTo7(realm: DynamicRealm) { private fun migrateTo7(realm: DynamicRealm) {
Timber.d("Step 6 -> 7") Timber.d("Step 6 -> 7")
Timber.d("Updating KeyInfoEntity table") Timber.d("Updating KeyInfoEntity table")
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
val keyInfoEntities = realm.where("KeyInfoEntity").findAll() val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try { try {
keyInfoEntities.forEach { keyInfoEntities.forEach {

View File

@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.FieldAttribute import io.realm.FieldAttribute
import io.realm.RealmMigration 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.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.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.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
@ -40,20 +41,19 @@ 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.SpaceParentSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.query.process
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
class RealmSessionStoreMigration @Inject constructor() : RealmMigration { internal object RealmSessionStoreMigration : RealmMigration {
companion object {
// SC-specific DB changes on top of Element // SC-specific DB changes on top of Element
// 1: added markedUnread field // 1: added markedUnread field
const val SESSION_STORE_SCHEMA_SC_VERSION = 2L const val SESSION_STORE_SCHEMA_SC_VERSION = 2L
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12) const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
const val SESSION_STORE_SCHEMA_VERSION = 14L + const val SESSION_STORE_SCHEMA_VERSION = 15L +
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
}
override fun migrate(realm: DynamicRealm, combinedOldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, combinedOldVersion: Long, newVersion: Long) {
val oldVersion = combinedOldVersion % SESSION_STORE_SCHEMA_SC_VERSION_OFFSET val oldVersion = combinedOldVersion % SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
@ -75,6 +75,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 11) migrateTo12(realm) if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm) if (oldVersion <= 12) migrateTo13(realm)
if (oldVersion <= 13) migrateTo14(realm) if (oldVersion <= 13) migrateTo14(realm)
if (oldVersion <= 14) migrateTo15(realm)
if (oldScVersion <= 0) migrateToSc1(realm) if (oldScVersion <= 0) migrateToSc1(realm)
if (oldScVersion <= 1) migrateToSc2(realm) if (oldScVersion <= 1) migrateToSc2(realm)
@ -334,4 +335,16 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
roomAccountDataSchema.isEmbedded = true 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)
}
}
} }

View File

@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
@SessionFilesDirectory val directory: File, @SessionFilesDirectory val directory: File,
@SessionId val sessionId: String, @SessionId val sessionId: String,
@UserMd5 val userMd5: String, @UserMd5 val userMd5: String,
val migration: RealmSessionStoreMigration,
context: Context) { context: Context) {
// Keep legacy preferences name for compatibility reason // Keep legacy preferences name for compatibility reason
@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.modules(SessionRealmModule()) .modules(SessionRealmModule())
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
.migration(migration) .migration(RealmSessionStoreMigration)
.build() .build()
// Try creating a realm instance and if it succeeds we can clear the flag // Try creating a realm instance and if it succeeds we can clear the flag

View File

@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
internal class DefaultLegacySessionImporter @Inject constructor( internal class DefaultLegacySessionImporter @Inject constructor(
private val context: Context, private val context: Context,
private val sessionParamsStore: SessionParamsStore, private val sessionParamsStore: SessionParamsStore,
private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
private val realmKeysUtils: RealmKeysUtils private val realmKeysUtils: RealmKeysUtils
) : LegacySessionImporter { ) : LegacySessionImporter {
@ -172,7 +171,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
.name("crypto_store.realm") .name("crypto_store.realm")
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
.migration(realmCryptoStoreMigration) .migration(RealmCryptoStoreMigration)
.build() .build()
Timber.d("Migration: copy DB to encrypted DB") Timber.d("Migration: copy DB to encrypted DB")

View File

@ -59,9 +59,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
return eventType == EventType.CALL_INVITE return eventType == EventType.CALL_INVITE
} }
suspend fun processFastLane(event: Event) { fun processFastLane(event: Event) {
eventsToPostProcess.add(event) dispatchToCallSignalingHandlerIfNeeded(event)
onPostProcess()
} }
override suspend fun onPostProcess() { override suspend fun onPostProcess() {
@ -73,13 +72,12 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
val now = System.currentTimeMillis() 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 { event.roomId ?: return Unit.also {
Timber.w("Event with no room id ${event.eventId}") Timber.w("Event with no room id ${event.eventId}")
} }
val age = now - (event.ageLocalTs ?: now) val age = now - (event.ageLocalTs ?: now)
if (age > 40_000) { if (age > 40_000) {
// To old to ring? // Too old to ring?
return return
} }
callSignalingHandler.onCallEvent(event) callSignalingHandler.onCallEvent(event)

View File

@ -41,6 +41,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
private val mxCallFactory: MxCallFactory, private val mxCallFactory: MxCallFactory,
@UserId private val userId: String) { @UserId private val userId: String) {
private val invitedCallIds = mutableSetOf<String>()
private val callListeners = mutableSetOf<CallListener>() private val callListeners = mutableSetOf<CallListener>()
private val callListenersDispatcher = CallListenersDispatcher(callListeners) private val callListenersDispatcher = CallListenersDispatcher(callListeners)
@ -182,17 +183,17 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
val content = event.getClearContent().toModel<CallInviteContent>() ?: return val content = event.getClearContent().toModel<CallInviteContent>() ?: return
content.callId ?: 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 // Call is already known, maybe due to fast lane. Ignore
Timber.d("Ignoring already known call invite") Timber.d("Ignoring already known call invite")
return return
} }
val incomingCall = mxCallFactory.createIncomingCall( val incomingCall = mxCallFactory.createIncomingCall(
roomId = event.roomId, roomId = event.roomId,
opponentUserId = event.senderId, opponentUserId = event.senderId,
content = content content = content
) ?: return ) ?: return
invitedCallIds.add(content.callId)
activeCallHandler.addCall(incomingCall) activeCallHandler.addCall(incomingCall)
callListenersDispatcher.onCallInviteReceived(incomingCall, content) callListenersDispatcher.onCallInviteReceived(incomingCall, content)
} }

View File

@ -29,7 +29,7 @@ internal class DefaultEventService @Inject constructor(
override suspend fun getEvent(roomId: String, eventId: String): Event { override suspend fun getEvent(roomId: String, eventId: String): Event {
val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) 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 // Fast lane to the call event processors: try to make the incoming call ring faster
if (callEventProcessor.shouldProcessFastLane(event.getClearType())) { if (callEventProcessor.shouldProcessFastLane(event.getClearType())) {
callEventProcessor.processFastLane(event) callEventProcessor.processFastLane(event)

View File

@ -60,7 +60,6 @@ internal abstract class IdentityModule {
@SessionScope @SessionScope
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
@SessionFilesDirectory directory: File, @SessionFilesDirectory directory: File,
migration: RealmIdentityStoreMigration,
@UserMd5 userMd5: String): RealmConfiguration { @UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(directory) .directory(directory)
@ -69,7 +68,7 @@ internal abstract class IdentityModule {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
} }
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION) .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
.migration(migration) .migration(RealmIdentityStoreMigration)
.allowWritesOnUiThread(true) .allowWritesOnUiThread(true)
.modules(IdentityRealmModule()) .modules(IdentityRealmModule())
.build() .build()

View File

@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import timber.log.Timber 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) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion") Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")

View File

@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor(
deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
} }
override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState {
return roomChangeMembershipStateDataSource.getState(roomIdOrAlias)
}
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> { override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
return roomChangeMembershipStateDataSource.getLiveStates() return roomChangeMembershipStateDataSource.getLiveStates()
} }

View File

@ -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.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -30,7 +31,7 @@ import javax.inject.Inject
internal class RoomChangeMembershipStateDataSource @Inject constructor() { internal class RoomChangeMembershipStateDataSource @Inject constructor() {
private val mutableLiveStates = MutableLiveData<Map<String, ChangeMembershipState>>(emptyMap()) 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. * This will update local states to be synced with the server.

View File

@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor(
) : JoinRoomTask { ) : JoinRoomTask {
override suspend fun execute(params: JoinRoomTask.Params) { 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) roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
val joinRoomResponse = try { val joinRoomResponse = try {
executeRequest(globalErrorReceiver) { executeRequest(globalErrorReceiver) {

View File

@ -213,7 +213,6 @@ internal class RoomSummaryUpdater @Inject constructor(
measureTimeMillis { measureTimeMillis {
val lookupMap = realm.where(RoomSummaryEntity::class.java) val lookupMap = realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
// we order by roomID to be consistent when breaking parent/child cycles // we order by roomID to be consistent when breaking parent/child cycles
.sort(RoomSummaryEntityFields.ROOM_ID) .sort(RoomSummaryEntityFields.ROOM_ID)
.findAll().map { .findAll().map {

View File

@ -46,7 +46,7 @@ internal object WorkerParamsFactory {
inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data) 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) val json = data.getString(KEY)
return if (json == null) { return if (json == null) {
null null

View File

@ -43,7 +43,7 @@ android {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.3.0' 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' implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Log // Log

1
newsfragment/3207.bugfix Normal file
View File

@ -0,0 +1 @@
Space Explore Rooms no feedback on failed to join

1
newsfragment/3520.misc Normal file
View File

@ -0,0 +1 @@
VoIP: Merge virtual room timeline in corresponding native room (call events only).

View File

@ -0,0 +1 @@
Introduces AutoAcceptInvites which can be enabled at compile time.

View File

@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 1 ext.versionMinor = 1
ext.versionPatch = 11 ext.versionPatch = 12
ext.scVersion = 36 ext.scVersion = 36
@ -250,6 +250,11 @@ android {
productFlavors { productFlavors {
gplay { gplay {
apply plugin: 'com.google.gms.google-services'
afterEvaluate {
tasks.matching { it.name.contains("GoogleServices") && !it.name.contains("Gplay") }*.enabled = false
}
dimension "store" dimension "store"
isDefault = true isDefault = true
@ -305,7 +310,7 @@ android {
dependencies { dependencies {
def epoxy_version = '4.6.2' 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 arrow_version = "0.8.2"
def markwon_version = '4.1.2' def markwon_version = '4.1.2'
def big_image_viewer_version = '1.8.0' def big_image_viewer_version = '1.8.0'
@ -320,7 +325,7 @@ dependencies {
def jjwt_version = '0.11.2' def jjwt_version = '0.11.2'
// Tests // Tests
def kluent_version = '1.65' def kluent_version = '1.67'
def androidxTest_version = '1.3.0' def androidxTest_version = '1.3.0'
def espresso_version = '3.3.0' def espresso_version = '3.3.0'
@ -360,7 +365,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0' implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
// rx // rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
@ -511,7 +516,3 @@ dependencies {
exclude group: 'org.jetbrains.kotlin' exclude group: 'org.jetbrains.kotlin'
} }
} }
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Gplay")) {
apply plugin: 'com.google.gms.google-services'
}

View File

@ -114,6 +114,12 @@
android:text="Vector" /> android:text="Vector" />
</LinearLayout> </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 <Button
android:id="@+id/debug_test_text_view_dark" android:id="@+id/debug_test_text_view_dark"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -122,12 +128,6 @@
android:layout_weight="1" android:layout_weight="1"
android:text="Text Views Dark" /> 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 <Button
android:id="@+id/debug_show_sas_emoji" android:id="@+id/debug_show_sas_emoji"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -49,13 +49,12 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr
// TODO Keep this class for now, will maybe be used fro Space // TODO Keep this class for now, will maybe be used fro Space
@Singleton @Singleton
class AppStateHandler @Inject constructor( class AppStateHandler @Inject constructor(
sessionDataSource: ActiveSessionDataSource, private val sessionDataSource: ActiveSessionDataSource,
private val uiStateRepository: UiStateRepository, private val uiStateRepository: UiStateRepository,
private val activeSessionHolder: ActiveSessionHolder private val activeSessionHolder: ActiveSessionHolder
) : LifecycleObserver { ) : LifecycleObserver {
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty()) private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() val selectedRoomGroupingObservable = selectedSpaceDataSource.observe()
@ -95,11 +94,11 @@ class AppStateHandler @Inject constructor(
} }
} }
init { private fun observeActiveSession() {
sessionDataSource.observe() sessionDataSource.observe()
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .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 -> it.orNull()?.let { session ->
if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) {
setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session) setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session)
@ -122,6 +121,7 @@ class AppStateHandler @Inject constructor(
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() { fun entersForeground() {
observeActiveSession()
} }
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)

View File

@ -47,6 +47,7 @@ import im.vector.app.core.rx.RxConfig
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog 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.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
@ -95,6 +96,7 @@ class VectorApplication :
@Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var popupAlertManager: PopupAlertManager
@Inject lateinit var pinLocker: PinLocker @Inject lateinit var pinLocker: PinLocker
@Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var invitesAcceptor: InvitesAcceptor
lateinit var vectorComponent: VectorComponent lateinit var vectorComponent: VectorComponent
@ -116,6 +118,7 @@ class VectorApplication :
appContext = this appContext = this
vectorComponent = DaggerVectorComponent.factory().create(this) vectorComponent = DaggerVectorComponent.factory().create(this)
vectorComponent.inject(this) vectorComponent.inject(this)
invitesAcceptor.initialize()
vectorUncaughtExceptionHandler.activate(this) vectorUncaughtExceptionHandler.activate(this)
rxConfig.setupRxPlugin() rxConfig.setupRxPlugin()

View File

@ -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.filtered.FilteredRoomsActivity
import im.vector.app.features.home.room.list.RoomListModule import im.vector.app.features.home.room.list.RoomListModule
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet 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.InviteUsersToRoomActivity
import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.invite.VectorInviteView
import im.vector.app.features.link.LinkHandlerActivity import im.vector.app.features.link.LinkHandlerActivity
@ -122,6 +123,7 @@ interface ScreenComponent {
fun errorFormatter(): ErrorFormatter fun errorFormatter(): ErrorFormatter
fun uiStateRepository(): UiStateRepository fun uiStateRepository(): UiStateRepository
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
fun autoAcceptInvites(): AutoAcceptInvites
/* ========================================================================================== /* ==========================================================================================
* Activities * Activities

View File

@ -43,6 +43,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.PowerLevelsHolder
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.VectorHtmlCompressor 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.login.ReAuthHelper
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotifiableEventResolver import im.vector.app.features.notifications.NotifiableEventResolver
@ -161,6 +162,8 @@ interface VectorComponent {
fun pinLocker(): PinLocker fun pinLocker(): PinLocker
fun autoAcceptInvites(): AutoAcceptInvites
fun webRtcCallManager(): WebRtcCallManager fun webRtcCallManager(): WebRtcCallManager
fun roomSummaryHolder(): RoomSummariesHolder fun roomSummaryHolder(): RoomSummariesHolder

View File

@ -25,6 +25,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter 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.DefaultNavigator
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.PinCodeStore
@ -105,4 +107,7 @@ abstract class VectorModule {
@Binds @Binds
abstract fun bindPinCodeStore(store: SharedPrefPinCodeStore): PinCodeStore abstract fun bindPinCodeStore(store: SharedPrefPinCodeStore): PinCodeStore
@Binds
abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites
} }

View File

@ -20,14 +20,11 @@ import android.app.Activity
import android.text.Editable import android.text.Editable
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.databinding.DialogExportE2eKeysBinding import im.vector.app.databinding.DialogExportE2eKeysBinding
class ExportKeysDialog { class ExportKeysDialog {
private var passwordVisible = false
fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) { fun show(activity: Activity, exportKeyDialogListener: ExportKeyDialogListener) {
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null) val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_export_e2e_keys, null)
val views = DialogExportE2eKeysBinding.bind(dialogLayout) val views = DialogExportE2eKeysBinding.bind(dialogLayout)
@ -57,13 +54,6 @@ class ExportKeysDialog {
views.exportDialogEt.addTextChangedListener(textWatcher) views.exportDialogEt.addTextChangedListener(textWatcher)
views.exportDialogEtConfirm.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() val exportDialog = builder.show()
views.exportDialogSubmit.setOnClickListener { views.exportDialogSubmit.setOnClickListener {

View File

@ -1,27 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.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
}

View File

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

View File

@ -40,14 +40,9 @@ fun SearchView.withoutLeftMargin() {
} }
} }
fun EditText.showPassword(visible: Boolean, updateCursor: Boolean = true) { fun EditText.hidePassword() {
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 inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
} }
if (updateCursor) setSelection(text?.length ?: 0)
}
fun View.getMeasurements(): Pair<Int, Int> { fun View.getMeasurements(): Pair<Int, Int> {
measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)

View File

@ -14,11 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
@file:Suppress("DEPRECATION")
package im.vector.app.core.platform package im.vector.app.core.platform
import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
@ -29,11 +26,12 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.MainThread import androidx.annotation.MainThread
import com.google.android.material.appbar.MaterialToolbar import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.BaseMvRxFragment import com.airbnb.mvrx.BaseMvRxFragment
import com.bumptech.glide.util.Util.assertMainThread import com.bumptech.glide.util.Util.assertMainThread
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jakewharton.rxbinding3.view.clicks import com.jakewharton.rxbinding3.view.clicks
import im.vector.app.R import im.vector.app.R
@ -44,10 +42,10 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -67,7 +65,7 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
protected lateinit var errorFormatter: ErrorFormatter protected lateinit var errorFormatter: ErrorFormatter
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog
private var progress: ProgressDialog? = null private var progress: AlertDialog? = null
/* ========================================================================================== /* ==========================================================================================
* View model * View model
@ -203,14 +201,10 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
vectorBaseActivity.getCoordinatorLayout()?.showOptimizedSnackbar(errorFormatter.toHumanReadable(throwable)) vectorBaseActivity.getCoordinatorLayout()?.showOptimizedSnackbar(errorFormatter.toHumanReadable(throwable))
} }
protected fun showLoadingDialog(message: CharSequence? = null, cancelable: Boolean = false) { protected fun showLoadingDialog(message: CharSequence? = null) {
progress?.dismiss() progress?.dismiss()
progress = ProgressDialog(requireContext()).apply { progress = MaterialProgressDialog(requireContext())
setCancelable(cancelable) .show(message ?: getString(R.string.please_wait))
setMessage(message ?: getString(R.string.please_wait))
setProgressStyle(ProgressDialog.STYLE_SPINNER)
show()
}
} }
protected fun dismissLoadingDialog() { protected fun dismissLoadingDialog() {

View File

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

View File

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

View File

@ -24,7 +24,6 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentReauthConfirmBinding import im.vector.app.databinding.FragmentReauthConfirmBinding
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
@ -41,13 +40,6 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
views.reAuthConfirmButton.debouncedClicks { views.reAuthConfirmButton.debouncedClicks {
onButtonClicked() onButtonClicked()
} }
views.passwordReveal.debouncedClicks {
viewModel.handle(ReAuthActions.StartSSOFallback)
}
views.passwordReveal.debouncedClicks {
viewModel.handle(ReAuthActions.TogglePassVisibility)
}
} }
private fun onButtonClicked() = withState(viewModel) { state -> private fun onButtonClicked() = withState(viewModel) { state ->
@ -74,11 +66,11 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
when (it.flowType) { when (it.flowType) {
LoginFlowTypes.SSO -> { LoginFlowTypes.SSO -> {
views.passwordContainer.isVisible = false views.passwordFieldTil.isVisible = false
views.reAuthConfirmButton.text = getString(R.string.auth_login_sso) views.reAuthConfirmButton.text = getString(R.string.auth_login_sso)
} }
LoginFlowTypes.PASSWORD -> { LoginFlowTypes.PASSWORD -> {
views.passwordContainer.isVisible = true views.passwordFieldTil.isVisible = true
views.reAuthConfirmButton.text = getString(R.string._continue) views.reAuthConfirmButton.text = getString(R.string._continue)
} }
else -> { else -> {
@ -86,9 +78,6 @@ class PromptFragment : VectorBaseFragment<FragmentReauthConfirmBinding>() {
} }
} }
views.passwordField.showPassword(it.passwordVisible)
views.passwordReveal.render(it.passwordVisible)
if (it.lastErrorCode != null) { if (it.lastErrorCode != null) {
when (it.flowType) { when (it.flowType) {
LoginFlowTypes.SSO -> { LoginFlowTypes.SSO -> {

View File

@ -22,6 +22,5 @@ sealed class ReAuthActions : VectorViewModelAction {
object StartSSOFallback : ReAuthActions() object StartSSOFallback : ReAuthActions()
object FallBackPageLoaded : ReAuthActions() object FallBackPageLoaded : ReAuthActions()
object FallBackPageClosed : ReAuthActions() object FallBackPageClosed : ReAuthActions()
object TogglePassVisibility : ReAuthActions()
data class ReAuthWithPass(val password: String) : ReAuthActions() data class ReAuthWithPass(val password: String) : ReAuthActions()
} }

View File

@ -23,7 +23,6 @@ data class ReAuthState(
val session: String? = null, val session: String? = null,
val flowType: String? = null, val flowType: String? = null,
val ssoFallbackPageWasShown: Boolean = false, val ssoFallbackPageWasShown: Boolean = false,
val passwordVisible: Boolean = false,
val lastErrorCode: String? = null, val lastErrorCode: String? = null,
val resultKeyStoreAlias: String = "" val resultKeyStoreAlias: String = ""
) : MvRxState { ) : MvRxState {

View File

@ -65,13 +65,6 @@ class ReAuthViewModel @AssistedInject constructor(
ReAuthActions.FallBackPageClosed -> { ReAuthActions.FallBackPageClosed -> {
// Should we do something here? // Should we do something here?
} }
ReAuthActions.TogglePassVisibility -> {
setState {
copy(
passwordVisible = !state.passwordVisible
)
}
}
is ReAuthActions.ReAuthWithPass -> { is ReAuthActions.ReAuthWithPass -> {
val safeForIntentCypher = ByteArrayOutputStream().also { val safeForIntentCypher = ByteArrayOutputStream().also {
it.use { it.use {

View File

@ -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) { class CallUserMapper(private val session: Session, private val protocolsChecker: CallProtocolsChecker) {
fun nativeRoomForVirtualRoom(roomId: String): String? { fun nativeRoomForVirtualRoom(roomId: String): String? {
if (!protocolsChecker.supportVirtualRooms) return null
val virtualRoom = session.getRoom(roomId) ?: return null val virtualRoom = session.getRoom(roomId) ?: return null
val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)
return virtualRoomEvent?.content?.toModel<RoomVirtualContent>()?.nativeRoomId 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? { suspend fun getOrCreateVirtualRoomForRoom(roomId: String, opponentUserId: String): String? {
protocolsChecker.awaitCheckProtocols() protocolsChecker.awaitCheckProtocols()
if (!protocolsChecker.supportVirtualRooms) return null 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 // will make sure we know where how to map calls and also allow us know not to display
// it in the future. // it in the future.
invitedRoom.markVirtual(nativeRoomId) 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)
} }
} }
} }

View File

@ -83,9 +83,9 @@ import java.util.concurrent.TimeUnit
import javax.inject.Provider import javax.inject.Provider
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
private const val STREAM_ID = "ARDAMS" private const val STREAM_ID = "userMedia"
private const val AUDIO_TRACK_ID = "ARDAMSa0" private const val AUDIO_TRACK_ID = "${STREAM_ID}a0"
private const val VIDEO_TRACK_ID = "ARDAMSv0" private const val VIDEO_TRACK_ID = "${STREAM_ID}v0"
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints() private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
class WebRtcCall( class WebRtcCall(
@ -274,12 +274,77 @@ class WebRtcCall(
peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this)) peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, PeerConnectionObserver(this))
} }
/**
* 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?) { fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
sessionScope?.launch(dispatcher) {
Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer") Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
localSurfaceRenderers.addIfNeeded(localViewRenderer) localSurfaceRenderers.addIfNeeded(localViewRenderer)
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer) remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
sessionScope?.launch(dispatcher) {
when (mode) { when (mode) {
VectorCallActivity.INCOMING_ACCEPT -> { VectorCallActivity.INCOMING_ACCEPT -> {
internalAcceptIncomingCall() internalAcceptIncomingCall()
@ -299,67 +364,31 @@ class WebRtcCall(
} }
} }
/** private suspend fun attachViewRenderersInternal() = withContext(dispatcher) {
* Without consultation // render local video in pip view
*/ localSurfaceRenderers.forEach { renderer ->
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) { renderer.get()?.let { pipSurface ->
mxCall.transfer( pipSurface.setMirror(cameraInUse?.type == CameraType.FRONT)
targetUserId = targetUserId, // no need to check if already added, addSink is checking that
targetRoomId = targetRoomId, localVideoTrack?.addSink(pipSurface)
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()
}
} }
} }
/** // If remote track exists, then sink it to surface
* Sends a DTMF digit to the other party remoteSurfaceRenderers.forEach { renderer ->
* @param digit The digit (nb. string - '#' and '*' are dtmf too) renderer.get()?.let { participantSurface ->
*/ remoteVideoTrack?.addSink(participantSurface)
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")
}
} }
} }
} }
fun detachRenderers(renderers: List<SurfaceViewRenderer>?) { fun detachRenderers(renderers: List<SurfaceViewRenderer>?) {
sessionScope?.launch(dispatcher) {
detachRenderersInternal(renderers)
}
}
private suspend fun detachRenderersInternal(renderers: List<SurfaceViewRenderer>?) = withContext(dispatcher) {
Timber.v("## VOIP detachRenderers") Timber.v("## VOIP detachRenderers")
if (renderers.isNullOrEmpty()) { if (renderers.isNullOrEmpty()) {
// remove all sinks // 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? { private suspend fun getTurnServer(): TurnServerResponse? {
return tryOrNull { return tryOrNull {
sessionProvider.get()?.callSignalingService()?.getTurnServer() sessionProvider.get()?.callSignalingService()?.getTurnServer()
@ -580,10 +591,12 @@ class WebRtcCall(
} }
fun setCaptureFormat(format: CaptureFormat) { fun setCaptureFormat(format: CaptureFormat) {
sessionScope?.launch(dispatcher) {
Timber.v("## VOIP setCaptureFormat $format") Timber.v("## VOIP setCaptureFormat $format")
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps) videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
currentCaptureFormat = format currentCaptureFormat = format
} }
}
private fun updateMuteStatus() { private fun updateMuteStatus() {
val micShouldBeMuted = micMuted || remoteOnHold val micShouldBeMuted = micMuted || remoteOnHold
@ -645,14 +658,18 @@ class WebRtcCall(
} }
fun muteCall(muted: Boolean) { fun muteCall(muted: Boolean) {
sessionScope?.launch(dispatcher) {
micMuted = muted micMuted = muted
updateMuteStatus() updateMuteStatus()
} }
}
fun enableVideo(enabled: Boolean) { fun enableVideo(enabled: Boolean) {
sessionScope?.launch(dispatcher) {
videoMuted = !enabled videoMuted = !enabled
updateMuteStatus() updateMuteStatus()
} }
}
fun canSwitchCamera(): Boolean { fun canSwitchCamera(): Boolean {
return availableCamera.size > 1 return availableCamera.size > 1
@ -668,9 +685,10 @@ class WebRtcCall(
} }
fun switchCamera() { fun switchCamera() {
sessionScope?.launch(dispatcher) {
Timber.v("## VOIP switchCamera") Timber.v("## VOIP switchCamera")
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) { if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
val oppositeCamera = getOppositeCameraIfAny() ?: return val oppositeCamera = getOppositeCameraIfAny() ?: return@launch
videoCapturer?.switchCamera( videoCapturer?.switchCamera(
object : CameraVideoCapturer.CameraSwitchHandler { object : CameraVideoCapturer.CameraSwitchHandler {
// Invoked on success. |isFrontCamera| is true if the new camera is front facing. // Invoked on success. |isFrontCamera| is true if the new camera is front facing.
@ -692,6 +710,7 @@ class WebRtcCall(
) )
} }
} }
}
private suspend fun createAnswer(): SessionDescription? { private suspend fun createAnswer(): SessionDescription? {
Timber.w("## VOIP createAnswer") Timber.w("## VOIP createAnswer")
@ -718,11 +737,12 @@ class WebRtcCall(
return currentCaptureFormat return currentCaptureFormat
} }
private fun release() { private suspend fun release() {
listeners.clear() listeners.clear()
mxCall.removeListener(this) mxCall.removeListener(this)
timer.stop() timer.stop()
timer.tickListener = null timer.tickListener = null
detachRenderersInternal(null)
videoCapturer?.stopCapture() videoCapturer?.stopCapture()
videoCapturer?.dispose() videoCapturer?.dispose()
videoCapturer = null videoCapturer = null
@ -736,6 +756,8 @@ class WebRtcCall(
localAudioTrack = null localAudioTrack = null
localVideoSource = null localVideoSource = null
localVideoTrack = null localVideoTrack = null
remoteAudioTrack = null
remoteVideoTrack = null
cameraAvailabilityCallback = null cameraAvailabilityCallback = null
} }
@ -745,7 +767,7 @@ class WebRtcCall(
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) { if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
Timber.e("## VOIP StreamObserver weird looking stream: $stream") Timber.e("## VOIP StreamObserver weird looking stream: $stream")
// TODO maybe do something more?? // TODO maybe do something more??
mxCall.hangUp() endCall(true)
return@launch return@launch
} }
if (stream.audioTracks.size == 1) { if (stream.audioTracks.size == 1) {
@ -774,8 +796,9 @@ class WebRtcCall(
} }
fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) { fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
sessionScope?.launch(dispatcher) {
if (mxCall.state == CallState.Terminated) { if (mxCall.state == CallState.Terminated) {
return return@launch
} }
// Close tracks ASAP // Close tracks ASAP
localVideoTrack?.setEnabled(false) localVideoTrack?.setEnabled(false)
@ -786,10 +809,8 @@ class WebRtcCall(
} }
val wasRinging = mxCall.state is CallState.LocalRinging val wasRinging = mxCall.state is CallState.LocalRinging
mxCall.state = CallState.Terminated mxCall.state = CallState.Terminated
sessionScope?.launch(dispatcher) {
release() release()
onCallEnded(callId) onCallEnded(callId)
}
if (sendEndSignaling) { if (sendEndSignaling) {
if (wasRinging) { if (wasRinging) {
mxCall.reject() mxCall.reject()
@ -798,6 +819,7 @@ class WebRtcCall(
} }
} }
} }
}
// Call listener // Call listener

View File

@ -25,7 +25,6 @@ import android.view.inputmethod.EditorInfo
import androidx.core.text.set import androidx.core.text.set
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentKeysBackupRestoreFromPassphraseBinding import im.vector.app.databinding.FragmentKeysBackupRestoreFromPassphraseBinding
@ -40,10 +39,6 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
private lateinit var viewModel: KeysBackupRestoreFromPassphraseViewModel private lateinit var viewModel: KeysBackupRestoreFromPassphraseViewModel
private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel private lateinit var sharedViewModel: KeysBackupRestoreSharedViewModel
private fun toggleVisibilityMode() {
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -56,12 +51,6 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
views.helperTextWithLink.text = spannableStringForHelperText() views.helperTextWithLink.text = spannableStringForHelperText()
viewModel.showPasswordMode.observe(viewLifecycleOwner) {
val shouldBeVisible = it ?: false
views.keysBackupPassphraseEnterEdittext.showPassword(shouldBeVisible)
views.keysBackupViewShowPassword.render(shouldBeVisible)
}
views.keysBackupPassphraseEnterEdittext.setOnEditorActionListener { _, actionId, _ -> views.keysBackupPassphraseEnterEdittext.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
onRestoreBackup() onRestoreBackup()
@ -70,7 +59,6 @@ class KeysBackupRestoreFromPassphraseFragment @Inject constructor() : VectorBase
return@setOnEditorActionListener false return@setOnEditorActionListener false
} }
views.keysBackupViewShowPassword.setOnClickListener { toggleVisibilityMode() }
views.helperTextWithLink.setOnClickListener { onUseRecoveryKey() } views.helperTextWithLink.setOnClickListener { onUseRecoveryKey() }
views.keysBackupRestoreWithPassphraseSubmit.setOnClickListener { onRestoreBackup() } views.keysBackupRestoreWithPassphraseSubmit.setOnClickListener { onRestoreBackup() }
views.keysBackupPassphraseEnterEdittext.doOnTextChanged { text, _, _, _ -> onPassphraseTextEditChange(text) } views.keysBackupPassphraseEnterEdittext.doOnTextChanged { text, _, _, _ -> onPassphraseTextEditChange(text) }

View File

@ -30,12 +30,10 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor(
var passphrase: MutableLiveData<String> = MutableLiveData() var passphrase: MutableLiveData<String> = MutableLiveData()
var passphraseErrorText: MutableLiveData<String> = MutableLiveData() var passphraseErrorText: MutableLiveData<String> = MutableLiveData()
var showPasswordMode: MutableLiveData<Boolean> = MutableLiveData()
init { init {
passphrase.value = null passphrase.value = null
passphraseErrorText.value = null passphraseErrorText.value = null
showPasswordMode.value = false
} }
// ========= Actions ========= // ========= Actions =========

View File

@ -64,7 +64,6 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
var confirmPassphraseError: MutableLiveData<String> = MutableLiveData() var confirmPassphraseError: MutableLiveData<String> = MutableLiveData()
var passwordStrength: MutableLiveData<Strength> = MutableLiveData() var passwordStrength: MutableLiveData<Strength> = MutableLiveData()
var showPasswordMode: MutableLiveData<Boolean> = MutableLiveData()
// Step 3 // Step 3
// Var to ignore events from previous request(s) to generate a recovery key // 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() var loadingStatus: MutableLiveData<WaitingViewData> = MutableLiveData()
init { init {
showPasswordMode.value = false
recoveryKey.value = null recoveryKey.value = null
isCreatingBackupVersion.value = false isCreatingBackupVersion.value = false
prepareRecoverFailError.value = null prepareRecoverFailError.value = null
@ -97,9 +95,6 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() {
currentRequestId.value = System.currentTimeMillis() currentRequestId.value = System.currentTimeMillis()
isCreatingBackupVersion.value = true isCreatingBackupVersion.value = true
// Ensure passphrase is hidden during the process
showPasswordMode.value = false
recoveryKey.value = null recoveryKey.value = null
prepareRecoverFailError.value = null prepareRecoverFailError.value = null
session.let { mxSession -> session.let { mxSession ->

View File

@ -25,7 +25,7 @@ import androidx.lifecycle.viewModelScope
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import com.nulabinc.zxcvbn.Zxcvbn import com.nulabinc.zxcvbn.Zxcvbn
import im.vector.app.R 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.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding
import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorLocale
@ -113,13 +113,6 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
views.keysBackupSetupStep2PassphraseConfirmEditText.setText(viewModel.confirmPassphrase.value) 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) { viewModel.confirmPassphraseError.observe(viewLifecycleOwner) {
TransitionManager.beginDelayedTransition(views.keysBackupRoot) TransitionManager.beginDelayedTransition(views.keysBackupRoot)
views.keysBackupSetupStep2PassphraseConfirmTil.error = it views.keysBackupSetupStep2PassphraseConfirmTil.error = it
@ -135,7 +128,6 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
} }
private fun setupViews() { private fun setupViews() {
views.keysBackupSetupStep2ShowPassword.setOnClickListener { toggleVisibilityMode() }
views.keysBackupSetupStep2Button.setOnClickListener { doNext() } views.keysBackupSetupStep2Button.setOnClickListener { doNext() }
views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() } views.keysBackupSetupStep2SkipButton.setOnClickListener { skipPassphrase() }
@ -143,10 +135,6 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() } views.keysBackupSetupStep2PassphraseConfirmEditText.doOnTextChanged { _, _, _, _ -> onConfirmPassphraseChanged() }
} }
private fun toggleVisibilityMode() {
viewModel.showPasswordMode.value = !(viewModel.showPasswordMode.value ?: false)
}
private fun doNext() { private fun doNext() {
when { when {
viewModel.passphrase.value.isNullOrEmpty() -> { viewModel.passphrase.value.isNullOrEmpty() -> {
@ -161,6 +149,9 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
else -> { else -> {
viewModel.megolmBackupCreationInfo = null viewModel.megolmBackupCreationInfo = null
// Ensure passphrase is hidden during the process
views.keysBackupSetupStep2PassphraseEnterEdittext.hidePassword()
views.keysBackupSetupStep2PassphraseConfirmEditText.hidePassword()
viewModel.prepareRecoveryKey(requireActivity(), viewModel.passphrase.value) viewModel.prepareRecoveryKey(requireActivity(), viewModel.passphrase.value)
} }
} }
@ -172,6 +163,9 @@ class KeysBackupSetupStep2Fragment @Inject constructor() : VectorBaseFragment<Fr
// Generate a recovery key for the user // Generate a recovery key for the user
viewModel.megolmBackupCreationInfo = null viewModel.megolmBackupCreationInfo = null
// Ensure passphrase is hidden during the process
views.keysBackupSetupStep2PassphraseEnterEdittext.hidePassword()
views.keysBackupSetupStep2PassphraseConfirmEditText.hidePassword()
viewModel.prepareRecoveryKey(requireActivity(), null) viewModel.prepareRecoveryKey(requireActivity(), null)
} }
else -> { else -> {

View File

@ -21,8 +21,6 @@ import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.platform.WaitingViewData
sealed class SharedSecureStorageAction : VectorViewModelAction { sealed class SharedSecureStorageAction : VectorViewModelAction {
object TogglePasswordVisibility : SharedSecureStorageAction()
object UseKey : SharedSecureStorageAction() object UseKey : SharedSecureStorageAction()
object Back : SharedSecureStorageAction() object Back : SharedSecureStorageAction()
object Cancel : SharedSecureStorageAction() object Cancel : SharedSecureStorageAction()

View File

@ -50,7 +50,6 @@ import java.io.ByteArrayOutputStream
data class SharedSecureStorageViewState( data class SharedSecureStorageViewState(
val ready: Boolean = false, val ready: Boolean = false,
val hasPassphrase: Boolean = true, val hasPassphrase: Boolean = true,
val passphraseVisible: Boolean = false,
val checkingSSSSAction: Async<Unit> = Uninitialized, val checkingSSSSAction: Async<Unit> = Uninitialized,
val step: Step = Step.EnterPassphrase, val step: Step = Step.EnterPassphrase,
val activeDeviceCount: Int = 0, val activeDeviceCount: Int = 0,
@ -128,7 +127,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
override fun handle(action: SharedSecureStorageAction) = withState { override fun handle(action: SharedSecureStorageAction) = withState {
when (action) { when (action) {
is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility()
is SharedSecureStorageAction.Cancel -> handleCancel() is SharedSecureStorageAction.Cancel -> handleCancel()
is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action)
SharedSecureStorageAction.UseKey -> handleUseKey() SharedSecureStorageAction.UseKey -> handleUseKey()
@ -319,14 +317,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss) _viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
} }
private fun handleTogglePasswordVisibility() {
setState {
copy(
passphraseVisible = !passphraseVisible
)
}
}
companion object : MvRxViewModelFactory<SharedSecureStorageViewModel, SharedSecureStorageViewState> { companion object : MvRxViewModelFactory<SharedSecureStorageViewModel, SharedSecureStorageViewState> {
@JvmStatic @JvmStatic

View File

@ -23,11 +23,9 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.editorActionEvents import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
@ -92,7 +90,6 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
views.ssssPassphraseSubmit.debouncedClicks { submit() } views.ssssPassphraseSubmit.debouncedClicks { submit() }
views.ssssPassphraseUseKey.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) } views.ssssPassphraseUseKey.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.UseKey) }
views.ssssViewShowPassword.debouncedClicks { sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) }
} }
fun submit() { fun submit() {
@ -101,10 +98,4 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
views.ssssPassphraseSubmit.isEnabled = false views.ssssPassphraseSubmit.isEnabled = false
sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text)) sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text))
} }
override fun invalidate() = withState(sharedViewModel) { state ->
val shouldBeVisible = state.passphraseVisible
views.ssssPassphraseEnterEdittext.showPassword(shouldBeVisible)
views.ssssViewShowPassword.render(shouldBeVisible)
}
} }

View File

@ -34,7 +34,6 @@ sealed class BootstrapActions : VectorViewModelAction {
data class DoInitialize(val passphrase: String) : BootstrapActions() data class DoInitialize(val passphrase: String) : BootstrapActions()
object DoInitializeGeneratedKey : BootstrapActions() object DoInitializeGeneratedKey : BootstrapActions()
object TogglePasswordVisibility : BootstrapActions()
data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions() data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions()
data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions() data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions()
// data class ReAuth(val pass: String) : BootstrapActions() // data class ReAuth(val pass: String) : BootstrapActions()

View File

@ -28,7 +28,6 @@ import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard 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.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import io.reactivex.android.schedulers.AndroidSchedulers 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() } 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)
}
}
} }

View File

@ -26,7 +26,6 @@ import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.editorActionEvents import com.jakewharton.rxbinding3.widget.editorActionEvents
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.showPassword
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
import im.vector.app.features.settings.VectorLocale 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() } views.bootstrapSubmit.debouncedClicks { submit() }
} }
@ -101,10 +99,6 @@ class BootstrapEnterPassphraseFragment @Inject constructor()
override fun invalidate() = withState(sharedViewModel) { state -> override fun invalidate() = withState(sharedViewModel) { state ->
if (state.step is BootstrapStep.SetupPassphrase) { 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 -> state.passphraseStrength.invoke()?.let { strength ->
val score = strength.score val score = strength.score
views.ssssPassphraseSecurityProgress.strength = score views.ssssPassphraseSecurityProgress.strength = score

View File

@ -34,7 +34,6 @@ import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult 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.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.core.utils.colorizeMatchingText
@ -84,7 +83,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
// sharedViewModel.observeViewEvents {} // sharedViewModel.observeViewEvents {}
views.bootstrapMigrateContinueButton.debouncedClicks { submit() } views.bootstrapMigrateContinueButton.debouncedClicks { submit() }
views.bootstrapMigrateShowPassword.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) }
views.bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) } views.bootstrapMigrateForgotPassphrase.debouncedClicks { sharedViewModel.handle(BootstrapActions.HandleForgotBackupPassphrase) }
views.bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) } views.bootstrapMigrateUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
} }
@ -116,7 +114,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
val isEnteringKey = getBackupSecretForMigration.useKey() val isEnteringKey = getBackupSecretForMigration.useKey()
if (isEnteringKey) { if (isEnteringKey) {
views.bootstrapMigrateShowPassword.isVisible = false
views.bootstrapMigrateEditText.inputType = TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or TYPE_TEXT_FLAG_MULTI_LINE 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) val recKey = getString(R.string.bootstrap_migration_backup_recovery_key)
@ -128,14 +125,6 @@ class BootstrapMigrateBackupFragment @Inject constructor(
views.bootstrapMigrateForgotPassphrase.isVisible = false views.bootstrapMigrateForgotPassphrase.isVisible = false
views.bootstrapMigrateUseFile.isVisible = true views.bootstrapMigrateUseFile.isVisible = true
} else { } 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.bootstrapDescriptionText.text = getString(R.string.bootstrap_migration_enter_backup_password)
views.bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase) views.bootstrapMigrateEditText.hint = getString(R.string.passphrase_enter_passphrase)

View File

@ -139,7 +139,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
private fun handleStartMigratingKeyBackup() { private fun handleStartMigratingKeyBackup() {
if (isBackupCreatedFromPassphrase) { if (isBackupCreatedFromPassphrase) {
setState { setState {
copy(step = BootstrapStep.GetBackupSecretPassForMigration(isPasswordVisible = false, useKey = false)) copy(step = BootstrapStep.GetBackupSecretPassForMigration(useKey = false))
} }
} else { } else {
setState { setState {
@ -151,29 +151,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
override fun handle(action: BootstrapActions) = withState { state -> override fun handle(action: BootstrapActions) = withState { state ->
when (action) { when (action) {
is BootstrapActions.GoBack -> queryBack() 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 -> { BootstrapActions.StartKeyBackupMigration -> {
handleStartMigratingKeyBackup() handleStartMigratingKeyBackup()
} }
@ -193,9 +170,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
setState { setState {
copy( copy(
passphrase = action.passphrase, passphrase = action.passphrase,
step = BootstrapStep.ConfirmPassphrase( step = BootstrapStep.ConfirmPassphrase
isPasswordVisible = (state.step as? BootstrapStep.SetupPassphrase)?.isPasswordVisible ?: false
)
) )
} }
} }
@ -255,7 +230,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
BootstrapActions.HandleForgotBackupPassphrase -> { BootstrapActions.HandleForgotBackupPassphrase -> {
if (state.step is BootstrapStep.GetBackupSecretPassForMigration) { if (state.step is BootstrapStep.GetBackupSecretPassForMigration) {
setState { setState {
copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true)) copy(step = BootstrapStep.GetBackupSecretPassForMigration(true))
} }
} else return@withState } else return@withState
} }
@ -293,7 +268,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
if (action.userWantsToEnterPassphrase) { if (action.userWantsToEnterPassphrase) {
setState { setState {
copy( copy(
step = BootstrapStep.SetupPassphrase(isPasswordVisible = false) step = BootstrapStep.SetupPassphrase
) )
} }
} else { } else {
@ -493,7 +468,6 @@ class BootstrapSharedViewModel @AssistedInject constructor(
setState { setState {
copy( copy(
step = BootstrapStep.GetBackupSecretPassForMigration( step = BootstrapStep.GetBackupSecretPassForMigration(
isPasswordVisible = state.step.isPasswordVisible,
useKey = false useKey = false
) )
) )
@ -524,9 +498,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
is BootstrapStep.ConfirmPassphrase -> { is BootstrapStep.ConfirmPassphrase -> {
setState { setState {
copy( copy(
step = BootstrapStep.SetupPassphrase( step = BootstrapStep.SetupPassphrase
isPasswordVisible = state.step.isPasswordVisible
)
) )
} }
} }

View File

@ -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 // 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 FirstForm(val keyBackUpExist: Boolean, val reset: Boolean = false) : BootstrapStep()
data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() object SetupPassphrase : BootstrapStep()
data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() object ConfirmPassphrase : BootstrapStep()
data class AccountReAuth(val failure: String? = null) : BootstrapStep() data class AccountReAuth(val failure: String? = null) : BootstrapStep()
abstract class GetBackupSecretForMigration : 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 GetBackupSecretKeyForMigration : GetBackupSecretForMigration()
object Initializing : BootstrapStep() object Initializing : BootstrapStep()

View File

@ -32,6 +32,8 @@ import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.home.room.ScSdkPreferences import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -58,7 +60,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
private val scSdkPreferences: ScSdkPreferences, private val scSdkPreferences: ScSdkPreferences,
private val callManager: WebRtcCallManager, private val callManager: WebRtcCallManager,
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler) private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites)
: VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState), : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener { CallProtocolsChecker.Listener {
@ -206,7 +209,10 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
} }
is RoomGroupingMethod.BySpace -> { is RoomGroupingMethod.BySpace -> {
val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId
val dmInvites = session.getRoomSummaries( var dmInvites = 0
var roomsInvite = 0
if (autoAcceptInvites.showInvites()) {
dmInvites = session.getRoomSummaries(
roomSummaryQueryParams { roomSummaryQueryParams {
memberships = listOf(Membership.INVITE) memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_DM roomCategoryFilter = RoomCategoryFilter.ONLY_DM
@ -214,13 +220,14 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
} }
).size ).size
val roomsInvite = session.getRoomSummaries( roomsInvite = session.getRoomSummaries(
roomSummaryQueryParams { roomSummaryQueryParams {
memberships = listOf(Membership.INVITE) memberships = listOf(Membership.INVITE)
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId)
} }
).size ).size
}
val dmRooms = session.getNotificationCountForRooms( val dmRooms = session.getNotificationCountForRooms(
roomSummaryQueryParams { roomSummaryQueryParams {

View File

@ -30,6 +30,7 @@ import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.home.room.ScSdkPreferences import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -56,7 +57,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
session: Session, session: Session,
private val scSdkPreferences: ScSdkPreferences, private val scSdkPreferences: ScSdkPreferences,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
appStateHandler: AppStateHandler) appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites)
: VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) { : VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -95,12 +97,17 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
}, },
scSdkPreferences scSdkPreferences
) )
val invites = session.getRoomSummaries( val invites = if (autoAcceptInvites.hideInvites) {
0
} else {
session.getRoomSummaries(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.INVITE) this.memberships = listOf(Membership.INVITE)
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
} }
).size ).size
}
copy( copy(
homeSpaceUnread = RoomAggregateNotificationCount( homeSpaceUnread = RoomAggregateNotificationCount(
counts.notificationCount + invites, counts.notificationCount + invites,
@ -134,10 +141,13 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
is RoomGroupingMethod.BySpace -> { is RoomGroupingMethod.BySpace -> {
val selectedSpace = appStateHandler.safeActiveSpaceId() val selectedSpace = appStateHandler.safeActiveSpaceId()
val inviteCount = session.getRoomSummaries( val inviteCount = if (autoAcceptInvites.hideInvites) {
0
} else {
session.getRoomSummaries(
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
).size ).size
}
val totalCount = session.getNotificationCountForRooms( val totalCount = session.getNotificationCountForRooms(
roomSummaryQueryParams { roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN) this.memberships = listOf(Membership.JOIN)

View File

@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
@ -75,7 +74,6 @@ import com.vanniktech.emoji.EmojiPopup
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.ConfirmationDialogBuilder
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper 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.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.exhaustive 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.ActiveConferenceView
import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.FailedMessagesWarningView 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.KnownCallsViewHolder
import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.ui.views.NotificationAreaView
import im.vector.app.core.utils.Debouncer import im.vector.app.core.utils.Debouncer
@ -241,7 +238,6 @@ class RoomDetailFragment @Inject constructor(
VectorBaseFragment<FragmentRoomDetailBinding>(), VectorBaseFragment<FragmentRoomDetailBinding>(),
TimelineEventController.Callback, TimelineEventController.Callback,
VectorInviteView.Callback, VectorInviteView.Callback,
JumpToReadMarkerView.Callback,
AttachmentTypeSelectorView.Callback, AttachmentTypeSelectorView.Callback,
AttachmentsHelper.Callback, AttachmentsHelper.Callback,
GalleryOrCameraDialogHelper.Listener, GalleryOrCameraDialogHelper.Listener,
@ -358,6 +354,10 @@ class RoomDetailFragment @Inject constructor(
renderTombstoneEventHandling(it) renderTombstoneEventHandling(it)
} }
roomDetailViewModel.selectSubscribe(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ ->
updateJumpToReadMarkerViewVisibility()
}
roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode, RoomDetailViewState::canSendMessage) { mode, canSend -> roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode, RoomDetailViewState::canSendMessage) { mode, canSend ->
if (!canSend) { if (!canSend) {
return@selectSubscribe return@selectSubscribe
@ -727,7 +727,12 @@ class RoomDetailFragment @Inject constructor(
} }
private fun setupJumpToReadMarkerView() { private fun setupJumpToReadMarkerView() {
views.jumpToReadMarkerView.callback = this views.jumpToReadMarkerView.setOnClickListener {
onJumpToReadMarkerClicked()
}
views.jumpToReadMarkerView.setOnCloseIconClickListener {
roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead)
}
} }
private fun setupActiveCallView() { private fun setupActiveCallView() {
@ -1066,7 +1071,13 @@ class RoomDetailFragment @Inject constructor(
timelineEventController.timeline = roomDetailViewModel.timeline timelineEventController.timeline = roomDetailViewModel.timeline
views.timelineRecyclerView.trackItemsVisibilityChange() 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() val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController)
// Force scroll until the user has scrolled to address the bug where the list would jump during initial loading // Force scroll until the user has scrolled to address the bug where the list would jump during initial loading
@ -1079,8 +1090,6 @@ class RoomDetailFragment @Inject constructor(
it.dispatchTo(stateRestorer) it.dispatchTo(stateRestorer)
it.dispatchTo(scrollOnNewMessageCallback) it.dispatchTo(scrollOnNewMessageCallback)
it.dispatchTo(scrollOnHighlightedEventCallback) it.dispatchTo(scrollOnHighlightedEventCallback)
updateJumpToReadMarkerViewVisibility()
jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay()
} }
timelineEventController.addModelBuildListener(modelBuildListener) timelineEventController.addModelBuildListener(modelBuildListener)
views.timelineRecyclerView.adapter = timelineEventController.adapter views.timelineRecyclerView.adapter = timelineEventController.adapter
@ -1145,7 +1154,7 @@ class RoomDetailFragment @Inject constructor(
is UnreadState.ReadMarkerNotLoaded -> true is UnreadState.ReadMarkerNotLoaded -> true
is UnreadState.HasUnread -> { is UnreadState.HasUnread -> {
if (it.canShowJumpToReadMarker) { if (it.canShowJumpToReadMarker) {
val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() val positionOfReadMarker = timelineEventController.getPositionOfReadMarker()
if (positionOfReadMarker == null) { if (positionOfReadMarker == null) {
false false
@ -1433,7 +1442,7 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailAction.ReportContent -> { is RoomDetailAction.ReportContent -> {
when { when {
data.spam -> { data.spam -> {
MaterialAlertDialogBuilder(requireActivity()) MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
.setTitle(R.string.content_reported_as_spam_title) .setTitle(R.string.content_reported_as_spam_title)
.setMessage(R.string.content_reported_as_spam_content) .setMessage(R.string.content_reported_as_spam_content)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
@ -1441,10 +1450,9 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
} }
.show() .show()
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
} }
data.inappropriate -> { data.inappropriate -> {
MaterialAlertDialogBuilder(requireActivity()) MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
.setTitle(R.string.content_reported_as_inappropriate_title) .setTitle(R.string.content_reported_as_inappropriate_title)
.setMessage(R.string.content_reported_as_inappropriate_content) .setMessage(R.string.content_reported_as_inappropriate_content)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
@ -1452,10 +1460,9 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
} }
.show() .show()
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
} }
else -> { else -> {
MaterialAlertDialogBuilder(requireActivity()) MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive)
.setTitle(R.string.content_reported_title) .setTitle(R.string.content_reported_title)
.setMessage(R.string.content_reported_content) .setMessage(R.string.content_reported_content)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
@ -1463,7 +1470,6 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId)) roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
} }
.show() .show()
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
} }
} }
} }
@ -1525,7 +1531,7 @@ class RoomDetailFragment @Inject constructor(
.subscribe { managed -> .subscribe { managed ->
if (!managed) { if (!managed) {
if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) { 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) .setTitle(R.string.external_link_confirmation_title)
.setMessage( .setMessage(
getString(R.string.external_link_confirmation_message, title, url) getString(R.string.external_link_confirmation_message, title, url)
@ -1538,7 +1544,6 @@ class RoomDetailFragment @Inject constructor(
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
} else { } else {
// Open in external browser, in a new Tab // Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url) openUrlInExternalBrowser(requireContext(), url)
@ -1637,8 +1642,7 @@ class RoomDetailFragment @Inject constructor(
override fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean { override fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
val roomId = roomDetailArgs.roomId val roomId = roomDetailViewModel.timeline.getTimelineEventWithId(informationData.eventId)?.roomId ?: return false
this.view?.hideKeyboard() this.view?.hideKeyboard()
MessageActionsBottomSheet MessageActionsBottomSheet
@ -1722,7 +1726,6 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onReadMarkerVisible() { override fun onReadMarkerVisible() {
updateJumpToReadMarkerViewVisibility()
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState) roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
} }
@ -1893,7 +1896,7 @@ class RoomDetailFragment @Inject constructor(
} }
private fun askConfirmationToIgnoreUser(senderId: String) { private fun askConfirmationToIgnoreUser(senderId: String) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
.setTitle(R.string.room_participants_action_ignore_title) .setTitle(R.string.room_participants_action_ignore_title)
.setMessage(R.string.room_participants_action_ignore_prompt_msg) .setMessage(R.string.room_participants_action_ignore_prompt_msg)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
@ -1901,7 +1904,6 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId)) roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId))
} }
.show() .show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
} }
/** /**
@ -1984,10 +1986,7 @@ class RoomDetailFragment @Inject constructor(
roomDetailViewModel.handle(RoomDetailAction.RejectInvite) roomDetailViewModel.handle(RoomDetailAction.RejectInvite)
} }
// JumpToReadMarkerView.Callback private fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
views.jumpToReadMarkerView.isVisible = false
if (it.unreadState is UnreadState.HasUnread) { if (it.unreadState is UnreadState.HasUnread) {
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false)) roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false))
} }
@ -1996,10 +1995,6 @@ class RoomDetailFragment @Inject constructor(
} }
} }
override fun onClearReadMarkerClicked() {
roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead)
}
// AttachmentTypeSelectorView.Callback // AttachmentTypeSelectorView.Callback
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted -> private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->

View File

@ -48,9 +48,9 @@ import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrate
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider 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.composer.rainbow.RainbowGenerator
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler 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.PowerLevelsHolder import im.vector.app.features.home.room.detail.timeline.helper.PowerLevelsHolder
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder 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.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
@ -121,7 +121,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val chatEffectManager: ChatEffectManager, private val chatEffectManager: ChatEffectManager,
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val jitsiService: JitsiService, private val jitsiService: JitsiService,
timelineSettingsFactory: TimelineSettingsFactory timelineFactory: TimelineFactory
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
@ -129,9 +129,8 @@ class RoomDetailViewModel @AssistedInject constructor(
private val eventId = initialState.eventId private val eventId = initialState.eventId
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>() private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>()
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>() private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
private val timelineSettings = timelineSettingsFactory.create()
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>() 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) // Same lifecycle than the ViewModel (survive to screen rotation)
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope) val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
@ -1252,6 +1251,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleMarkAllAsRead() { private fun handleMarkAllAsRead() {
setState { copy(unreadState = UnreadState.HasNoUnread) }
viewModelScope.launch { viewModelScope.launch {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) } tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) }
} }
@ -1388,7 +1388,6 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
} }
.subscribe { .subscribe {
Timber.v("Unread state: $it")
setState { copy(unreadState = it) } setState { copy(unreadState = it) }
} }
.disposeOnClear() .disposeOnClear()

View File

@ -20,7 +20,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.core.platform.DefaultListUpdateCallback
import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
/** /**
@ -42,20 +41,11 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView,
private fun scrollIfNeeded() { private fun scrollIfNeeded() {
val eventId = scheduledEventId.get() ?: return val eventId = scheduledEventId.get() ?: return
val positionToScroll = timelineEventController.searchPositionOfEvent(eventId) val positionToScroll = timelineEventController.searchPositionOfEvent(eventId) ?: return
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() recyclerView.stopScroll()
layoutManager.scrollToPosition(positionToScroll) layoutManager.scrollToPosition(positionToScroll)
}
scheduledEventId.set(null) scheduledEventId.set(null)
} }
}
fun scheduleScrollTo(eventId: String?) { fun scheduleScrollTo(eventId: String?) {
scheduledEventId.set(eventId) scheduledEventId.set(eventId)

View File

@ -39,14 +39,14 @@ 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.MergedHeaderItemFactory
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory 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.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.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.InvalidateTimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper 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.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper 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.helper.TimelineEventVisibilityStateChangedListener
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.InvalidateTimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider 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.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
@ -164,10 +164,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
override fun onChanged(position: Int, count: Int, payload: Any?) { override fun onChanged(position: Int, count: Int, payload: Any?) {
synchronized(modelCache) { synchronized(modelCache) {
assertUpdateCallbacksAllowed() assertUpdateCallbacksAllowed()
(position until (position + count)).forEach { (position until position + count).forEach {
// Invalidate cache // Invalidate cache
modelCache[it] = null 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() requestModelBuild()
} }
} }
@ -353,10 +362,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val event = currentSnapshot[position] val event = currentSnapshot[position]
val nextEvent = currentSnapshot.nextOrNull(position) val nextEvent = currentSnapshot.nextOrNull(position)
val prevEvent = currentSnapshot.prevOrNull(position) val prevEvent = currentSnapshot.prevOrNull(position)
val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
}
val params = TimelineItemFactoryParams( val params = TimelineItemFactoryParams(
event = event, event = event,
prevEvent = prevEvent, prevEvent = prevEvent,
nextEvent = nextEvent, nextEvent = nextEvent,
nextDisplayableEvent = nextDisplayableEvent,
highlightedEventId = eventIdToHighlight, highlightedEventId = eventIdToHighlight,
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts, lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
callback = callback callback = callback

View File

@ -16,6 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.factory package im.vector.app.features.home.room.detail.timeline.factory
import im.vector.app.core.epoxy.VectorEpoxyModel 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.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController 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.CallTileTimelineItem_ import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData 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.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent 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 import javax.inject.Inject
class CallItemFactory @Inject constructor( class CallItemFactory @Inject constructor(
private val session: Session,
private val messageColorProvider: MessageColorProvider, private val messageColorProvider: MessageColorProvider,
private val messageInformationDataFactory: MessageInformationDataFactory, private val messageInformationDataFactory: MessageInformationDataFactory,
private val messageItemAttributesFactory: MessageItemAttributesFactory, private val messageItemAttributesFactory: MessageItemAttributesFactory,
@ -132,7 +135,8 @@ class CallItemFactory @Inject constructor(
isStillActive: Boolean, isStillActive: Boolean,
callback: TimelineEventController.Callback? callback: TimelineEventController.Callback?
): CallTileTimelineItem? { ): 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 { val attributes = messageItemAttributesFactory.create(null, informationData, callback).let {
CallTileTimelineItem.Attributes( CallTileTimelineItem.Attributes(
callId = callId, callId = callId,

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

View File

@ -23,6 +23,7 @@ data class TimelineItemFactoryParams(
val event: TimelineEvent, val event: TimelineEvent,
val prevEvent: TimelineEvent? = null, val prevEvent: TimelineEvent? = null,
val nextEvent: TimelineEvent? = null, val nextEvent: TimelineEvent? = null,
val nextDisplayableEvent: TimelineEvent? = null,
val highlightedEventId: String? = null, val highlightedEventId: String? = null,
val lastSentEventIdWithoutReadReceipts: String? = null, val lastSentEventIdWithoutReadReceipts: String? = null,
val callback: TimelineEventController.Callback? = null val callback: TimelineEventController.Callback? = null

View File

@ -57,28 +57,29 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
private val roomSummariesHolder: RoomSummariesHolder, private val roomSummariesHolder: RoomSummariesHolder,
private val powerLevelsHolder: PowerLevelsHolder, private val powerLevelsHolder: PowerLevelsHolder,
private val dateFormatter: VectorDateFormatter, private val dateFormatter: VectorDateFormatter,
private val vectorPreferences: VectorPreferences, private val context: Context,
private val context: Context) { private val visibilityHelper: TimelineEventVisibilityHelper,
private val vectorPreferences: VectorPreferences) {
fun create(params: TimelineItemFactoryParams): MessageInformationData { fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event val event = params.event
val nextEvent = params.nextEvent val nextDisplayableEvent = params.nextDisplayableEvent
val eventId = event.eventId val eventId = event.eventId
val date = event.root.localDateTime() val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextDisplayableEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false ?: false
val showInformation = val showInformation =
addDaySeparator addDaySeparator
|| event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl || event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl
|| event.senderInfo.disambiguatedDisplayName != nextEvent?.senderInfo?.disambiguatedDisplayName || event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName
|| nextEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) || nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo || isNextMessageReceivedMoreThanOneHourAgo
|| isTileTypeMessage(nextEvent) || isTileTypeMessage(nextDisplayableEvent)
|| nextEvent.isEdition() || nextDisplayableEvent.isEdition()
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE) val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
val e2eDecoration = getE2EDecoration(event) val e2eDecoration = getE2EDecoration(event)

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

View File

@ -23,6 +23,8 @@ import im.vector.app.RoomGroupingMethod
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.ScSdkPreferences import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -40,6 +42,7 @@ class GroupRoomListSectionBuilder(
val stringProvider: StringProvider, val stringProvider: StringProvider,
val viewModelScope: CoroutineScope, val viewModelScope: CoroutineScope,
val appStateHandler: AppStateHandler, val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites,
val onDisposable: (Disposable) -> Unit, val onDisposable: (Disposable) -> Unit,
val onUdpatable: (UpdatableLivePageResult) -> Unit val onUdpatable: (UpdatableLivePageResult) -> Unit
) : RoomListSectionBuilder { ) : RoomListSectionBuilder {
@ -79,6 +82,7 @@ class GroupRoomListSectionBuilder(
) )
} }
RoomListDisplayMode.NOTIFICATIONS -> { RoomListDisplayMode.NOTIFICATIONS -> {
if (autoAcceptInvites.showInvites()) {
addSection( addSection(
sections, sections,
activeGroupAwareQueries, activeGroupAwareQueries,
@ -89,7 +93,7 @@ class GroupRoomListSectionBuilder(
it.roomCategoryFilter = RoomCategoryFilter.ALL it.roomCategoryFilter = RoomCategoryFilter.ALL
it.activeGroupId = actualGroupId it.activeGroupId = actualGroupId
} }
}
addSection( addSection(
sections, sections,
activeGroupAwareQueries, activeGroupAwareQueries,
@ -179,6 +183,7 @@ class GroupRoomListSectionBuilder(
private fun buildRoomsSections(sections: MutableList<RoomsSection>, private fun buildRoomsSections(sections: MutableList<RoomsSection>,
activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>, activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>,
actualGroupId: String?) { actualGroupId: String?) {
if (autoAcceptInvites.showInvites()) {
addSection( addSection(
sections, sections,
activeSpaceAwareQueries, activeSpaceAwareQueries,
@ -189,6 +194,7 @@ class GroupRoomListSectionBuilder(
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
it.activeGroupId = actualGroupId it.activeGroupId = actualGroupId
} }
}
addSection( addSection(
sections, sections,
@ -244,6 +250,7 @@ class GroupRoomListSectionBuilder(
activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>, activeSpaceAwareQueries: MutableList<UpdatableLivePageResult>,
actualGroupId: String? actualGroupId: String?
) { ) {
if (autoAcceptInvites.showInvites()) {
addSection(sections, addSection(sections,
activeSpaceAwareQueries, activeSpaceAwareQueries,
R.string.invitations_header, R.string.invitations_header,
@ -253,6 +260,7 @@ class GroupRoomListSectionBuilder(
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
it.activeGroupId = actualGroupId it.activeGroupId = actualGroupId
} }
}
addSection( addSection(
sections, sections,

View File

@ -16,7 +16,6 @@
package im.vector.app.features.home.room.list package im.vector.app.features.home.room.list
import android.content.DialogInterface
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
@ -37,7 +36,6 @@ import com.airbnb.mvrx.withState
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.dialogs.withColoredButton
import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
@ -425,7 +423,7 @@ class RoomListFragment @Inject constructor(
append(getString(R.string.room_participants_leave_private_warning)) 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) .setTitle(R.string.room_participants_leave_prompt_title)
.setMessage(message) .setMessage(message)
.setPositiveButton(R.string.leave) { _, _ -> .setPositiveButton(R.string.leave) { _, _ ->
@ -433,11 +431,6 @@ class RoomListFragment @Inject constructor(
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
.apply {
if (!isPublicRoom) {
withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
}
} }
override fun invalidate() = withState(roomListViewModel) { state -> override fun invalidate() = withState(roomListViewModel) { state ->

View File

@ -31,6 +31,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.room.ScSdkPreferences import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -51,7 +52,8 @@ class RoomListViewModel @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val scSdkPreferences: ScSdkPreferences, private val scSdkPreferences: ScSdkPreferences,
private val vectorPreferences: VectorPreferences private val vectorPreferences: VectorPreferences,
private val autoAcceptInvites: AutoAcceptInvites
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) { ) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
interface Factory { interface Factory {
@ -129,6 +131,7 @@ class RoomListViewModel @Inject constructor(
appStateHandler, appStateHandler,
viewModelScope, viewModelScope,
suggestedRoomJoiningState, suggestedRoomJoiningState,
autoAcceptInvites,
{ {
it.disposeOnClear() it.disposeOnClear()
}, },
@ -144,6 +147,7 @@ class RoomListViewModel @Inject constructor(
stringProvider, stringProvider,
viewModelScope, viewModelScope,
appStateHandler, appStateHandler,
autoAcceptInvites,
{ {
it.disposeOnClear() it.disposeOnClear()
}, },

View File

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list
import im.vector.app.AppStateHandler import im.vector.app.AppStateHandler
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.room.ScSdkPreferences import im.vector.app.features.home.room.ScSdkPreferences
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject import javax.inject.Inject
@ -28,17 +29,19 @@ class RoomListViewModelFactory @Inject constructor(private val session: Provider
private val scSdkPreferences: ScSdkPreferences, private val scSdkPreferences: ScSdkPreferences,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences) private val vectorPreferences: VectorPreferences,
private val autoAcceptInvites: AutoAcceptInvites)
: RoomListViewModel.Factory { : RoomListViewModel.Factory {
override fun create(initialState: RoomListViewState): RoomListViewModel { override fun create(initialState: RoomListViewState): RoomListViewModel {
return RoomListViewModel( return RoomListViewModel(
initialState, initialState = initialState,
session.get(), session = session.get(),
stringProvider, stringProvider = stringProvider,
appStateHandler, appStateHandler = appStateHandler,
scSdkPreferences, scSdkPreferences = scSdkPreferences,
vectorPreferences vectorPreferences = vectorPreferences,
autoAcceptInvites = autoAcceptInvites
) )
} }
} }

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