mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 17:35:54 +03:00
Merge branch 'develop' into feature/bca/room_upgrade
This commit is contained in:
commit
3e53fa710a
143 changed files with 1478 additions and 1622 deletions
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
1
changelog.d/3545.feature
Normal file
1
changelog.d/3545.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling.
|
1
changelog.d/3547.feature
Normal file
1
changelog.d/3547.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Implements new design for Jump to unread and quick fix visibility issues.
|
1
changelog.d/3564.bugfix
Normal file
1
changelog.d/3564.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix call invite processed after call is ended because of fastlane mode.
|
1
changelog.d/3577.bugfix
Normal file
1
changelog.d/3577.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix crash after video call.
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
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
2
gradlew
vendored
|
@ -72,7 +72,7 @@ case "`uname`" in
|
||||||
Darwin* )
|
Darwin* )
|
||||||
darwin=true
|
darwin=true
|
||||||
;;
|
;;
|
||||||
MINGW* )
|
MSYS* | MINGW* )
|
||||||
msys=true
|
msys=true
|
||||||
;;
|
;;
|
||||||
NONSTOP* )
|
NONSTOP* )
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
37
library/ui-styles/src/main/java/MaterialProgressDialog.kt
Normal file
37
library/ui-styles/src/main/java/MaterialProgressDialog.kt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.lib.ui.styles.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import im.vector.lib.ui.styles.R
|
||||||
|
import im.vector.lib.ui.styles.databinding.DialogProgressMaterialBinding
|
||||||
|
|
||||||
|
class MaterialProgressDialog(val context: Context) {
|
||||||
|
fun show(message: CharSequence, cancellable: Boolean = false): AlertDialog {
|
||||||
|
val view = LayoutInflater.from(context).inflate(R.layout.dialog_progress_material, null)
|
||||||
|
val views = DialogProgressMaterialBinding.bind(view)
|
||||||
|
views.message.text = message
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(context)
|
||||||
|
.setCancelable(cancellable)
|
||||||
|
.setView(view)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!-- Inspired from https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/layout/progress_dialog.xml -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@android:id/progress"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:max="10000" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message"
|
||||||
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
tools:text="Content\nLine 2" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
13
library/ui-styles/src/main/res/values/dimens_font.xml
Normal file
13
library/ui-styles/src/main/res/values/dimens_font.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<dimen name="text_size_title">24sp</dimen>
|
||||||
|
<dimen name="text_size_headline">18sp</dimen>
|
||||||
|
<dimen name="text_size_subtitle">16sp</dimen>
|
||||||
|
<dimen name="text_size_body">14sp</dimen>
|
||||||
|
<dimen name="text_size_caption">12sp</dimen>
|
||||||
|
<dimen name="text_size_micro">10sp</dimen>
|
||||||
|
|
||||||
|
<dimen name="text_size_button">14sp</dimen>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -1,28 +1,40 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<attr name="vctr_jump_to_unread_style" format="reference" />
|
||||||
|
|
||||||
|
<style name="Widget.Vector.JumpToUnread.Base" parent="Widget.MaterialComponents.Chip.Action">
|
||||||
|
<item name="android:textAppearance">@style/TextAppearance.Vector.Body.Medium</item>
|
||||||
|
<item name="chipEndPadding">12dp</item>
|
||||||
|
<item name="chipIconSize">24dp</item>
|
||||||
|
<item name="chipMinHeight">44dp</item>
|
||||||
|
<item name="chipStartPadding">12dp</item>
|
||||||
|
<item name="closeIconVisible">true</item>
|
||||||
|
<item name="android:elevation">6dp</item>
|
||||||
|
<item name="closeIconSize">24dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Vector.JumpToUnread.Light" parent="Widget.Vector.JumpToUnread.Base">
|
||||||
|
<item name="chipBackgroundColor">@color/element_background_light</item>
|
||||||
|
<item name="closeIconTint">?vctr_content_secondary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Vector.JumpToUnread.Dark" parent="Widget.Vector.JumpToUnread.Base">
|
||||||
|
<item name="chipBackgroundColor">@color/element_system_dark</item>
|
||||||
|
<item name="closeIconTint">?vctr_content_quaternary</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -14,7 +14,7 @@
|
||||||
<style name="TextAppearance.Vector.Title" parent="TextAppearance.MaterialComponents.Headline3">
|
<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>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<style name="TextAppearance.Vector.Body" parent="TextAppearance.MaterialComponents.Body1">
|
<style name="TextAppearance.Vector.Body" parent="TextAppearance.MaterialComponents.Body1">
|
||||||
<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>
|
||||||
|
@ -62,7 +62,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>
|
||||||
|
@ -70,14 +70,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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -132,6 +132,9 @@
|
||||||
<item name="vctr_social_login_button_twitter_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Dark</item>
|
<item name="vctr_social_login_button_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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -134,6 +134,9 @@
|
||||||
<item name="vctr_social_login_button_twitter_style">@style/Widget.Vector.Button.Outlined.SocialLogin.Twitter.Light</item>
|
<item name="vctr_social_login_button_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" />
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
1
newsfragment/3207.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Space Explore Rooms no feedback on failed to join
|
1
newsfragment/3520.misc
Normal file
1
newsfragment/3520.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
VoIP: Merge virtual room timeline in corresponding native room (call events only).
|
1
newsfragment/3531.feature
Normal file
1
newsfragment/3531.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Introduces AutoAcceptInvites which can be enabled at compile time.
|
|
@ -300,7 +300,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'
|
||||||
|
@ -315,7 +315,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'
|
||||||
|
|
||||||
|
@ -355,7 +355,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'
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -92,11 +91,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)
|
||||||
|
@ -119,6 +118,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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,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
|
||||||
|
@ -124,6 +125,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
|
||||||
|
|
|
@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorPr
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
import im.vector.app.features.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
|
||||||
|
@ -160,6 +161,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun pinLocker(): PinLocker
|
fun pinLocker(): PinLocker
|
||||||
|
|
||||||
|
fun autoAcceptInvites(): AutoAcceptInvites
|
||||||
|
|
||||||
fun webRtcCallManager(): WebRtcCallManager
|
fun webRtcCallManager(): WebRtcCallManager
|
||||||
|
|
||||||
fun roomSummaryHolder(): RoomSummariesHolder
|
fun roomSummaryHolder(): RoomSummariesHolder
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.core.dialogs
|
|
||||||
|
|
||||||
import androidx.annotation.AttrRes
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
|
||||||
|
|
||||||
fun AlertDialog.withColoredButton(whichButton: Int, @AttrRes color: Int = R.attr.colorError): AlertDialog {
|
|
||||||
getButton(whichButton)?.setTextColor(ThemeUtils.getColor(context, color))
|
|
||||||
return this
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.core.dialogs
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.text.Editable
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
|
||||||
import im.vector.app.core.extensions.showPassword
|
|
||||||
import im.vector.app.core.platform.SimpleTextWatcher
|
|
||||||
import im.vector.app.databinding.DialogPromptPasswordBinding
|
|
||||||
|
|
||||||
class PromptPasswordDialog {
|
|
||||||
|
|
||||||
private var passwordVisible = false
|
|
||||||
|
|
||||||
fun show(activity: Activity, listener: (String) -> Unit) {
|
|
||||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_prompt_password, null)
|
|
||||||
val views = DialogPromptPasswordBinding.bind(dialogLayout)
|
|
||||||
val textWatcher = object : SimpleTextWatcher() {
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
views.promptPasswordTil.error = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
views.promptPassword.addTextChangedListener(textWatcher)
|
|
||||||
|
|
||||||
views.promptPasswordPasswordReveal.setOnClickListener {
|
|
||||||
passwordVisible = !passwordVisible
|
|
||||||
views.promptPassword.showPassword(passwordVisible)
|
|
||||||
views.promptPasswordPasswordReveal.render(passwordVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
|
||||||
.setTitle(R.string.devices_delete_dialog_title)
|
|
||||||
.setView(dialogLayout)
|
|
||||||
.setPositiveButton(R.string.auth_submit, null)
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
|
||||||
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
dialog.cancel()
|
|
||||||
return@OnKeyListener true
|
|
||||||
}
|
|
||||||
false
|
|
||||||
})
|
|
||||||
.setOnDismissListener {
|
|
||||||
dialogLayout.hideKeyboard()
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
.apply {
|
|
||||||
setOnShowListener {
|
|
||||||
getButton(AlertDialog.BUTTON_POSITIVE)
|
|
||||||
.setOnClickListener {
|
|
||||||
if (views.promptPassword.text.toString().isEmpty()) {
|
|
||||||
views.promptPasswordTil.error = activity.getString(R.string.error_empty_field_your_password)
|
|
||||||
} else {
|
|
||||||
listener.invoke(views.promptPassword.text.toString())
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,13 +40,8 @@ fun SearchView.withoutLeftMargin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun EditText.showPassword(visible: Boolean, updateCursor: Boolean = true) {
|
fun EditText.hidePassword() {
|
||||||
if (visible) {
|
|
||||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||||
} else {
|
|
||||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
|
||||||
}
|
|
||||||
if (updateCursor) setSelection(text?.length ?: 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun View.getMeasurements(): Pair<Int, Int> {
|
fun View.getMeasurements(): Pair<Int, Int> {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.core.ui.views
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.databinding.ViewJumpToReadMarkerBinding
|
|
||||||
|
|
||||||
class JumpToReadMarkerView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0
|
|
||||||
) : RelativeLayout(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
fun onJumpToReadMarkerClicked()
|
|
||||||
fun onClearReadMarkerClicked()
|
|
||||||
}
|
|
||||||
|
|
||||||
var callback: Callback? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
setupView()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupView() {
|
|
||||||
inflate(context, R.layout.view_jump_to_read_marker, this)
|
|
||||||
val views = ViewJumpToReadMarkerBinding.bind(this)
|
|
||||||
setBackgroundColor(ContextCompat.getColor(context, R.color.notification_accent_color))
|
|
||||||
views.jumpToReadMarkerLabelView.setOnClickListener {
|
|
||||||
callback?.onJumpToReadMarkerClicked()
|
|
||||||
}
|
|
||||||
views.closeJumpToReadMarkerView.setOnClickListener {
|
|
||||||
visibility = View.INVISIBLE
|
|
||||||
callback?.onClearReadMarkerClicked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.core.ui.views
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
|
||||||
import im.vector.app.R
|
|
||||||
|
|
||||||
class RevealPasswordImageView @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
defStyleAttr: Int = 0
|
|
||||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
render(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun render(isPasswordShown: Boolean) {
|
|
||||||
if (isPasswordShown) {
|
|
||||||
contentDescription = context.getString(R.string.a11y_hide_password)
|
|
||||||
setImageResource(R.drawable.ic_eye_closed)
|
|
||||||
} else {
|
|
||||||
contentDescription = context.getString(R.string.a11y_show_password)
|
|
||||||
setImageResource(R.drawable.ic_eye)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,7 +24,6 @@ import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.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 -> {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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 =========
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -31,6 +31,8 @@ import im.vector.app.features.call.dialpad.DialPadLookup
|
||||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
import im.vector.app.features.call.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.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
|
||||||
|
@ -56,7 +58,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||||
private val uiStateRepository: UiStateRepository,
|
private val uiStateRepository: UiStateRepository,
|
||||||
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 {
|
||||||
|
|
||||||
|
@ -204,7 +207,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
|
||||||
|
@ -212,13 +218,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 {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod
|
||||||
import im.vector.app.core.platform.EmptyAction
|
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.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
|
||||||
|
@ -54,7 +55,8 @@ data class CountInfo(
|
||||||
class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState,
|
class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState,
|
||||||
session: Session,
|
session: Session,
|
||||||
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
|
||||||
|
@ -92,12 +94,17 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
||||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
|
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
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,
|
||||||
|
@ -129,10 +136,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)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -76,7 +75,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
|
||||||
|
@ -95,7 +93,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,
|
||||||
|
@ -367,6 +363,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
|
||||||
|
@ -751,7 +751,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() {
|
||||||
|
@ -1080,7 +1085,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)
|
||||||
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController)
|
scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(views.timelineRecyclerView, layoutManager, timelineEventController)
|
||||||
|
@ -1091,8 +1102,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
|
||||||
|
@ -1148,7 +1157,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
|
||||||
|
@ -1434,7 +1443,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)
|
||||||
|
@ -1442,10 +1451,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)
|
||||||
|
@ -1453,10 +1461,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)
|
||||||
|
@ -1464,7 +1471,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
.withColoredButton(DialogInterface.BUTTON_NEGATIVE)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1526,7 +1532,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)
|
||||||
|
@ -1539,7 +1545,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)
|
||||||
|
@ -1638,8 +1643,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
|
||||||
|
@ -1723,7 +1727,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReadMarkerVisible() {
|
override fun onReadMarkerVisible() {
|
||||||
updateJumpToReadMarkerViewVisibility()
|
|
||||||
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
|
roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1892,7 +1895,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)
|
||||||
|
@ -1900,7 +1903,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId))
|
roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId))
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1983,10 +1985,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))
|
||||||
}
|
}
|
||||||
|
@ -1995,10 +1994,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 ->
|
||||||
|
|
|
@ -48,8 +48,8 @@ import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrate
|
||||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
import im.vector.app.features.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.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
|
||||||
|
@ -118,7 +118,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 {
|
||||||
|
|
||||||
|
@ -126,9 +126,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)
|
||||||
|
@ -1264,6 +1263,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) }
|
||||||
}
|
}
|
||||||
|
@ -1400,7 +1400,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.subscribe {
|
.subscribe {
|
||||||
Timber.v("Unread state: $it")
|
|
||||||
setState { copy(unreadState = it) }
|
setState { copy(unreadState = it) }
|
||||||
}
|
}
|
||||||
.disposeOnClear()
|
.disposeOnClear()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.detail.timeline.merged
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import kotlin.reflect.KMutableProperty0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This can be use to merge timeline tiles from 2 different rooms.
|
||||||
|
* Be aware it wont work properly with permalink.
|
||||||
|
*/
|
||||||
|
class MergedTimelines(
|
||||||
|
private val coroutineScope: CoroutineScope,
|
||||||
|
private val mainTimeline: Timeline,
|
||||||
|
private val secondaryTimelineParams: SecondaryTimelineParams) : Timeline by mainTimeline {
|
||||||
|
|
||||||
|
data class SecondaryTimelineParams(
|
||||||
|
val timeline: Timeline,
|
||||||
|
val disableReadReceipts: Boolean = true,
|
||||||
|
val shouldFilterTypes: Boolean = false,
|
||||||
|
val allowedTypes: List<String> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
private var mainIsInit = false
|
||||||
|
private var secondaryIsInit = false
|
||||||
|
private val secondaryTimeline = secondaryTimelineParams.timeline
|
||||||
|
|
||||||
|
private val listenersMapping = HashMap<Timeline.Listener, List<ListenerInterceptor>>()
|
||||||
|
private val mainTimelineEvents = ArrayList<TimelineEvent>()
|
||||||
|
private val secondaryTimelineEvents = ArrayList<TimelineEvent>()
|
||||||
|
private val positionsMapping = HashMap<String, Int>()
|
||||||
|
private val mergedEvents = ArrayList<TimelineEvent>()
|
||||||
|
|
||||||
|
private val processingSemaphore = Semaphore(1)
|
||||||
|
|
||||||
|
private class ListenerInterceptor(
|
||||||
|
var timeline: Timeline?,
|
||||||
|
private val wrappedListener: Timeline.Listener,
|
||||||
|
private val shouldFilterTypes: Boolean,
|
||||||
|
private val allowedTypes: List<String>,
|
||||||
|
private val onTimelineUpdate: (List<TimelineEvent>) -> Unit
|
||||||
|
) : Timeline.Listener by wrappedListener {
|
||||||
|
|
||||||
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
val filteredEvents = if (shouldFilterTypes) {
|
||||||
|
snapshot.filter {
|
||||||
|
allowedTypes.contains(it.root.getClearType())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
snapshot
|
||||||
|
}
|
||||||
|
onTimelineUpdate(filteredEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addListener(listener: Timeline.Listener): Boolean {
|
||||||
|
val mainTimelineListener = ListenerInterceptor(
|
||||||
|
timeline = mainTimeline,
|
||||||
|
wrappedListener = listener,
|
||||||
|
shouldFilterTypes = false,
|
||||||
|
allowedTypes = emptyList()) {
|
||||||
|
processTimelineUpdates(::mainIsInit, mainTimelineEvents, it)
|
||||||
|
}
|
||||||
|
val secondaryTimelineListener = ListenerInterceptor(
|
||||||
|
timeline = secondaryTimeline,
|
||||||
|
wrappedListener = listener,
|
||||||
|
shouldFilterTypes = secondaryTimelineParams.shouldFilterTypes,
|
||||||
|
allowedTypes = secondaryTimelineParams.allowedTypes) {
|
||||||
|
processTimelineUpdates(::secondaryIsInit, secondaryTimelineEvents, it)
|
||||||
|
}
|
||||||
|
listenersMapping[listener] = listOf(mainTimelineListener, secondaryTimelineListener)
|
||||||
|
return mainTimeline.addListener(mainTimelineListener) && secondaryTimeline.addListener(secondaryTimelineListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeListener(listener: Timeline.Listener): Boolean {
|
||||||
|
return listenersMapping.remove(listener)?.let {
|
||||||
|
it.forEach { listener ->
|
||||||
|
listener.timeline?.removeListener(listener)
|
||||||
|
listener.timeline = null
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAllListeners() {
|
||||||
|
mainTimeline.removeAllListeners()
|
||||||
|
secondaryTimeline.removeAllListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
mainTimeline.start()
|
||||||
|
secondaryTimeline.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
mainTimeline.dispose()
|
||||||
|
secondaryTimeline.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun restartWithEventId(eventId: String?) {
|
||||||
|
mainTimeline.restartWithEventId(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasMoreToLoad(direction: Timeline.Direction): Boolean {
|
||||||
|
return mainTimeline.hasMoreToLoad(direction) || secondaryTimeline.hasMoreToLoad(direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun paginate(direction: Timeline.Direction, count: Int) {
|
||||||
|
mainTimeline.paginate(direction, count)
|
||||||
|
secondaryTimeline.paginate(direction, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pendingEventCount(): Int {
|
||||||
|
return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun failedToDeliverEventCount(): Int {
|
||||||
|
return mainTimeline.pendingEventCount() + secondaryTimeline.pendingEventCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTimelineEventAtIndex(index: Int): TimelineEvent? {
|
||||||
|
return mergedEvents.getOrNull(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIndexOfEvent(eventId: String?): Int? {
|
||||||
|
return positionsMapping[eventId]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTimelineEventWithId(eventId: String?): TimelineEvent? {
|
||||||
|
return positionsMapping[eventId]?.let {
|
||||||
|
getTimelineEventAtIndex(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processTimelineUpdates(isInit: KMutableProperty0<Boolean>, eventsRef: MutableList<TimelineEvent>, newData: List<TimelineEvent>) {
|
||||||
|
coroutineScope.launch(Dispatchers.Default) {
|
||||||
|
processingSemaphore.withPermit {
|
||||||
|
isInit.set(true)
|
||||||
|
eventsRef.apply {
|
||||||
|
clear()
|
||||||
|
addAll(newData)
|
||||||
|
}
|
||||||
|
mergeTimeline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun mergeTimeline() {
|
||||||
|
val merged = mutableListOf<TimelineEvent>()
|
||||||
|
val mainItr = mainTimelineEvents.toList().listIterator()
|
||||||
|
val secondaryItr = secondaryTimelineEvents.toList().listIterator()
|
||||||
|
var index = 0
|
||||||
|
var correctedSenderInfo: SenderInfo? = mainTimelineEvents.firstOrNull()?.senderInfo
|
||||||
|
if (!mainIsInit || !secondaryIsInit) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
while (merged.size < mainTimelineEvents.size + secondaryTimelineEvents.size) {
|
||||||
|
if (mainItr.hasNext()) {
|
||||||
|
val nextMain = mainItr.next()
|
||||||
|
correctedSenderInfo = nextMain.senderInfo
|
||||||
|
if (secondaryItr.hasNext()) {
|
||||||
|
val nextSecondary = secondaryItr.next()
|
||||||
|
if (nextSecondary.root.originServerTs ?: 0 > nextMain.root.originServerTs ?: 0) {
|
||||||
|
positionsMapping[nextSecondary.eventId] = index
|
||||||
|
merged.add(nextSecondary.correctBeforeMerging(correctedSenderInfo))
|
||||||
|
mainItr.previous()
|
||||||
|
} else {
|
||||||
|
positionsMapping[nextMain.eventId] = index
|
||||||
|
merged.add(nextMain)
|
||||||
|
secondaryItr.previous()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
positionsMapping[nextMain.eventId] = index
|
||||||
|
merged.add(nextMain)
|
||||||
|
}
|
||||||
|
} else if (secondaryItr.hasNext()) {
|
||||||
|
val nextSecondary = secondaryItr.next()
|
||||||
|
positionsMapping[nextSecondary.eventId] = index
|
||||||
|
merged.add(nextSecondary.correctBeforeMerging(correctedSenderInfo))
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
mergedEvents.apply {
|
||||||
|
clear()
|
||||||
|
addAll(merged)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
listenersMapping.keys.forEach { listener ->
|
||||||
|
tryOrNull { listener.onTimelineUpdated(merged) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TimelineEvent.correctBeforeMerging(correctedSenderInfo: SenderInfo?): TimelineEvent {
|
||||||
|
return copy(
|
||||||
|
senderInfo = correctedSenderInfo ?: senderInfo,
|
||||||
|
readReceipts = if (secondaryTimelineParams.disableReadReceipts) emptyList() else readReceipts
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import im.vector.app.R
|
||||||
import im.vector.app.RoomGroupingMethod
|
import im.vector.app.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.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
|
||||||
|
@ -38,6 +40,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 {
|
||||||
|
@ -73,6 +76,7 @@ class GroupRoomListSectionBuilder(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||||
|
if (autoAcceptInvites.showInvites()) {
|
||||||
addSection(
|
addSection(
|
||||||
sections,
|
sections,
|
||||||
activeGroupAwareQueries,
|
activeGroupAwareQueries,
|
||||||
|
@ -83,7 +87,7 @@ class GroupRoomListSectionBuilder(
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||||
it.activeGroupId = actualGroupId
|
it.activeGroupId = actualGroupId
|
||||||
}
|
}
|
||||||
|
}
|
||||||
addSection(
|
addSection(
|
||||||
sections,
|
sections,
|
||||||
activeGroupAwareQueries,
|
activeGroupAwareQueries,
|
||||||
|
@ -115,6 +119,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,
|
||||||
|
@ -125,6 +130,7 @@ class GroupRoomListSectionBuilder(
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
it.activeGroupId = actualGroupId
|
it.activeGroupId = actualGroupId
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
sections,
|
sections,
|
||||||
|
@ -180,6 +186,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,
|
||||||
|
@ -189,6 +196,7 @@ class GroupRoomListSectionBuilder(
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
it.activeGroupId = actualGroupId
|
it.activeGroupId = actualGroupId
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
sections,
|
sections,
|
||||||
|
|
|
@ -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.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -34,7 +33,6 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
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
|
||||||
|
@ -386,7 +384,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) { _, _ ->
|
||||||
|
@ -394,11 +392,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 ->
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.app.RoomGroupingMethod
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
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
|
||||||
|
@ -49,7 +50,8 @@ class RoomListViewModel @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val appStateHandler: AppStateHandler,
|
private val appStateHandler: AppStateHandler,
|
||||||
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 {
|
||||||
|
@ -126,6 +128,7 @@ class RoomListViewModel @Inject constructor(
|
||||||
appStateHandler,
|
appStateHandler,
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
suggestedRoomJoiningState,
|
suggestedRoomJoiningState,
|
||||||
|
autoAcceptInvites,
|
||||||
{
|
{
|
||||||
it.disposeOnClear()
|
it.disposeOnClear()
|
||||||
},
|
},
|
||||||
|
@ -140,6 +143,7 @@ class RoomListViewModel @Inject constructor(
|
||||||
stringProvider,
|
stringProvider,
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
appStateHandler,
|
appStateHandler,
|
||||||
|
autoAcceptInvites,
|
||||||
{
|
{
|
||||||
it.disposeOnClear()
|
it.disposeOnClear()
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,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.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
|
||||||
|
@ -26,16 +27,18 @@ import javax.inject.Provider
|
||||||
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
||||||
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,
|
||||||
vectorPreferences
|
vectorPreferences = vectorPreferences,
|
||||||
|
autoAcceptInvites = autoAcceptInvites
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,13 @@
|
||||||
package im.vector.app.features.home.room.list
|
package im.vector.app.features.home.room.list
|
||||||
|
|
||||||
import com.airbnb.mvrx.Async
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||||
|
@ -37,7 +39,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
private val avatarRenderer: AvatarRenderer) {
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val errorFormatter: ErrorFormatter) {
|
||||||
|
|
||||||
fun create(roomSummary: RoomSummary,
|
fun create(roomSummary: RoomSummary,
|
||||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||||
|
@ -55,12 +58,21 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||||
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
|
fun createSuggestion(spaceChildInfo: SpaceChildInfo,
|
||||||
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
|
suggestedRoomJoiningStates: Map<String, Async<Unit>>,
|
||||||
listener: RoomListListener?): VectorEpoxyModel<*> {
|
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||||
|
val error = (suggestedRoomJoiningStates[spaceChildInfo.childRoomId] as? Fail)?.error
|
||||||
return SpaceChildInfoItem_()
|
return SpaceChildInfoItem_()
|
||||||
.id("sug_${spaceChildInfo.childRoomId}")
|
.id("sug_${spaceChildInfo.childRoomId}")
|
||||||
.matrixItem(spaceChildInfo.toMatrixItem())
|
.matrixItem(spaceChildInfo.toMatrixItem())
|
||||||
.avatarRenderer(avatarRenderer)
|
.avatarRenderer(avatarRenderer)
|
||||||
.topic(spaceChildInfo.topic)
|
.topic(spaceChildInfo.topic)
|
||||||
.buttonLabel(stringProvider.getString(R.string.join))
|
.errorLabel(
|
||||||
|
error?.let {
|
||||||
|
stringProvider.getString(R.string.error_failed_to_join_room, errorFormatter.toHumanReadable(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.buttonLabel(
|
||||||
|
if (error != null) stringProvider.getString(R.string.global_retry)
|
||||||
|
else stringProvider.getString(R.string.join)
|
||||||
|
)
|
||||||
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
|
.loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading)
|
||||||
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
|
.memberCount(spaceChildInfo.activeMemberCount ?: 0)
|
||||||
.buttonClickListener { listener?.onJoinSuggestedRoom(spaceChildInfo) }
|
.buttonClickListener { listener?.onJoinSuggestedRoom(spaceChildInfo) }
|
||||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import me.gujun.android.span.image
|
import me.gujun.android.span.image
|
||||||
|
@ -52,6 +53,7 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
||||||
@EpoxyAttribute var loading: Boolean = false
|
@EpoxyAttribute var loading: Boolean = false
|
||||||
|
|
||||||
@EpoxyAttribute var buttonLabel: String? = null
|
@EpoxyAttribute var buttonLabel: String? = null
|
||||||
|
@EpoxyAttribute var errorLabel: String? = null
|
||||||
|
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
|
||||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null
|
||||||
|
@ -97,6 +99,8 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
||||||
holder.joinButton.isVisible = true
|
holder.joinButton.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
holder.errorTextView.setTextOrHide(errorLabel)
|
||||||
|
|
||||||
holder.joinButton.onClick {
|
holder.joinButton.onClick {
|
||||||
// local echo
|
// local echo
|
||||||
holder.joinButton.isEnabled = false
|
holder.joinButton.isEnabled = false
|
||||||
|
@ -120,5 +124,6 @@ abstract class SpaceChildInfoItem : VectorEpoxyModel<SpaceChildInfoItem.Holder>(
|
||||||
val descriptionText by bind<TextView>(R.id.suggestedRoomDescription)
|
val descriptionText by bind<TextView>(R.id.suggestedRoomDescription)
|
||||||
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||||
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
|
val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
|
||||||
|
val errorTextView by bind<TextView>(R.id.inlineErrorText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import im.vector.app.AppStateHandler
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
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.invite.AutoAcceptInvites
|
||||||
|
import im.vector.app.features.invite.showInvites
|
||||||
import im.vector.app.space
|
import im.vector.app.space
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
@ -50,6 +52,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
val appStateHandler: AppStateHandler,
|
val appStateHandler: AppStateHandler,
|
||||||
val viewModelScope: CoroutineScope,
|
val viewModelScope: CoroutineScope,
|
||||||
private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>,
|
private val suggestedRoomJoiningState: LiveData<Map<String, Async<Unit>>>,
|
||||||
|
private val autoAcceptInvites: AutoAcceptInvites,
|
||||||
val onDisposable: (Disposable) -> Unit,
|
val onDisposable: (Disposable) -> Unit,
|
||||||
val onUdpatable: (UpdatableLivePageResult) -> Unit,
|
val onUdpatable: (UpdatableLivePageResult) -> Unit,
|
||||||
val onlyOrphansInHome: Boolean = false
|
val onlyOrphansInHome: Boolean = false
|
||||||
|
@ -88,6 +91,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||||
|
if (autoAcceptInvites.showInvites()) {
|
||||||
addSection(
|
addSection(
|
||||||
sections = sections,
|
sections = sections,
|
||||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||||
|
@ -103,6 +107,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
it.memberships = listOf(Membership.INVITE)
|
it.memberships = listOf(Membership.INVITE)
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
sections = sections,
|
sections = sections,
|
||||||
|
@ -136,6 +141,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildRoomsSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
private fun buildRoomsSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
||||||
|
if (autoAcceptInvites.showInvites()) {
|
||||||
addSection(
|
addSection(
|
||||||
sections = sections,
|
sections = sections,
|
||||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||||
|
@ -147,6 +153,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
it.memberships = listOf(Membership.INVITE)
|
it.memberships = listOf(Membership.INVITE)
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSection(
|
addSection(
|
||||||
sections,
|
sections,
|
||||||
|
@ -253,6 +260,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildDmSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
private fun buildDmSections(sections: MutableList<RoomsSection>, activeSpaceAwareQueries: MutableList<RoomListViewModel.ActiveSpaceQueryUpdater>) {
|
||||||
|
if (autoAcceptInvites.showInvites()) {
|
||||||
addSection(sections = sections,
|
addSection(sections = sections,
|
||||||
activeSpaceUpdaters = activeSpaceAwareQueries,
|
activeSpaceUpdaters = activeSpaceAwareQueries,
|
||||||
nameRes = R.string.invitations_header,
|
nameRes = R.string.invitations_header,
|
||||||
|
@ -263,6 +271,7 @@ class SpaceRoomListSectionBuilder(
|
||||||
it.memberships = listOf(Membership.INVITE)
|
it.memberships = listOf(Membership.INVITE)
|
||||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addSection(sections,
|
addSection(sections,
|
||||||
activeSpaceAwareQueries,
|
activeSpaceAwareQueries,
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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.invite
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines 2 flags so you can handle auto accept invites.
|
||||||
|
* At the moment we only have [CompileTimeAutoAcceptInvites] implementation.
|
||||||
|
*/
|
||||||
|
interface AutoAcceptInvites {
|
||||||
|
/**
|
||||||
|
* Enable auto-accept invites. It means, as soon as you got an invite from the sync, it will try to join it.
|
||||||
|
*/
|
||||||
|
val isEnabled: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide invites from the UI (from notifications, notification count and room list). By default invites are hidden when [isEnabled] is true
|
||||||
|
*/
|
||||||
|
val hideInvites: Boolean
|
||||||
|
get() = isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AutoAcceptInvites.showInvites() = !hideInvites
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple compile time implementation of AutoAcceptInvites flags.
|
||||||
|
*/
|
||||||
|
class CompileTimeAutoAcceptInvites @Inject constructor() : AutoAcceptInvites {
|
||||||
|
override val isEnabled = false
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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.invite
|
||||||
|
|
||||||
|
import im.vector.app.ActiveSessionDataSource
|
||||||
|
import im.vector.app.features.session.coroutineScope
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
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.roomSummaryQueryParams
|
||||||
|
import org.matrix.android.sdk.rx.rx
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is responsible for auto accepting invites.
|
||||||
|
* It's listening to invites and membershipChanges so it can retry automatically if needed.
|
||||||
|
* This mechanism will be on only if AutoAcceptInvites.isEnabled is true.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class InvitesAcceptor @Inject constructor(
|
||||||
|
private val sessionDataSource: ActiveSessionDataSource,
|
||||||
|
private val autoAcceptInvites: AutoAcceptInvites
|
||||||
|
) : Session.Listener {
|
||||||
|
|
||||||
|
private lateinit var activeSessionDisposable: Disposable
|
||||||
|
private val shouldRejectRoomIds = mutableSetOf<String>()
|
||||||
|
private val invitedRoomDisposables = HashMap<String, Disposable>()
|
||||||
|
private val semaphore = Semaphore(1)
|
||||||
|
|
||||||
|
fun initialize() {
|
||||||
|
observeActiveSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeActiveSession() {
|
||||||
|
activeSessionDisposable = sessionDataSource.observe()
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe {
|
||||||
|
it.orNull()?.let { session ->
|
||||||
|
onSessionActive(session)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSessionActive(session: Session) {
|
||||||
|
if (!autoAcceptInvites.isEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (invitedRoomDisposables.containsKey(session.sessionId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session.addListener(this)
|
||||||
|
val roomQueryParams = roomSummaryQueryParams {
|
||||||
|
this.memberships = listOf(Membership.INVITE)
|
||||||
|
}
|
||||||
|
val rxSession = session.rx()
|
||||||
|
Observable
|
||||||
|
.combineLatest(
|
||||||
|
rxSession.liveRoomSummaries(roomQueryParams),
|
||||||
|
rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS),
|
||||||
|
{ invitedRooms, _ -> invitedRooms.map { it.roomId } }
|
||||||
|
)
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.subscribe { invitedRoomIds ->
|
||||||
|
session.coroutineScope.launch {
|
||||||
|
semaphore.withPermit {
|
||||||
|
Timber.v("Invited roomIds: $invitedRoomIds")
|
||||||
|
for (roomId in invitedRoomIds) {
|
||||||
|
async { session.joinRoomSafely(roomId) }.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.also {
|
||||||
|
invitedRoomDisposables[session.sessionId] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun Session.joinRoomSafely(roomId: String) {
|
||||||
|
if (shouldRejectRoomIds.contains(roomId)) {
|
||||||
|
getRoom(roomId)?.rejectInviteSafely()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val roomMembershipChanged = getChangeMemberships(roomId)
|
||||||
|
if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) {
|
||||||
|
try {
|
||||||
|
Timber.v("Try auto join room: $roomId")
|
||||||
|
joinRoom(roomId)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.v("Failed auto join room: $roomId")
|
||||||
|
// if we got 404 on invites, the inviting user have left or the hs is off.
|
||||||
|
if (failure is Failure.ServerError && failure.httpCode == 404) {
|
||||||
|
val room = getRoom(roomId) ?: return
|
||||||
|
val inviterId = room.roomSummary()?.inviterId
|
||||||
|
// if the inviting user is on the same HS, there can only be one cause: they left, so we try to reject the invite.
|
||||||
|
if (inviterId?.endsWith(sessionParams.credentials.homeServer.orEmpty()).orFalse()) {
|
||||||
|
shouldRejectRoomIds.add(roomId)
|
||||||
|
room.rejectInviteSafely()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun Room.rejectInviteSafely() {
|
||||||
|
try {
|
||||||
|
leave(null)
|
||||||
|
shouldRejectRoomIds.remove(roomId)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.v("Fail rejecting invite for room: $roomId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSessionStopped(session: Session) {
|
||||||
|
session.removeListener(this)
|
||||||
|
invitedRoomDisposables.remove(session.sessionId)?.dispose()
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
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.extensions.hidePassword
|
||||||
import im.vector.app.core.extensions.toReducedUrl
|
import im.vector.app.core.extensions.toReducedUrl
|
||||||
import im.vector.app.databinding.FragmentLoginBinding
|
import im.vector.app.databinding.FragmentLoginBinding
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
@ -53,7 +53,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLoginBinding>() {
|
class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLoginBinding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
|
||||||
private var isSignupMode = false
|
private var isSignupMode = false
|
||||||
|
|
||||||
// Temporary patch for https://github.com/vector-im/riotX-android/issues/1410,
|
// Temporary patch for https://github.com/vector-im/riotX-android/issues/1410,
|
||||||
|
@ -69,7 +68,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||||
|
|
||||||
setupSubmitButton()
|
setupSubmitButton()
|
||||||
setupForgottenPasswordButton()
|
setupForgottenPasswordButton()
|
||||||
setupPasswordReveal()
|
|
||||||
|
|
||||||
views.passwordField.setOnEditorActionListener { _, actionId, _ ->
|
views.passwordField.setOnEditorActionListener { _, actionId, _ ->
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
@ -247,23 +245,6 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnForgetPasswordClicked))
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnForgetPasswordClicked))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
|
||||||
passwordShown = false
|
|
||||||
|
|
||||||
views.passwordReveal.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderPasswordField() {
|
|
||||||
views.passwordField.showPassword(passwordShown)
|
|
||||||
views.passwordReveal.render(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
loginViewModel.handle(LoginAction.ResetLogin)
|
loginViewModel.handle(LoginAction.ResetLogin)
|
||||||
}
|
}
|
||||||
|
@ -290,8 +271,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||||
when (state.asyncLoginAction) {
|
when (state.asyncLoginAction) {
|
||||||
is Loading -> {
|
is Loading -> {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
is Fail -> {
|
is Fail -> {
|
||||||
val error = state.asyncLoginAction.error
|
val error = state.asyncLoginAction.error
|
||||||
|
@ -317,8 +297,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||||
when (state.asyncRegistration) {
|
when (state.asyncRegistration) {
|
||||||
is Loading -> {
|
is Loading -> {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
// Success is handled by the LoginActivity
|
// Success is handled by the LoginActivity
|
||||||
is Success -> Unit
|
is Success -> Unit
|
||||||
|
|
|
@ -28,7 +28,7 @@ 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.isEmail
|
import im.vector.app.core.extensions.isEmail
|
||||||
import im.vector.app.core.extensions.showPassword
|
import im.vector.app.core.extensions.hidePassword
|
||||||
import im.vector.app.core.extensions.toReducedUrl
|
import im.vector.app.core.extensions.toReducedUrl
|
||||||
import im.vector.app.databinding.FragmentLoginResetPasswordBinding
|
import im.vector.app.databinding.FragmentLoginResetPasswordBinding
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
@ -41,8 +41,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<FragmentLoginResetPasswordBinding>() {
|
class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<FragmentLoginResetPasswordBinding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
|
||||||
|
|
||||||
// Show warning only once
|
// Show warning only once
|
||||||
private var showWarning = true
|
private var showWarning = true
|
||||||
|
|
||||||
|
@ -54,7 +52,6 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
setupSubmitButton()
|
setupSubmitButton()
|
||||||
setupPasswordReveal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUi(state: LoginViewState) {
|
private fun setupUi(state: LoginViewState) {
|
||||||
|
@ -112,23 +109,6 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
|
||||||
views.passwordFieldTil.error = null
|
views.passwordFieldTil.error = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
|
||||||
passwordShown = false
|
|
||||||
|
|
||||||
views.passwordReveal.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderPasswordField() {
|
|
||||||
views.passwordField.showPassword(passwordShown)
|
|
||||||
views.passwordReveal.render(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
loginViewModel.handle(LoginAction.ResetResetPassword)
|
loginViewModel.handle(LoginAction.ResetResetPassword)
|
||||||
}
|
}
|
||||||
|
@ -139,8 +119,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
|
||||||
when (state.asyncResetPassword) {
|
when (state.asyncResetPassword) {
|
||||||
is Loading -> {
|
is Loading -> {
|
||||||
// Ensure new password is hidden
|
// Ensure new password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
is Fail -> {
|
is Fail -> {
|
||||||
views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
|
views.resetPasswordEmailTil.error = errorFormatter.toHumanReadable(state.asyncResetPassword.error)
|
||||||
|
|
|
@ -28,7 +28,7 @@ import com.airbnb.mvrx.Fail
|
||||||
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.extensions.hidePassword
|
||||||
import im.vector.app.databinding.FragmentLoginSigninPassword2Binding
|
import im.vector.app.databinding.FragmentLoginSigninPassword2Binding
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import io.reactivex.rxkotlin.subscribeBy
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
|
@ -47,8 +47,6 @@ class LoginFragmentSigninPassword2 @Inject constructor(
|
||||||
private val avatarRenderer: AvatarRenderer
|
private val avatarRenderer: AvatarRenderer
|
||||||
) : AbstractSSOLoginFragment2<FragmentLoginSigninPassword2Binding>() {
|
) : AbstractSSOLoginFragment2<FragmentLoginSigninPassword2Binding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninPassword2Binding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninPassword2Binding {
|
||||||
return FragmentLoginSigninPassword2Binding.inflate(inflater, container, false)
|
return FragmentLoginSigninPassword2Binding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +56,6 @@ class LoginFragmentSigninPassword2 @Inject constructor(
|
||||||
|
|
||||||
setupSubmitButton()
|
setupSubmitButton()
|
||||||
setupForgottenPasswordButton()
|
setupForgottenPasswordButton()
|
||||||
setupPasswordReveal()
|
|
||||||
setupAutoFill()
|
setupAutoFill()
|
||||||
|
|
||||||
views.passwordField.setOnEditorActionListener { _, actionId, _ ->
|
views.passwordField.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
@ -135,23 +132,6 @@ class LoginFragmentSigninPassword2 @Inject constructor(
|
||||||
loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen))
|
loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
|
||||||
passwordShown = false
|
|
||||||
|
|
||||||
views.passwordReveal.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderPasswordField() {
|
|
||||||
views.passwordField.showPassword(passwordShown)
|
|
||||||
views.passwordReveal.render(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
// loginViewModel.handle(LoginAction2.ResetSignin)
|
// loginViewModel.handle(LoginAction2.ResetSignin)
|
||||||
}
|
}
|
||||||
|
@ -169,8 +149,7 @@ class LoginFragmentSigninPassword2 @Inject constructor(
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import androidx.autofill.HintConstants
|
||||||
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.extensions.hidePassword
|
||||||
import im.vector.app.databinding.FragmentLoginSignupPassword2Binding
|
import im.vector.app.databinding.FragmentLoginSignupPassword2Binding
|
||||||
import io.reactivex.rxkotlin.subscribeBy
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -37,8 +37,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginSignupPassword2Binding>() {
|
class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginSignupPassword2Binding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupPassword2Binding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSignupPassword2Binding {
|
||||||
return FragmentLoginSignupPassword2Binding.inflate(inflater, container, false)
|
return FragmentLoginSignupPassword2Binding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +46,6 @@ class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment
|
||||||
|
|
||||||
setupSubmitButton()
|
setupSubmitButton()
|
||||||
setupAutoFill()
|
setupAutoFill()
|
||||||
setupPasswordReveal()
|
|
||||||
|
|
||||||
views.passwordField.setOnEditorActionListener { _, actionId, _ ->
|
views.passwordField.setOnEditorActionListener { _, actionId, _ ->
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
@ -97,23 +94,6 @@ class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment
|
||||||
.disposeOnDestroyView()
|
.disposeOnDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
|
||||||
passwordShown = false
|
|
||||||
|
|
||||||
views.passwordReveal.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderPasswordField() {
|
|
||||||
views.passwordReveal.render(passwordShown)
|
|
||||||
views.passwordField.showPassword(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
// loginViewModel.handle(LoginAction2.ResetSignup)
|
// loginViewModel.handle(LoginAction2.ResetSignup)
|
||||||
}
|
}
|
||||||
|
@ -127,8 +107,7 @@ class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import androidx.core.view.isVisible
|
||||||
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.extensions.hidePassword
|
||||||
import im.vector.app.core.extensions.toReducedUrl
|
import im.vector.app.core.extensions.toReducedUrl
|
||||||
import im.vector.app.databinding.FragmentLoginSigninToAny2Binding
|
import im.vector.app.databinding.FragmentLoginSigninToAny2Binding
|
||||||
import im.vector.app.features.login.LoginMode
|
import im.vector.app.features.login.LoginMode
|
||||||
|
@ -48,8 +48,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<FragmentLoginSigninToAny2Binding>() {
|
class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<FragmentLoginSigninToAny2Binding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninToAny2Binding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSigninToAny2Binding {
|
||||||
return FragmentLoginSigninToAny2Binding.inflate(inflater, container, false)
|
return FragmentLoginSigninToAny2Binding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +57,6 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
|
||||||
|
|
||||||
setupSubmitButton()
|
setupSubmitButton()
|
||||||
setupForgottenPasswordButton()
|
setupForgottenPasswordButton()
|
||||||
setupPasswordReveal()
|
|
||||||
setupAutoFill()
|
setupAutoFill()
|
||||||
setupSocialLoginButtons()
|
setupSocialLoginButtons()
|
||||||
|
|
||||||
|
@ -159,23 +156,6 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
|
||||||
loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen))
|
loginViewModel.handle(LoginAction2.PostViewEvent(LoginViewEvents2.OpenResetPasswordScreen))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
|
||||||
passwordShown = false
|
|
||||||
|
|
||||||
views.passwordReveal.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderPasswordField() {
|
|
||||||
views.passwordField.showPassword(passwordShown)
|
|
||||||
views.passwordReveal.render(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
// loginViewModel.handle(LoginAction2.ResetSignin)
|
// loginViewModel.handle(LoginAction2.ResetSignin)
|
||||||
}
|
}
|
||||||
|
@ -208,8 +188,7 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
// Ensure password is hidden
|
// Ensure password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,12 @@ 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.isEmail
|
import im.vector.app.core.extensions.isEmail
|
||||||
import im.vector.app.core.extensions.showPassword
|
import im.vector.app.core.extensions.hidePassword
|
||||||
import im.vector.app.core.extensions.toReducedUrl
|
import im.vector.app.core.extensions.toReducedUrl
|
||||||
import im.vector.app.core.utils.autoResetTextInputLayoutErrors
|
import im.vector.app.core.utils.autoResetTextInputLayoutErrors
|
||||||
import im.vector.app.databinding.FragmentLoginResetPassword2Binding
|
import im.vector.app.databinding.FragmentLoginResetPassword2Binding
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.rxkotlin.subscribeBy
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +41,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginResetPassword2Binding>() {
|
class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginResetPassword2Binding>() {
|
||||||
|
|
||||||
private var passwordShown = false
|
|
||||||
|
|
||||||
// Show warning only once
|
// Show warning only once
|
||||||
private var showWarning = true
|
private var showWarning = true
|
||||||
|
|
||||||
|
@ -55,7 +52,6 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
setupSubmitButton()
|
setupSubmitButton()
|
||||||
setupPasswordReveal()
|
|
||||||
setupAutoFill()
|
setupAutoFill()
|
||||||
|
|
||||||
autoResetTextInputLayoutErrors(listOf(views.resetPasswordEmailTil, views.passwordFieldTil))
|
autoResetTextInputLayoutErrors(listOf(views.resetPasswordEmailTil, views.passwordFieldTil))
|
||||||
|
@ -148,23 +144,6 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2
|
||||||
views.passwordFieldTil.error = null
|
views.passwordFieldTil.error = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPasswordReveal() {
|
|
||||||
passwordShown = false
|
|
||||||
|
|
||||||
views.passwordReveal.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPasswordField()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderPasswordField() {
|
|
||||||
views.passwordField.showPassword(passwordShown)
|
|
||||||
views.passwordReveal.render(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
loginViewModel.handle(LoginAction2.ResetResetPassword)
|
loginViewModel.handle(LoginAction2.ResetResetPassword)
|
||||||
}
|
}
|
||||||
|
@ -178,8 +157,7 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
// Ensure new password is hidden
|
// Ensure new password is hidden
|
||||||
passwordShown = false
|
views.passwordField.hidePassword()
|
||||||
renderPasswordField()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.BuildConfig
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.FirstThrottler
|
import im.vector.app.core.utils.FirstThrottler
|
||||||
|
import im.vector.app.features.invite.AutoAcceptInvites
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -50,7 +51,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
private val activeSessionDataSource: ActiveSessionDataSource,
|
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
private val iconLoader: IconLoader,
|
private val iconLoader: IconLoader,
|
||||||
private val bitmapLoader: BitmapLoader,
|
private val bitmapLoader: BitmapLoader,
|
||||||
private val outdatedDetector: OutdatedEventDetector?) {
|
private val outdatedDetector: OutdatedEventDetector?,
|
||||||
|
private val autoAcceptInvites: AutoAcceptInvites) {
|
||||||
|
|
||||||
private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
|
private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
|
||||||
private var backgroundHandler: Handler
|
private var backgroundHandler: Handler
|
||||||
|
@ -253,7 +255,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||||
roomEvents.add(event)
|
roomEvents.add(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is InviteNotifiableEvent -> invitationEvents.add(event)
|
is InviteNotifiableEvent -> {
|
||||||
|
if (autoAcceptInvites.hideInvites) {
|
||||||
|
// Forget this event
|
||||||
|
eventIterator.remove()
|
||||||
|
} else {
|
||||||
|
invitationEvents.add(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
||||||
else -> Timber.w("Type not handled")
|
else -> Timber.w("Type not handled")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package im.vector.app.features.roomprofile
|
package im.vector.app.features.roomprofile
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -33,7 +32,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.animations.AppBarStateChangeListener
|
import im.vector.app.core.animations.AppBarStateChangeListener
|
||||||
import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener
|
import im.vector.app.core.animations.MatrixItemAppBarStateChangeListener
|
||||||
import im.vector.app.core.dialogs.withColoredButton
|
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.extensions.copyOnLongClick
|
import im.vector.app.core.extensions.copyOnLongClick
|
||||||
|
@ -269,7 +267,7 @@ class RoomProfileFragment @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) { _, _ ->
|
||||||
|
@ -277,11 +275,6 @@ class RoomProfileFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
.apply {
|
|
||||||
if (!isPublicRoom) {
|
|
||||||
withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRoomAliasesClicked() {
|
override fun onRoomAliasesClicked() {
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.app.features.roomprofile.alias
|
package im.vector.app.features.roomprofile.alias
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -27,7 +26,6 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
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.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
@ -133,7 +131,7 @@ class RoomAliasFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unpublishAlias(alias: String) {
|
private fun unpublishAlias(alias: String) {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
|
||||||
.setTitle(R.string.dialog_title_confirmation)
|
.setTitle(R.string.dialog_title_confirmation)
|
||||||
.setMessage(getString(R.string.room_alias_unpublish_confirmation, alias))
|
.setMessage(getString(R.string.room_alias_unpublish_confirmation, alias))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
@ -141,7 +139,6 @@ class RoomAliasFragment @Inject constructor(
|
||||||
viewModel.handle(RoomAliasAction.UnpublishAlias(alias))
|
viewModel.handle(RoomAliasAction.UnpublishAlias(alias))
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toggleManualPublishForm() {
|
override fun toggleManualPublishForm() {
|
||||||
|
@ -185,7 +182,7 @@ class RoomAliasFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeLocalAlias(alias: String) {
|
private fun removeLocalAlias(alias: String) {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
|
||||||
.setTitle(R.string.dialog_title_confirmation)
|
.setTitle(R.string.dialog_title_confirmation)
|
||||||
.setMessage(getString(R.string.room_alias_delete_confirmation, alias))
|
.setMessage(getString(R.string.room_alias_delete_confirmation, alias))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
@ -193,6 +190,5 @@ class RoomAliasFragment @Inject constructor(
|
||||||
viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias))
|
viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias))
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
||||||
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.extensions.hidePassword
|
||||||
import im.vector.app.core.intent.getFilenameFromUri
|
import im.vector.app.core.intent.getFilenameFromUri
|
||||||
import im.vector.app.core.platform.SimpleTextWatcher
|
import im.vector.app.core.platform.SimpleTextWatcher
|
||||||
import im.vector.app.core.preference.UserAvatarPreference
|
import im.vector.app.core.preference.UserAvatarPreference
|
||||||
|
@ -347,18 +347,6 @@ class VectorSettingsGeneralFragment @Inject constructor(
|
||||||
val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup
|
val view: ViewGroup = activity.layoutInflater.inflate(R.layout.dialog_change_password, null) as ViewGroup
|
||||||
val views = DialogChangePasswordBinding.bind(view)
|
val views = DialogChangePasswordBinding.bind(view)
|
||||||
|
|
||||||
var passwordShown = false
|
|
||||||
|
|
||||||
views.changePasswordShowPasswords.setOnClickListener {
|
|
||||||
passwordShown = !passwordShown
|
|
||||||
|
|
||||||
views.changePasswordOldPwdText.showPassword(passwordShown)
|
|
||||||
views.changePasswordNewPwdText.showPassword(passwordShown)
|
|
||||||
views.changePasswordConfirmNewPwdText.showPassword(passwordShown)
|
|
||||||
|
|
||||||
views.changePasswordShowPasswords.render(passwordShown)
|
|
||||||
}
|
|
||||||
|
|
||||||
val dialog = MaterialAlertDialogBuilder(activity)
|
val dialog = MaterialAlertDialogBuilder(activity)
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
|
@ -377,13 +365,8 @@ class VectorSettingsGeneralFragment @Inject constructor(
|
||||||
fun updateUi() {
|
fun updateUi() {
|
||||||
val oldPwd = views.changePasswordOldPwdText.text.toString()
|
val oldPwd = views.changePasswordOldPwdText.text.toString()
|
||||||
val newPwd = views.changePasswordNewPwdText.text.toString()
|
val newPwd = views.changePasswordNewPwdText.text.toString()
|
||||||
val newConfirmPwd = views.changePasswordConfirmNewPwdText.text.toString()
|
|
||||||
|
|
||||||
updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd
|
updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty()
|
||||||
|
|
||||||
if (newPwd.isNotEmpty() && newConfirmPwd.isNotEmpty() && newPwd != newConfirmPwd) {
|
|
||||||
views.changePasswordConfirmNewPwdTil.error = getString(R.string.passwords_do_not_match)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
views.changePasswordOldPwdText.addTextChangedListener(object : SimpleTextWatcher() {
|
views.changePasswordOldPwdText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
|
@ -395,32 +378,20 @@ class VectorSettingsGeneralFragment @Inject constructor(
|
||||||
|
|
||||||
views.changePasswordNewPwdText.addTextChangedListener(object : SimpleTextWatcher() {
|
views.changePasswordNewPwdText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
views.changePasswordConfirmNewPwdTil.error = null
|
|
||||||
updateUi()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
views.changePasswordConfirmNewPwdText.addTextChangedListener(object : SimpleTextWatcher() {
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
views.changePasswordConfirmNewPwdTil.error = null
|
|
||||||
updateUi()
|
updateUi()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
fun showPasswordLoadingView(toShow: Boolean) {
|
fun showPasswordLoadingView(toShow: Boolean) {
|
||||||
if (toShow) {
|
if (toShow) {
|
||||||
views.changePasswordShowPasswords.isEnabled = false
|
|
||||||
views.changePasswordOldPwdText.isEnabled = false
|
views.changePasswordOldPwdText.isEnabled = false
|
||||||
views.changePasswordNewPwdText.isEnabled = false
|
views.changePasswordNewPwdText.isEnabled = false
|
||||||
views.changePasswordConfirmNewPwdText.isEnabled = false
|
|
||||||
views.changePasswordLoader.isVisible = true
|
views.changePasswordLoader.isVisible = true
|
||||||
updateButton.isEnabled = false
|
updateButton.isEnabled = false
|
||||||
cancelButton.isEnabled = false
|
cancelButton.isEnabled = false
|
||||||
} else {
|
} else {
|
||||||
views.changePasswordShowPasswords.isEnabled = true
|
|
||||||
views.changePasswordOldPwdText.isEnabled = true
|
views.changePasswordOldPwdText.isEnabled = true
|
||||||
views.changePasswordNewPwdText.isEnabled = true
|
views.changePasswordNewPwdText.isEnabled = true
|
||||||
views.changePasswordConfirmNewPwdText.isEnabled = true
|
|
||||||
views.changePasswordLoader.isVisible = false
|
views.changePasswordLoader.isVisible = false
|
||||||
updateButton.isEnabled = true
|
updateButton.isEnabled = true
|
||||||
cancelButton.isEnabled = true
|
cancelButton.isEnabled = true
|
||||||
|
@ -428,10 +399,9 @@ class VectorSettingsGeneralFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
updateButton.setOnClickListener {
|
updateButton.setOnClickListener {
|
||||||
if (passwordShown) {
|
|
||||||
// Hide passwords during processing
|
// Hide passwords during processing
|
||||||
views.changePasswordShowPasswords.performClick()
|
views.changePasswordOldPwdText.hidePassword()
|
||||||
}
|
views.changePasswordNewPwdText.hidePassword()
|
||||||
|
|
||||||
view.hideKeyboard()
|
view.hideKeyboard()
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.dialogs.ExportKeysDialog
|
import im.vector.app.core.dialogs.ExportKeysDialog
|
||||||
import im.vector.app.core.extensions.queryExportKeys
|
import im.vector.app.core.extensions.queryExportKeys
|
||||||
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.intent.ExternalIntentData
|
import im.vector.app.core.intent.ExternalIntentData
|
||||||
import im.vector.app.core.intent.analyseIntent
|
import im.vector.app.core.intent.analyseIntent
|
||||||
import im.vector.app.core.intent.getFilenameFromUri
|
import im.vector.app.core.intent.getFilenameFromUri
|
||||||
|
@ -451,14 +450,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||||
.setTitle(R.string.encryption_import_room_keys)
|
.setTitle(R.string.encryption_import_room_keys)
|
||||||
.setView(dialogLayout)
|
.setView(dialogLayout)
|
||||||
|
|
||||||
var passwordVisible = false
|
|
||||||
|
|
||||||
views.importDialogShowPassword.setOnClickListener {
|
|
||||||
passwordVisible = !passwordVisible
|
|
||||||
views.dialogE2eKeysPassphraseEditText.showPassword(passwordVisible)
|
|
||||||
views.importDialogShowPassword.render(passwordVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
views.dialogE2eKeysPassphraseEditText.addTextChangedListener(object : SimpleTextWatcher() {
|
views.dialogE2eKeysPassphraseEditText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
views.dialogE2eKeysImportButton.isEnabled = !views.dialogE2eKeysPassphraseEditText.text.isNullOrEmpty()
|
views.dialogE2eKeysImportButton.isEnabled = !views.dialogE2eKeysPassphraseEditText.text.isNullOrEmpty()
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.app.features.settings.account.deactivation
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
|
|
||||||
sealed class DeactivateAccountAction : VectorViewModelAction {
|
sealed class DeactivateAccountAction : VectorViewModelAction {
|
||||||
object TogglePassword : DeactivateAccountAction()
|
|
||||||
data class DeactivateAccount(val eraseAllData: Boolean) : DeactivateAccountAction()
|
data class DeactivateAccount(val eraseAllData: Boolean) : DeactivateAccountAction()
|
||||||
|
|
||||||
object SsoAuthDone: DeactivateAccountAction()
|
object SsoAuthDone: DeactivateAccountAction()
|
||||||
|
|
|
@ -41,7 +41,7 @@ import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
data class DeactivateAccountViewState(
|
data class DeactivateAccountViewState(
|
||||||
val passwordShown: Boolean = false
|
val dummy: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
||||||
class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private val initialState: DeactivateAccountViewState,
|
class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private val initialState: DeactivateAccountViewState,
|
||||||
|
@ -58,7 +58,6 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
|
||||||
|
|
||||||
override fun handle(action: DeactivateAccountAction) {
|
override fun handle(action: DeactivateAccountAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
DeactivateAccountAction.TogglePassword -> handleTogglePassword()
|
|
||||||
is DeactivateAccountAction.DeactivateAccount -> handleDeactivateAccount(action)
|
is DeactivateAccountAction.DeactivateAccount -> handleDeactivateAccount(action)
|
||||||
DeactivateAccountAction.SsoAuthDone -> {
|
DeactivateAccountAction.SsoAuthDone -> {
|
||||||
Timber.d("## UIA - FallBack success")
|
Timber.d("## UIA - FallBack success")
|
||||||
|
@ -87,12 +86,6 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleTogglePassword() = withState {
|
|
||||||
setState {
|
|
||||||
copy(passwordShown = !passwordShown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleDeactivateAccount(action: DeactivateAccountAction.DeactivateAccount) {
|
private fun handleDeactivateAccount(action: DeactivateAccountAction.DeactivateAccount) {
|
||||||
_viewEvents.post(DeactivateAccountViewEvents.Loading())
|
_viewEvents.post(DeactivateAccountViewEvents.Loading())
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package im.vector.app.features.settings.devtools
|
package im.vector.app.features.settings.devtools
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -26,7 +25,6 @@ import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
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.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
import im.vector.app.core.extensions.configureWith
|
import im.vector.app.core.extensions.configureWith
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
@ -85,7 +83,7 @@ class AccountDataFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun didLongTap(data: UserAccountDataEvent) {
|
override fun didLongTap(data: UserAccountDataEvent) {
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive)
|
||||||
.setTitle(R.string.delete)
|
.setTitle(R.string.delete)
|
||||||
.setMessage(getString(R.string.delete_account_data_warning, data.type))
|
.setMessage(getString(R.string.delete_account_data_warning, data.type))
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
@ -93,6 +91,5 @@ class AccountDataFragment @Inject constructor(
|
||||||
viewModel.handle(AccountDataAction.DeleteAccountData(data.type))
|
viewModel.handle(AccountDataAction.DeleteAccountData(data.type))
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue