mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 02:45:53 +03:00
Merge branch 'develop' into feature/aris/threads
# Conflicts: # matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt # matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt # vector/src/main/java/im/vector/app/features/command/Command.kt # vector/src/main/java/im/vector/app/features/command/CommandParser.kt # vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt
This commit is contained in:
commit
636474b748
169 changed files with 1327 additions and 1014 deletions
|
@ -61,8 +61,9 @@ Supported filename extensions are:
|
||||||
|
|
||||||
- ``.feature``: Signifying a new feature in Element Android or in the Matrix SDK.
|
- ``.feature``: Signifying a new feature in Element Android or in the Matrix SDK.
|
||||||
- ``.bugfix``: Signifying a bug fix.
|
- ``.bugfix``: Signifying a bug fix.
|
||||||
|
- ``.wip``: Signifying a work in progress change, typically a component of a larger feature which will be enabled once all tasks are complete.
|
||||||
- ``.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
|
- ``.sdk``: Signifying a change to the Matrix SDK, this could be an addition, deprecation or removal of a public API.
|
||||||
- ``.misc``: Any other changes.
|
- ``.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.
|
||||||
|
|
|
@ -47,12 +47,10 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":library:ui-styles")
|
implementation project(":library:ui-styles")
|
||||||
|
implementation project(":library:core-utils")
|
||||||
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
|
|
||||||
implementation libs.rx.rxKotlin
|
|
||||||
implementation libs.rx.rxAndroid
|
|
||||||
|
|
||||||
implementation libs.androidx.core
|
implementation libs.androidx.core
|
||||||
implementation libs.androidx.appCompat
|
implementation libs.androidx.appCompat
|
||||||
implementation libs.androidx.recyclerview
|
implementation libs.androidx.recyclerview
|
||||||
|
|
|
@ -20,12 +20,9 @@ import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import im.vector.lib.attachmentviewer.databinding.ItemVideoAttachmentBinding
|
import im.vector.lib.attachmentviewer.databinding.ItemVideoAttachmentBinding
|
||||||
import io.reactivex.Observable
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.disposables.Disposable
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
// TODO, it would be probably better to use a unique media player
|
// TODO, it would be probably better to use a unique media player
|
||||||
// for better customization and control
|
// for better customization and control
|
||||||
|
@ -35,7 +32,7 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
|
|
||||||
private var isSelected = false
|
private var isSelected = false
|
||||||
private var mVideoPath: String? = null
|
private var mVideoPath: String? = null
|
||||||
private var progressDisposable: Disposable? = null
|
private var countUpTimer: CountUpTimer? = null
|
||||||
private var progress: Int = 0
|
private var progress: Int = 0
|
||||||
private var wasPaused = false
|
private var wasPaused = false
|
||||||
|
|
||||||
|
@ -47,8 +44,7 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
|
|
||||||
override fun onRecycled() {
|
override fun onRecycled() {
|
||||||
super.onRecycled()
|
super.onRecycled()
|
||||||
progressDisposable?.dispose()
|
stopTimer()
|
||||||
progressDisposable = null
|
|
||||||
mVideoPath = null
|
mVideoPath = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,8 +68,7 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
override fun entersBackground() {
|
override fun entersBackground() {
|
||||||
if (views.videoView.isPlaying) {
|
if (views.videoView.isPlaying) {
|
||||||
progress = views.videoView.currentPosition
|
progress = views.videoView.currentPosition
|
||||||
progressDisposable?.dispose()
|
stopTimer()
|
||||||
progressDisposable = null
|
|
||||||
views.videoView.stopPlayback()
|
views.videoView.stopPlayback()
|
||||||
views.videoView.pause()
|
views.videoView.pause()
|
||||||
}
|
}
|
||||||
|
@ -91,8 +86,7 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
} else {
|
} else {
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
progressDisposable?.dispose()
|
stopTimer()
|
||||||
progressDisposable = null
|
|
||||||
} else {
|
} else {
|
||||||
if (mVideoPath != null) {
|
if (mVideoPath != null) {
|
||||||
startPlaying()
|
startPlaying()
|
||||||
|
@ -107,17 +101,19 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
views.videoView.isVisible = true
|
views.videoView.isVisible = true
|
||||||
|
|
||||||
views.videoView.setOnPreparedListener {
|
views.videoView.setOnPreparedListener {
|
||||||
progressDisposable?.dispose()
|
stopTimer()
|
||||||
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
|
countUpTimer = CountUpTimer(100).also {
|
||||||
.timeInterval()
|
it.tickListener = object : CountUpTimer.TickListener {
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
override fun onTick(milliseconds: Long) {
|
||||||
.subscribe {
|
|
||||||
val duration = views.videoView.duration
|
val duration = views.videoView.duration
|
||||||
val progress = views.videoView.currentPosition
|
val progress = views.videoView.currentPosition
|
||||||
val isPlaying = views.videoView.isPlaying
|
val isPlaying = views.videoView.isPlaying
|
||||||
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
|
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
|
||||||
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
it.resume()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
views.videoView.setVideoPath(mVideoPath)
|
views.videoView.setVideoPath(mVideoPath)
|
||||||
|
@ -134,6 +130,11 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun stopTimer() {
|
||||||
|
countUpTimer?.stop()
|
||||||
|
countUpTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun handleCommand(commands: AttachmentCommands) {
|
override fun handleCommand(commands: AttachmentCommands) {
|
||||||
if (!isSelected) return
|
if (!isSelected) return
|
||||||
when (commands) {
|
when (commands) {
|
||||||
|
|
10
build.gradle
10
build.gradle
|
@ -158,13 +158,3 @@ ext {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
//
|
|
||||||
//project(":matrix-sdk-android-rx") {
|
|
||||||
// sonarqube {
|
|
||||||
// properties {
|
|
||||||
// property "sonar.sources", project(":matrix-sdk-android-rx").android.sourceSets.main.java.srcDirs
|
|
||||||
// // exclude source code from analyses separated by a colon (:)
|
|
||||||
// // property "sonar.exclusions", "**/*.*"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
1
changelog.d/3932.bugfix
Normal file
1
changelog.d/3932.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Explore Rooms overflow menu - content update include "Create room"
|
1
changelog.d/4669.bugfix
Normal file
1
changelog.d/4669.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix sync timeout after returning from background
|
1
changelog.d/4811.feature
Normal file
1
changelog.d/4811.feature
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Enabling native support for window resizing
|
1
changelog.d/4865.misc
Normal file
1
changelog.d/4865.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"/kick" command is replaced with "/remove". Also replaced all occurrences in string resources
|
1
changelog.d/4880.wip
Normal file
1
changelog.d/4880.wip
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Updates the onboarding carousel images, copy and improves the handling of different device sizes
|
1
changelog.d/4895.removal
Normal file
1
changelog.d/4895.removal
Normal file
|
@ -0,0 +1 @@
|
||||||
|
`StateService.sendStateEvent()` now takes a non-nullable String for the parameter `stateKey`. If null was used, just now use an empty string.
|
1
changelog.d/4914.wip
Normal file
1
changelog.d/4914.wip
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Disabling onboarding automatic carousel transitions on user interaction
|
1
changelog.d/4918.wip
Normal file
1
changelog.d/4918.wip
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Locking phones to portrait during the FTUE onboarding
|
1
changelog.d/4927.wip
Normal file
1
changelog.d/4927.wip
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Adds a messaging use case screen to the FTUE onboarding
|
1
changelog.d/4935.bugfix
Normal file
1
changelog.d/4935.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix a wrong network error issue in the Legals screen
|
1
changelog.d/4942.misc
Normal file
1
changelog.d/4942.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Remove unused module matrix-sdk-android-rx and do some cleanup
|
1
changelog.d/4948.bugfix
Normal file
1
changelog.d/4948.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Prevent Alerts to be displayed in the automatically displayed analytics opt-in screen
|
1
changelog.d/4960.misc
Normal file
1
changelog.d/4960.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Improves local echo blinking when non room events received
|
1
changelog.d/516.bugfix
Normal file
1
changelog.d/516.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix for stuck local event messages at the bottom of the screen
|
|
@ -42,7 +42,6 @@ ext.libs = [
|
||||||
jetbrains : [
|
jetbrains : [
|
||||||
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
|
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
|
||||||
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
|
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
|
||||||
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines",
|
|
||||||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||||
],
|
],
|
||||||
androidx : [
|
androidx : [
|
||||||
|
@ -87,8 +86,7 @@ ext.libs = [
|
||||||
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"
|
'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit"
|
||||||
],
|
],
|
||||||
rx : [
|
rx : [
|
||||||
'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0",
|
'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0"
|
||||||
'rxAndroid' : "io.reactivex.rxjava2:rxandroid:2.1.1"
|
|
||||||
],
|
],
|
||||||
arrow : [
|
arrow : [
|
||||||
'core' : "io.arrow-kt:arrow-core:$arrow",
|
'core' : "io.arrow-kt:arrow-core:$arrow",
|
||||||
|
|
1
library/core-utils/.gitignore
vendored
Normal file
1
library/core-utils/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
55
library/core-utils/build.gradle
Normal file
55
library/core-utils/build.gradle
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
id 'kotlin-android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk versions.compileSdk
|
||||||
|
defaultConfig {
|
||||||
|
minSdk versions.minSdk
|
||||||
|
targetSdk versions.targetSdk
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
consumerProguardFiles "consumer-rules.pro"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility versions.sourceCompat
|
||||||
|
targetCompatibility versions.targetCompat
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
freeCompilerArgs += [
|
||||||
|
"-Xopt-in=kotlin.RequiresOptIn"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation libs.androidx.appCompat
|
||||||
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
|
}
|
2
library/core-utils/src/main/AndroidManifest.xml
Normal file
2
library/core-utils/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="im.vector.lib.core.utils" />
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.core.flow
|
package im.vector.lib.core.utils.flow
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -85,10 +85,12 @@ fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow<Unit> {
|
fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow<Unit> {
|
||||||
return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow()
|
return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
private fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel<Unit> {
|
private fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel<Unit> {
|
||||||
require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
|
require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
|
||||||
require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
|
require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
|
|
@ -14,9 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.core.utils
|
package im.vector.lib.core.utils.timer
|
||||||
|
|
||||||
import im.vector.app.core.flow.tickerFlow
|
import im.vector.lib.core.utils.flow.tickerFlow
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.onEach
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
|
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||||
class CountUpTimer(private val intervalInMs: Long = 1_000) {
|
class CountUpTimer(private val intervalInMs: Long = 1_000) {
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="?vctr_system"
|
||||||
|
android:startColor="#000000" />
|
||||||
|
</shape>
|
5
library/ui-styles/src/main/res/values-h720dp/dimens.xml
Normal file
5
library/ui-styles/src/main/res/values-h720dp/dimens.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<item name="ftue_auth_carousel_item_spacing" format="float" type="dimen">0.05</item>
|
||||||
|
<item name="ftue_auth_carousel_item_image_height" format="float" type="dimen">0.40</item>
|
||||||
|
</resources>
|
|
@ -2,5 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<dimen name="width_percent">0.6</dimen>
|
<dimen name="width_percent">0.6</dimen>
|
||||||
|
<bool name="is_tablet">true</bool>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -55,4 +55,7 @@
|
||||||
<!-- Onboarding -->
|
<!-- Onboarding -->
|
||||||
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
|
<item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item>
|
||||||
<item name="ftue_auth_gutter_end_percent" format="float" type="dimen">0.95</item>
|
<item name="ftue_auth_gutter_end_percent" format="float" type="dimen">0.95</item>
|
||||||
|
|
||||||
|
<item name="ftue_auth_carousel_item_spacing" format="float" type="dimen">0.01</item>
|
||||||
|
<item name="ftue_auth_carousel_item_image_height" format="float" type="dimen">0.35</item>
|
||||||
</resources>
|
</resources>
|
|
@ -2,5 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<dimen name="width_percent">1</dimen>
|
<dimen name="width_percent">1</dimen>
|
||||||
|
<bool name="is_tablet">false</bool>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
1
matrix-sdk-android-rx/.gitignore
vendored
1
matrix-sdk-android-rx/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
/build
|
|
|
@ -1,47 +0,0 @@
|
||||||
apply plugin: 'com.android.library'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply plugin: 'kotlin-kapt'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdk versions.compileSdk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk versions.minSdk
|
|
||||||
targetSdk versions.targetSdk
|
|
||||||
|
|
||||||
// Multidex is useful for tests
|
|
||||||
multiDexEnabled true
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility versions.sourceCompat
|
|
||||||
targetCompatibility versions.targetCompat
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "11"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
|
||||||
implementation libs.androidx.appCompat
|
|
||||||
implementation libs.rx.rxKotlin
|
|
||||||
implementation libs.rx.rxAndroid
|
|
||||||
implementation libs.jetbrains.coroutinesRx2
|
|
||||||
|
|
||||||
// Paging
|
|
||||||
implementation libs.androidx.pagingRuntimeKtx
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
implementation libs.jakewharton.timber
|
|
||||||
}
|
|
21
matrix-sdk-android-rx/proguard-rules.pro
vendored
21
matrix-sdk-android-rx/proguard-rules.pro
vendored
|
@ -1,21 +0,0 @@
|
||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
|
@ -1 +0,0 @@
|
||||||
<manifest package="org.matrix.android.sdk.rx" />
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.rx
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.Observer
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.android.MainThreadDisposable
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.schedulers.Schedulers
|
|
||||||
|
|
||||||
private class LiveDataObservable<T>(
|
|
||||||
private val liveData: LiveData<T>,
|
|
||||||
private val valueIfNull: T? = null
|
|
||||||
) : Observable<T>() {
|
|
||||||
|
|
||||||
override fun subscribeActual(observer: io.reactivex.Observer<in T>) {
|
|
||||||
val relay = RemoveObserverInMainThread(observer)
|
|
||||||
observer.onSubscribe(relay)
|
|
||||||
liveData.observeForever(relay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class RemoveObserverInMainThread(private val observer: io.reactivex.Observer<in T>) :
|
|
||||||
MainThreadDisposable(), Observer<T> {
|
|
||||||
|
|
||||||
override fun onChanged(t: T?) {
|
|
||||||
if (!isDisposed) {
|
|
||||||
if (t == null) {
|
|
||||||
if (valueIfNull != null) {
|
|
||||||
observer.onNext(valueIfNull)
|
|
||||||
} else {
|
|
||||||
observer.onError(NullPointerException(
|
|
||||||
"convert liveData value t to RxJava onNext(t), t cannot be null"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
observer.onNext(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDispose() {
|
|
||||||
liveData.removeObserver(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
|
||||||
return LiveDataObservable(this).observeOn(Schedulers.computation())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <T> Observable<T>.startWithCallable(supplier: () -> T): Observable<T> {
|
|
||||||
val startObservable = Observable
|
|
||||||
.fromCallable(supplier)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
return startWith(startObservable)
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.rx
|
|
||||||
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
|
||||||
|
|
||||||
fun <T : Any> Observable<Optional<T>>.unwrap(): Observable<T> {
|
|
||||||
return filter { it.hasValue() }.map { it.get() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any, U : Any> Observable<Optional<T>>.mapOptional(fn: (T) -> U?): Observable<Optional<U>> {
|
|
||||||
return map {
|
|
||||||
it.map(fn)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.rx
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import io.reactivex.Completable
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.Single
|
|
||||||
import kotlinx.coroutines.rx2.rxCompletable
|
|
||||||
import kotlinx.coroutines.rx2.rxSingle
|
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
|
||||||
import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
|
||||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
|
||||||
return room.getRoomSummaryLive()
|
|
||||||
.asObservable()
|
|
||||||
.startWithCallable { room.roomSummary().toOptional() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
|
||||||
return room.getRoomMembersLive(queryParams).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
room.getRoomMembers(queryParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
|
||||||
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
room.getEventAnnotationsSummary(eventId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
|
||||||
return room.getTimeLineEventLive(eventId).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
room.getTimeLineEvent(eventId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Observable<Optional<Event>> {
|
|
||||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
room.getStateEvent(eventType, stateKey).toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveStateEvents(eventTypes: Set<String>): Observable<List<Event>> {
|
|
||||||
return room.getStateEventsLive(eventTypes).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
room.getStateEvents(eventTypes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveReadMarker(): Observable<Optional<String>> {
|
|
||||||
return room.getReadMarkerLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveReadReceipt(): Observable<Optional<String>> {
|
|
||||||
return room.getMyReadReceiptLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadRoomMembersIfNeeded(): Single<Unit> = rxSingle {
|
|
||||||
room.loadRoomMembersIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun joinRoom(reason: String? = null,
|
|
||||||
viaServers: List<String> = emptyList()): Single<Unit> = rxSingle {
|
|
||||||
room.join(reason, viaServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
|
||||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveDraft(): Observable<Optional<UserDraft>> {
|
|
||||||
return room.getDraftLive().asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
room.getDraft().toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
|
||||||
return room.getLiveRoomNotificationState().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invite(userId: String, reason: String? = null): Completable = rxCompletable {
|
|
||||||
room.invite(userId, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invite3pid(threePid: ThreePid): Completable = rxCompletable {
|
|
||||||
room.invite3pid(threePid)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTopic(topic: String): Completable = rxCompletable {
|
|
||||||
room.updateTopic(topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateName(name: String): Completable = rxCompletable {
|
|
||||||
room.updateName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = rxCompletable {
|
|
||||||
room.updateHistoryReadability(readability)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = rxCompletable {
|
|
||||||
room.updateJoinRule(joinRules, guestAccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = rxCompletable {
|
|
||||||
room.updateAvatar(avatarUri, fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteAvatar(): Completable = rxCompletable {
|
|
||||||
room.deleteAvatar()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendMedia(attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set<String>): Completable = rxCompletable {
|
|
||||||
room.sendMedia(
|
|
||||||
attachment = attachment,
|
|
||||||
compressBeforeSending = compressBeforeSending,
|
|
||||||
roomIds = roomIds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
|
||||||
return RxRoom(this)
|
|
||||||
}
|
|
|
@ -1,251 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.rx
|
|
||||||
|
|
||||||
import androidx.paging.PagedList
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.Single
|
|
||||||
import io.reactivex.functions.Function3
|
|
||||||
import kotlinx.coroutines.rx2.rxSingle
|
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
|
||||||
import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
|
|
||||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
|
||||||
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
|
||||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
|
||||||
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
|
||||||
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
|
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
|
||||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
|
||||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.getRoomSummaries(queryParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
|
||||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.getGroupSummaries(queryParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable<List<RoomSummary>> {
|
|
||||||
return session.spaceService().getSpaceSummariesLive(queryParams).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.spaceService().getSpaceSummaries(queryParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
|
||||||
return session.getBreadcrumbsLive(queryParams).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.getBreadcrumbs(queryParams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveMyDevicesInfo(): Observable<List<DeviceInfo>> {
|
|
||||||
return session.cryptoService().getLiveMyDevicesInfo().asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.cryptoService().getMyDevicesInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
|
||||||
return session.getSyncStateLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun livePushers(): Observable<List<Pusher>> {
|
|
||||||
return session.getPushersLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
|
||||||
return session.getUserLive(userId).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.getUser(userId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveRoomMember(userId: String, roomId: String): Observable<Optional<RoomMemberSummary>> {
|
|
||||||
return session.getRoomMemberLive(userId, roomId).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.getRoomMember(userId, roomId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveUsers(): Observable<List<User>> {
|
|
||||||
return session.getUsersLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveIgnoredUsers(): Observable<List<User>> {
|
|
||||||
return session.getIgnoredUsersLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun livePagedUsers(filter: String? = null, excludedUserIds: Set<String>? = null): Observable<PagedList<User>> {
|
|
||||||
return session.getPagedUsersLive(filter, excludedUserIds).asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveThreePIds(refreshData: Boolean): Observable<List<ThreePid>> {
|
|
||||||
return session.getThreePidsLive(refreshData).asObservable()
|
|
||||||
.startWithCallable { session.getThreePids() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun livePendingThreePIds(): Observable<List<ThreePid>> {
|
|
||||||
return session.getPendingThreePidsLive().asObservable()
|
|
||||||
.startWithCallable { session.getPendingThreePids() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = rxSingle {
|
|
||||||
session.createRoom(roomParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchUsersDirectory(search: String,
|
|
||||||
limit: Int,
|
|
||||||
excludedUserIds: Set<String>): Single<List<User>> = rxSingle {
|
|
||||||
session.searchUsersDirectory(search, limit, excludedUserIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun joinRoom(roomIdOrAlias: String,
|
|
||||||
reason: String? = null,
|
|
||||||
viaServers: List<String> = emptyList()): Single<Unit> = rxSingle {
|
|
||||||
session.joinRoom(roomIdOrAlias, reason, viaServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getRoomIdByAlias(roomAlias: String,
|
|
||||||
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = rxSingle {
|
|
||||||
session.getRoomIdByAlias(roomAlias, searchOnServer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getProfileInfo(userId: String): Single<JsonDict> = rxSingle {
|
|
||||||
session.getProfile(userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
|
|
||||||
return session.cryptoService().getLiveCryptoDeviceInfo(userId).asObservable().startWithCallable {
|
|
||||||
session.cryptoService().getCryptoDeviceInfo(userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
|
|
||||||
return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveCrossSigningPrivateKeys(): Observable<Optional<PrivateKeysInfo>> {
|
|
||||||
return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveUserAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
|
||||||
return session.accountDataService().getLiveUserAccountDataEvents(types).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.accountDataService().getUserAccountDataEvents(types)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveRoomAccountData(types: Set<String>): Observable<List<RoomAccountDataEvent>> {
|
|
||||||
return session.accountDataService().getLiveRoomAccountDataEvents(types).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.accountDataService().getRoomAccountDataEvents(types)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveRoomWidgets(
|
|
||||||
roomId: String,
|
|
||||||
widgetId: QueryStringValue,
|
|
||||||
widgetTypes: Set<String>? = null,
|
|
||||||
excludedTypes: Set<String>? = null
|
|
||||||
): Observable<List<Widget>> {
|
|
||||||
return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asObservable()
|
|
||||||
.startWithCallable {
|
|
||||||
session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveRoomChangeMembershipState(): Observable<Map<String, ChangeMembershipState>> {
|
|
||||||
return session.getChangeMembershipsLive().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
|
|
||||||
return Observable.combineLatest<List<UserAccountDataEvent>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
|
||||||
liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
|
|
||||||
liveCrossSigningInfo(session.myUserId),
|
|
||||||
liveCrossSigningPrivateKeys(),
|
|
||||||
Function3 { _, crossSigningInfo, pInfo ->
|
|
||||||
// first check if 4S is already setup
|
|
||||||
val is4SSetup = session.sharedSecretStorageService.isRecoverySetup()
|
|
||||||
val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null
|
|
||||||
val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true
|
|
||||||
val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse()
|
|
||||||
|
|
||||||
val keysBackupService = session.cryptoService().keysBackupService()
|
|
||||||
val currentBackupVersion = keysBackupService.currentBackupVersion
|
|
||||||
val megolmBackupAvailable = currentBackupVersion != null
|
|
||||||
val savedBackupKey = keysBackupService.getKeyBackupRecoveryKeyInfo()
|
|
||||||
|
|
||||||
val megolmKeyKnown = savedBackupKey?.version == currentBackupVersion
|
|
||||||
SecretsSynchronisationInfo(
|
|
||||||
isBackupSetup = is4SSetup,
|
|
||||||
isCrossSigningEnabled = isCrossSigningEnabled,
|
|
||||||
isCrossSigningTrusted = isCrossSigningTrusted,
|
|
||||||
allPrivateKeysKnown = allPrivateKeysKnown,
|
|
||||||
megolmBackupAvailable = megolmBackupAvailable,
|
|
||||||
megolmSecretKnown = megolmKeyKnown,
|
|
||||||
isMegolmKeyIn4S = session.sharedSecretStorageService.isMegolmKeyInBackup()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lookupThreePid(threePid: ThreePid): Single<Optional<FoundThreePid>> = rxSingle {
|
|
||||||
session.identityService().lookUp(listOf(threePid)).firstOrNull().toOptional()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Session.rx(): RxSession {
|
|
||||||
return RxSession(this)
|
|
||||||
}
|
|
|
@ -118,6 +118,11 @@ dependencies {
|
||||||
implementation libs.squareup.retrofit
|
implementation libs.squareup.retrofit
|
||||||
implementation libs.squareup.retrofitMoshi
|
implementation libs.squareup.retrofitMoshi
|
||||||
|
|
||||||
|
// When version of okhttp is updated (current is 4.9.3), consider removing the workaround
|
||||||
|
// to force usage of Protocol.HTTP_1_1. Check the status of:
|
||||||
|
// - https://github.com/square/okhttp/issues/3278
|
||||||
|
// - https://github.com/square/okhttp/issues/4455
|
||||||
|
// - https://github.com/square/okhttp/issues/3146
|
||||||
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
|
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3"))
|
||||||
implementation 'com.squareup.okhttp3:okhttp'
|
implementation 'com.squareup.okhttp3:okhttp'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor'
|
implementation 'com.squareup.okhttp3:logging-interceptor'
|
||||||
|
|
|
@ -62,7 +62,7 @@ class EncryptionTest : InstrumentedTest {
|
||||||
// Send an encryption Event as a State Event
|
// Send an encryption Event as a State Event
|
||||||
room.sendStateEvent(
|
room.sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||||
stateKey = null,
|
stateKey = "",
|
||||||
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -550,7 +550,7 @@ class SpaceHierarchyTest : InstrumentedTest {
|
||||||
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
|
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
|
|
||||||
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
|
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
|
||||||
it.countDown()
|
it.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,9 +75,12 @@ interface MembershipService {
|
||||||
suspend fun unban(userId: String, reason: String? = null)
|
suspend fun unban(userId: String, reason: String? = null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kick a user from the room
|
* Remove a user from the room
|
||||||
*/
|
*/
|
||||||
suspend fun kick(userId: String, reason: String? = null)
|
suspend fun remove(userId: String, reason: String? = null)
|
||||||
|
|
||||||
|
@Deprecated("Use remove instead", ReplaceWith("remove(userId, reason)"))
|
||||||
|
suspend fun kick(userId: String, reason: String? = null) = remove(userId, reason)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join the room, or accept an invitation.
|
* Join the room, or accept an invitation.
|
||||||
|
|
|
@ -68,8 +68,11 @@ interface StateService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a state event to the room
|
* Send a state event to the room
|
||||||
|
* @param eventType The type of event to send.
|
||||||
|
* @param stateKey The state_key for the state to send. Can be an empty string.
|
||||||
|
* @param body The content object of the event; the fields in this object will vary depending on the type of event
|
||||||
*/
|
*/
|
||||||
suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict)
|
suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a state event of the room
|
* Get a state event of the room
|
||||||
|
|
|
@ -22,6 +22,7 @@ import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import okhttp3.ConnectionSpec
|
import okhttp3.ConnectionSpec
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Protocol
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.matrix.android.sdk.BuildConfig
|
import org.matrix.android.sdk.BuildConfig
|
||||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
|
@ -71,6 +72,8 @@ internal object NetworkModule {
|
||||||
val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build()
|
val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build()
|
||||||
|
|
||||||
return OkHttpClient.Builder()
|
return OkHttpClient.Builder()
|
||||||
|
// workaround for #4669
|
||||||
|
.protocols(listOf(Protocol.HTTP_1_1))
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(60, TimeUnit.SECONDS)
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
.writeTimeout(60, TimeUnit.SECONDS)
|
.writeTimeout(60, TimeUnit.SECONDS)
|
||||||
|
|
|
@ -130,7 +130,7 @@ internal class DefaultRoom(override val roomId: String,
|
||||||
else -> {
|
else -> {
|
||||||
val params = SendStateTask.Params(
|
val params = SendStateTask.Params(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
stateKey = null,
|
stateKey = "",
|
||||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||||
body = mapOf(
|
body = mapOf(
|
||||||
"algorithm" to algorithm
|
"algorithm" to algorithm
|
||||||
|
|
|
@ -125,7 +125,7 @@ internal class DefaultMembershipService @AssistedInject constructor(
|
||||||
membershipAdminTask.execute(params)
|
membershipAdminTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun kick(userId: String, reason: String?) {
|
override suspend fun remove(userId: String, reason: String?) {
|
||||||
val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason)
|
val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason)
|
||||||
membershipAdminTask.execute(params)
|
membershipAdminTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
|
|
||||||
override suspend fun sendStateEvent(
|
override suspend fun sendStateEvent(
|
||||||
eventType: String,
|
eventType: String,
|
||||||
stateKey: String?,
|
stateKey: String,
|
||||||
body: JsonDict
|
body: JsonDict
|
||||||
) {
|
) {
|
||||||
val params = SendStateTask.Params(
|
val params = SendStateTask.Params(
|
||||||
|
@ -92,7 +92,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_TOPIC,
|
eventType = EventType.STATE_ROOM_TOPIC,
|
||||||
body = mapOf("topic" to topic),
|
body = mapOf("topic" to topic),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_NAME,
|
eventType = EventType.STATE_ROOM_NAME,
|
||||||
body = mapOf("name" to name),
|
body = mapOf("name" to name),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
// Sort for the cleanup
|
// Sort for the cleanup
|
||||||
.sorted()
|
.sorted()
|
||||||
).toContent(),
|
).toContent(),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
body = mapOf("history_visibility" to readability),
|
body = mapOf("history_visibility" to readability),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,14 +142,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
eventType = EventType.STATE_ROOM_JOIN_RULES,
|
||||||
body = body,
|
body = body,
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (guestAccess != null) {
|
if (guestAccess != null) {
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_GUEST_ACCESS,
|
eventType = EventType.STATE_ROOM_GUEST_ACCESS,
|
||||||
body = mapOf("guest_access" to guestAccess),
|
body = mapOf("guest_access" to guestAccess),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_AVATAR,
|
eventType = EventType.STATE_ROOM_AVATAR,
|
||||||
body = mapOf("url" to response.contentUri),
|
body = mapOf("url" to response.contentUri),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
|
||||||
sendStateEvent(
|
sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_AVATAR,
|
eventType = EventType.STATE_ROOM_AVATAR,
|
||||||
body = emptyMap(),
|
body = emptyMap(),
|
||||||
stateKey = null
|
stateKey = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import javax.inject.Inject
|
||||||
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
|
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val stateKey: String?,
|
val stateKey: String,
|
||||||
val eventType: String,
|
val eventType: String,
|
||||||
val body: JsonDict
|
val body: JsonDict
|
||||||
)
|
)
|
||||||
|
@ -39,7 +39,7 @@ internal class DefaultSendStateTask @Inject constructor(
|
||||||
|
|
||||||
override suspend fun execute(params: SendStateTask.Params) {
|
override suspend fun execute(params: SendStateTask.Params) {
|
||||||
return executeRequest(globalErrorReceiver) {
|
return executeRequest(globalErrorReceiver) {
|
||||||
if (params.stateKey == null) {
|
if (params.stateKey.isEmpty()) {
|
||||||
roomAPI.sendStateEvent(
|
roomAPI.sendStateEvent(
|
||||||
roomId = params.roomId,
|
roomId = params.roomId,
|
||||||
stateEventType = params.eventType,
|
stateEventType = params.eventType,
|
||||||
|
|
|
@ -440,7 +440,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Handle deletion of [stuck] local echos if needed
|
||||||
|
deleteLocalEchosIfNeeded(insertType, roomEntity, eventList)
|
||||||
optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(
|
optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
realm = realm,
|
realm = realm,
|
||||||
|
@ -448,7 +449,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
|
|
||||||
// posting new events to timeline if any is registered
|
// posting new events to timeline if any is registered
|
||||||
timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds)
|
timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds)
|
||||||
|
|
||||||
return chunkEntity
|
return chunkEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,4 +499,49 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There are multiple issues like #516 that report stuck local echo events
|
||||||
|
* at the bottom of each room timeline.
|
||||||
|
*
|
||||||
|
* That can happen when a message is SENT but not received back from the /sync.
|
||||||
|
* Until now we use unsignedData.transactionId to determine whether or not the local
|
||||||
|
* event should be deleted on every /sync. However, this is partially correct, lets have a look
|
||||||
|
* at the following scenario:
|
||||||
|
*
|
||||||
|
* [There is no Internet connection] --> [10 Messages are sent] --> [The 10 messages are in the queue] -->
|
||||||
|
* [Internet comes back for 1 second] --> [3 messages are sent] --> [Internet drops again] -->
|
||||||
|
* [No /sync response is triggered | home server can even replied with /sync but never arrived while we are offline]
|
||||||
|
*
|
||||||
|
* So the state until now is that we have 7 pending events to send and 3 sent but not received them back from /sync
|
||||||
|
* Subsequently, those 3 local messages will not be deleted while there is no transactionId from the /sync
|
||||||
|
*
|
||||||
|
* lets continue:
|
||||||
|
* [Now lets assume that in the same room another user sent 15 events] -->
|
||||||
|
* [We are finally back online!] -->
|
||||||
|
* [We will receive the 10 latest events for the room and of course sent the pending 7 messages] -->
|
||||||
|
* Now /sync response will NOT contain the 3 local messages so our events will stuck in the device.
|
||||||
|
*
|
||||||
|
* Someone can say, yes but it will come with the rooms/{roomId}/messages while paginating,
|
||||||
|
* so the problem will be solved. No that is not the case for two reasons:
|
||||||
|
* 1. rooms/{roomId}/messages response do not contain the unsignedData.transactionId so we cannot know which event
|
||||||
|
* to delete
|
||||||
|
* 2. even if transactionId was there, currently we are not deleting it from the pagination
|
||||||
|
*
|
||||||
|
* ---------------------------------------------------------------------------------------------
|
||||||
|
* While we cannot know when a specific event arrived from the pagination (no transactionId included), after each room /sync
|
||||||
|
* we clear all SENT events, and we are sure that we will receive it from /sync or pagination
|
||||||
|
*/
|
||||||
|
private fun deleteLocalEchosIfNeeded(insertType: EventInsertType, roomEntity: RoomEntity, eventList: List<Event>) {
|
||||||
|
// Skip deletion if we are on initial sync
|
||||||
|
if (insertType == EventInsertType.INITIAL_SYNC) return
|
||||||
|
// Skip deletion if there are no timeline events or there is no event received from the current user
|
||||||
|
if (eventList.firstOrNull { it.senderId == userId } == null) return
|
||||||
|
roomEntity.sendingTimelineEvents.filter { timelineEvent ->
|
||||||
|
timelineEvent.root?.sendState == SendState.SENT
|
||||||
|
}.forEach {
|
||||||
|
roomEntity.sendingTimelineEvents.remove(it)
|
||||||
|
it.deleteOnCascade(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ internal class DefaultTermsService @Inject constructor(
|
||||||
*/
|
*/
|
||||||
override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse {
|
override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse {
|
||||||
return try {
|
return try {
|
||||||
val request = baseUrl + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register"
|
val request = baseUrl.ensureTrailingSlash() + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register"
|
||||||
executeRequest(null) {
|
executeRequest(null) {
|
||||||
termsAPI.register(request)
|
termsAPI.register(request)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
include ':vector'
|
include ':vector'
|
||||||
include ':matrix-sdk-android'
|
include ':matrix-sdk-android'
|
||||||
include ':matrix-sdk-android-rx'
|
|
||||||
include ':diff-match-patch'
|
include ':diff-match-patch'
|
||||||
include ':attachment-viewer'
|
include ':attachment-viewer'
|
||||||
include ':multipicker'
|
include ':multipicker'
|
||||||
|
include ':library:core-utils'
|
||||||
include ':library:ui-styles'
|
include ':library:ui-styles'
|
||||||
include ':matrix-sdk-android-flow'
|
include ':matrix-sdk-android-flow'
|
||||||
|
|
|
@ -15,13 +15,18 @@
|
||||||
name = "Bugfixes 🐛"
|
name = "Bugfixes 🐛"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
|
[[tool.towncrier.type]]
|
||||||
|
directory = "wip"
|
||||||
|
name = "In development 🚧"
|
||||||
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
directory = "doc"
|
directory = "doc"
|
||||||
name = "Improved Documentation 📚"
|
name = "Improved Documentation 📚"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
[[tool.towncrier.type]]
|
[[tool.towncrier.type]]
|
||||||
directory = "removal"
|
directory = "sdk"
|
||||||
name = "SDK API changes ⚠️"
|
name = "SDK API changes ⚠️"
|
||||||
showcontent = true
|
showcontent = true
|
||||||
|
|
||||||
|
|
|
@ -336,6 +336,7 @@ dependencies {
|
||||||
implementation project(":multipicker")
|
implementation project(":multipicker")
|
||||||
implementation project(":attachment-viewer")
|
implementation project(":attachment-viewer")
|
||||||
implementation project(":library:ui-styles")
|
implementation project(":library:ui-styles")
|
||||||
|
implementation project(":library:core-utils")
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
|
||||||
implementation libs.jetbrains.coroutinesCore
|
implementation libs.jetbrains.coroutinesCore
|
||||||
|
|
|
@ -36,13 +36,18 @@ class DebugFeaturesStateFactory @Inject constructor(
|
||||||
),
|
),
|
||||||
createBooleanFeature(
|
createBooleanFeature(
|
||||||
label = "FTUE Splash - I already have an account",
|
label = "FTUE Splash - I already have an account",
|
||||||
factory = VectorFeatures::isAlreadyHaveAccountSplashEnabled,
|
key = DebugFeatureKeys.onboardingAlreadyHaveAnAccount,
|
||||||
key = DebugFeatureKeys.alreadyHaveAnAccount
|
factory = VectorFeatures::isOnboardingAlreadyHaveAccountSplashEnabled
|
||||||
),
|
),
|
||||||
createBooleanFeature(
|
createBooleanFeature(
|
||||||
label = "FTUE Splash - Carousel",
|
label = "FTUE Splash - carousel",
|
||||||
factory = VectorFeatures::isSplashCarouselEnabled,
|
key = DebugFeatureKeys.onboardingSplashCarousel,
|
||||||
key = DebugFeatureKeys.splashCarousel
|
factory = VectorFeatures::isOnboardingSplashCarouselEnabled
|
||||||
|
),
|
||||||
|
createBooleanFeature(
|
||||||
|
label = "FTUE Use Case",
|
||||||
|
key = DebugFeatureKeys.onboardingUseCase,
|
||||||
|
factory = VectorFeatures::isOnboardingUseCaseEnabled
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,13 @@ class DebugVectorFeatures(
|
||||||
return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
|
return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.alreadyHaveAnAccount)
|
override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount)
|
||||||
?: vectorFeatures.isAlreadyHaveAccountSplashEnabled()
|
?: vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()
|
||||||
|
|
||||||
override fun isSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.splashCarousel) ?: vectorFeatures.isSplashCarouselEnabled()
|
override fun isOnboardingSplashCarouselEnabled(): Boolean = read(DebugFeatureKeys.onboardingSplashCarousel)
|
||||||
|
?: vectorFeatures.isOnboardingSplashCarouselEnabled()
|
||||||
|
|
||||||
|
override fun isOnboardingUseCaseEnabled(): Boolean = read(DebugFeatureKeys.onboardingUseCase) ?: vectorFeatures.isOnboardingUseCaseEnabled()
|
||||||
|
|
||||||
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
|
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -96,6 +99,7 @@ private inline fun <reified T : Enum<T>> enumPreferencesKey() = enumPreferencesK
|
||||||
private fun <T : Enum<T>> enumPreferencesKey(type: KClass<T>) = stringPreferencesKey("enum-${type.simpleName}")
|
private fun <T : Enum<T>> enumPreferencesKey(type: KClass<T>) = stringPreferencesKey("enum-${type.simpleName}")
|
||||||
|
|
||||||
object DebugFeatureKeys {
|
object DebugFeatureKeys {
|
||||||
val alreadyHaveAnAccount = booleanPreferencesKey("already-have-an-account")
|
val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account")
|
||||||
val splashCarousel = booleanPreferencesKey("splash-carousel")
|
val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel")
|
||||||
|
val onboardingUseCase = booleanPreferencesKey("onbboarding-splash-carousel")
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:resizeableActivity="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Vector.Light"
|
android:theme="@style/Theme.Vector.Light"
|
||||||
|
@ -402,7 +403,8 @@
|
||||||
android:value="androidx.startup"
|
android:value="androidx.startup"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
<!-- We init the lib ourself in EmojiCompatWrapper -->
|
<!-- We init the lib ourself in EmojiCompatWrapper -->
|
||||||
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
<meta-data
|
||||||
|
android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ class AppStateHandler @Inject constructor(
|
||||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
|
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
|
||||||
|
|
||||||
val selectedRoomGroupingObservable = selectedSpaceDataSource.stream()
|
val selectedRoomGroupingFlow = selectedSpaceDataSource.stream()
|
||||||
|
|
||||||
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
||||||
// XXX we should somehow make it live :/ just a work around
|
// XXX we should somehow make it live :/ just a work around
|
||||||
|
|
|
@ -31,7 +31,7 @@ import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class ActiveSessionHolder @Inject constructor(private val sessionObservableStore: ActiveSessionDataSource,
|
class ActiveSessionHolder @Inject constructor(private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
private val keyRequestHandler: KeyRequestHandler,
|
private val keyRequestHandler: KeyRequestHandler,
|
||||||
private val incomingVerificationRequestHandler: IncomingVerificationRequestHandler,
|
private val incomingVerificationRequestHandler: IncomingVerificationRequestHandler,
|
||||||
private val callManager: WebRtcCallManager,
|
private val callManager: WebRtcCallManager,
|
||||||
|
@ -46,7 +46,7 @@ class ActiveSessionHolder @Inject constructor(private val sessionObservableStore
|
||||||
fun setActiveSession(session: Session) {
|
fun setActiveSession(session: Session) {
|
||||||
Timber.w("setActiveSession of ${session.myUserId}")
|
Timber.w("setActiveSession of ${session.myUserId}")
|
||||||
activeSession.set(session)
|
activeSession.set(session)
|
||||||
sessionObservableStore.post(Option.just(session))
|
activeSessionDataSource.post(Option.just(session))
|
||||||
|
|
||||||
keyRequestHandler.start(session)
|
keyRequestHandler.start(session)
|
||||||
incomingVerificationRequestHandler.start(session)
|
incomingVerificationRequestHandler.start(session)
|
||||||
|
@ -66,7 +66,7 @@ class ActiveSessionHolder @Inject constructor(private val sessionObservableStore
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSession.set(null)
|
activeSession.set(null)
|
||||||
sessionObservableStore.post(Option.empty())
|
activeSessionDataSource.post(Option.empty())
|
||||||
|
|
||||||
keyRequestHandler.stop()
|
keyRequestHandler.stop()
|
||||||
incomingVerificationRequestHandler.stop()
|
incomingVerificationRequestHandler.stop()
|
||||||
|
|
|
@ -105,6 +105,7 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthServerSelectionFragmen
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthSignUpSignInSelectionFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashCarouselFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashCarouselFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthSplashFragment
|
||||||
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthUseCaseFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthWaitForEmailFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment
|
import im.vector.app.features.onboarding.ftueauth.FtueAuthWebFragment
|
||||||
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
|
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
|
||||||
|
@ -450,6 +451,11 @@ interface FragmentModule {
|
||||||
@FragmentKey(FtueAuthSplashCarouselFragment::class)
|
@FragmentKey(FtueAuthSplashCarouselFragment::class)
|
||||||
fun bindFtueAuthSplashCarouselFragment(fragment: FtueAuthSplashCarouselFragment): Fragment
|
fun bindFtueAuthSplashCarouselFragment(fragment: FtueAuthSplashCarouselFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(FtueAuthUseCaseFragment::class)
|
||||||
|
fun bindFtueAuthUseCaseFragment(fragment: FtueAuthUseCaseFragment): Fragment
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(FtueAuthWaitForEmailFragment::class)
|
@FragmentKey(FtueAuthWaitForEmailFragment::class)
|
||||||
|
|
|
@ -82,7 +82,7 @@ fun TextView.setTextWithColoredPart(@StringRes fullTextRes: Int,
|
||||||
fun TextView.setTextWithColoredPart(fullText: String,
|
fun TextView.setTextWithColoredPart(fullText: String,
|
||||||
coloredPart: String,
|
coloredPart: String,
|
||||||
@AttrRes colorAttribute: Int = R.attr.colorPrimary,
|
@AttrRes colorAttribute: Int = R.attr.colorPrimary,
|
||||||
underline: Boolean = false,
|
underline: Boolean = true,
|
||||||
onClick: (() -> Unit)? = null) {
|
onClick: (() -> Unit)? = null) {
|
||||||
val color = ThemeUtils.getColor(context, colorAttribute)
|
val color = ThemeUtils.getColor(context, colorAttribute)
|
||||||
|
|
||||||
|
@ -101,7 +101,6 @@ fun TextView.setTextWithColoredPart(fullText: String,
|
||||||
|
|
||||||
override fun updateDrawState(ds: TextPaint) {
|
override fun updateDrawState(ds: TextPaint) {
|
||||||
ds.color = color
|
ds.color = color
|
||||||
ds.isUnderlineText = !underline
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSpan(clickableSpan, index, index + coloredPart.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
setSpan(clickableSpan, index, index + coloredPart.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.core.platform
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import im.vector.app.R
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ScreenOrientationLocker @Inject constructor(
|
||||||
|
private val resources: Resources
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Some screens do not provide enough value for us to provide phone landscape experiences
|
||||||
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
|
fun lockPhonesToPortrait(activity: AppCompatActivity) {
|
||||||
|
when (resources.getBoolean(R.bool.is_tablet)) {
|
||||||
|
true -> {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
false -> {
|
||||||
|
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,3 +27,5 @@ class LocaleProvider @Inject constructor(private val resources: Resources) {
|
||||||
return ConfigurationCompat.getLocales(resources.configuration)[0]
|
return ConfigurationCompat.getLocales(resources.configuration)[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LocaleProvider.isEnglishSpeaking() = current().language.startsWith("en")
|
||||||
|
|
|
@ -21,10 +21,9 @@ import im.vector.app.BuildConfig
|
||||||
interface VectorFeatures {
|
interface VectorFeatures {
|
||||||
|
|
||||||
fun onboardingVariant(): OnboardingVariant
|
fun onboardingVariant(): OnboardingVariant
|
||||||
|
fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean
|
||||||
fun isAlreadyHaveAccountSplashEnabled(): Boolean
|
fun isOnboardingSplashCarouselEnabled(): Boolean
|
||||||
|
fun isOnboardingUseCaseEnabled(): Boolean
|
||||||
fun isSplashCarouselEnabled(): Boolean
|
|
||||||
|
|
||||||
enum class OnboardingVariant {
|
enum class OnboardingVariant {
|
||||||
LEGACY,
|
LEGACY,
|
||||||
|
@ -35,6 +34,7 @@ interface VectorFeatures {
|
||||||
|
|
||||||
class DefaultVectorFeatures : VectorFeatures {
|
class DefaultVectorFeatures : VectorFeatures {
|
||||||
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
|
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
|
||||||
override fun isAlreadyHaveAccountSplashEnabled() = true
|
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
|
||||||
override fun isSplashCarouselEnabled() = false
|
override fun isOnboardingSplashCarouselEnabled() = false
|
||||||
|
override fun isOnboardingUseCaseEnabled() = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
package im.vector.app.features.analytics
|
package im.vector.app.features.analytics
|
||||||
|
|
||||||
import im.vector.app.core.flow.tickerFlow
|
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
import im.vector.app.features.analytics.plan.Error
|
import im.vector.app.features.analytics.plan.Error
|
||||||
|
import im.vector.lib.core.utils.flow.tickerFlow
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
|
|
@ -20,8 +20,10 @@ import com.airbnb.mvrx.viewModel
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.core.extensions.addFragment
|
import im.vector.app.core.extensions.addFragment
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.ScreenOrientationLocker
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivitySimpleBinding
|
import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple container for AnalyticsOptInFragment
|
* Simple container for AnalyticsOptInFragment
|
||||||
|
@ -29,6 +31,8 @@ import im.vector.app.databinding.ActivitySimpleBinding
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class AnalyticsOptInActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
class AnalyticsOptInActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var orientationLocker: ScreenOrientationLocker
|
||||||
|
|
||||||
private val viewModel: AnalyticsConsentViewModel by viewModel()
|
private val viewModel: AnalyticsConsentViewModel by viewModel()
|
||||||
|
|
||||||
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||||
|
@ -36,6 +40,7 @@ class AnalyticsOptInActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||||
|
|
||||||
override fun initUiAndData() {
|
override fun initUiAndData() {
|
||||||
|
orientationLocker.lockPhonesToPortrait(this)
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
addFragment(views.simpleFragmentContainer, AnalyticsOptInFragment::class.java)
|
addFragment(views.simpleFragmentContainer, AnalyticsOptInFragment::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ class AutocompleteCommandPresenter @AssistedInject constructor(
|
||||||
if (query.isNullOrEmpty()) {
|
if (query.isNullOrEmpty()) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
it.command.startsWith(query, 1, true)
|
it.startsWith(query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controller.setData(data)
|
controller.setData(data)
|
||||||
|
|
|
@ -19,9 +19,7 @@ package im.vector.app.features.call.webrtc
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import im.vector.app.core.flow.chunk
|
|
||||||
import im.vector.app.core.services.CallService
|
import im.vector.app.core.services.CallService
|
||||||
import im.vector.app.core.utils.CountUpTimer
|
|
||||||
import im.vector.app.core.utils.PublishDataSource
|
import im.vector.app.core.utils.PublishDataSource
|
||||||
import im.vector.app.core.utils.TextUtils.formatDuration
|
import im.vector.app.core.utils.TextUtils.formatDuration
|
||||||
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
||||||
|
@ -37,6 +35,8 @@ import im.vector.app.features.call.utils.awaitSetLocalDescription
|
||||||
import im.vector.app.features.call.utils.awaitSetRemoteDescription
|
import im.vector.app.features.call.utils.awaitSetRemoteDescription
|
||||||
import im.vector.app.features.call.utils.mapToCallCandidate
|
import im.vector.app.features.call.utils.mapToCallCandidate
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
|
import im.vector.lib.core.utils.flow.chunk
|
||||||
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
|
@ -24,42 +24,51 @@ import im.vector.app.R
|
||||||
* the user can write theses messages to perform some actions
|
* the user can write theses messages to perform some actions
|
||||||
* the list will be displayed in this order
|
* the list will be displayed in this order
|
||||||
*/
|
*/
|
||||||
enum class Command(val command: String, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean, val isThreadCommand: Boolean) {
|
enum class Command(val command: String,
|
||||||
EMOTE("/me", "<message>", R.string.command_description_emote, false, true),
|
val aliases: Array<CharSequence>?,
|
||||||
BAN_USER("/ban", "<user-id> [reason]", R.string.command_description_ban_user, false, false),
|
val parameters: String,
|
||||||
UNBAN_USER("/unban", "<user-id> [reason]", R.string.command_description_unban_user, false, false),
|
@StringRes val description: Int,
|
||||||
IGNORE_USER("/ignore", "<user-id> [reason]", R.string.command_description_ignore_user, false, true),
|
val isDevCommand: Boolean,
|
||||||
UNIGNORE_USER("/unignore", "<user-id>", R.string.command_description_unignore_user, false, true),
|
val isThreadCommand: Boolean) {
|
||||||
SET_USER_POWER_LEVEL("/op", "<user-id> [<power-level>]", R.string.command_description_op_user, false, false),
|
EMOTE("/me", null, "<message>", R.string.command_description_emote, false, true),
|
||||||
RESET_USER_POWER_LEVEL("/deop", "<user-id>", R.string.command_description_deop_user, false, false),
|
BAN_USER("/ban", null, "<user-id> [reason]", R.string.command_description_ban_user, false, false),
|
||||||
ROOM_NAME("/roomname", "<name>", R.string.command_description_room_name, false, false),
|
UNBAN_USER("/unban", null, "<user-id> [reason]", R.string.command_description_unban_user, false, false),
|
||||||
INVITE("/invite", "<user-id> [reason]", R.string.command_description_invite_user, false, false),
|
IGNORE_USER("/ignore", null, "<user-id> [reason]", R.string.command_description_ignore_user, false, true),
|
||||||
JOIN_ROOM("/join", "<room-address> [reason]", R.string.command_description_join_room, false, false),
|
UNIGNORE_USER("/unignore", null, "<user-id>", R.string.command_description_unignore_user, false, true),
|
||||||
PART("/part", "[<room-address>]", R.string.command_description_part_room, false, false),
|
SET_USER_POWER_LEVEL("/op", null, "<user-id> [<power-level>]", R.string.command_description_op_user, false, false),
|
||||||
TOPIC("/topic", "<topic>", R.string.command_description_topic, false, false),
|
RESET_USER_POWER_LEVEL("/deop", null, "<user-id>", R.string.command_description_deop_user, false, false),
|
||||||
KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user, false, false),
|
ROOM_NAME("/roomname", null, "<name>", R.string.command_description_room_name, false, false),
|
||||||
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick, false, false),
|
INVITE("/invite", null, "<user-id> [reason]", R.string.command_description_invite_user, false, false),
|
||||||
CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", "<display-name>", R.string.command_description_nick_for_room, false, false),
|
JOIN_ROOM("/join", arrayOf("/j", "/goto"), "<room-address> [reason]", R.string.command_description_join_room, false, false),
|
||||||
ROOM_AVATAR("/roomavatar", "<mxc_url>", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */, false),
|
PART("/part", null, "[<room-address>]", R.string.command_description_part_room, false, false),
|
||||||
CHANGE_AVATAR_FOR_ROOM("/myroomavatar", "<mxc_url>", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */, false),
|
TOPIC("/topic", null, "<topic>", R.string.command_description_topic, false, false),
|
||||||
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown, false, false),
|
REMOVE_USER("/remove", arrayOf("/kick"), "<user-id> [reason]", R.string.command_description_kick_user, false, false),
|
||||||
RAINBOW("/rainbow", "<message>", R.string.command_description_rainbow, false, true),
|
CHANGE_DISPLAY_NAME("/nick", null, "<display-name>", R.string.command_description_nick, false, false),
|
||||||
RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote, false, true),
|
CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", arrayOf("/roomnick"), "<display-name>", R.string.command_description_nick_for_room, false, false),
|
||||||
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false, false),
|
ROOM_AVATAR("/roomavatar", null, "<mxc_url>", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */, false),
|
||||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler, false, true),
|
CHANGE_AVATAR_FOR_ROOM("/myroomavatar", null, "<mxc_url>", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */, false),
|
||||||
SHRUG("/shrug", "<message>", R.string.command_description_shrug, false, true),
|
MARKDOWN("/markdown", null, "<on|off>", R.string.command_description_markdown, false, false),
|
||||||
LENNY("/lenny", "<message>", R.string.command_description_lenny, false, true),
|
RAINBOW("/rainbow", null, "<message>", R.string.command_description_rainbow, false, true),
|
||||||
PLAIN("/plain", "<message>", R.string.command_description_plain, false, true),
|
RAINBOW_EMOTE("/rainbowme", null, "<message>", R.string.command_description_rainbow_emote, false, true),
|
||||||
WHOIS("/whois", "<user-id>", R.string.command_description_whois, false, true),
|
CLEAR_SCALAR_TOKEN("/clear_scalar_token", null, "", R.string.command_description_clear_scalar_token, false, false),
|
||||||
DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false, false),
|
SPOILER("/spoiler", null, "<message>", R.string.command_description_spoiler, false, true),
|
||||||
CONFETTI("/confetti", "<message>", R.string.command_confetti, false, false),
|
SHRUG("/shrug", null, "<message>", R.string.command_description_shrug, false, true),
|
||||||
SNOWFALL("/snowfall", "<message>", R.string.command_snow, false, false),
|
LENNY("/lenny", null, "<message>", R.string.command_description_lenny, false, true),
|
||||||
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space, true, false),
|
PLAIN("/plain", null, "<message>", R.string.command_description_plain, false, true),
|
||||||
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_add_to_space, true, false),
|
WHOIS("/whois", null, "<user-id>", R.string.command_description_whois, false, true),
|
||||||
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true, false),
|
DISCARD_SESSION("/discardsession", null, "", R.string.command_description_discard_session, false, false),
|
||||||
LEAVE_ROOM("/leave", "<roomId?>", R.string.command_description_leave_room, true, false),
|
CONFETTI("/confetti", null, "<message>", R.string.command_confetti, false, false),
|
||||||
UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true, false);
|
SNOWFALL("/snowfall", null, "<message>", R.string.command_snow, false, false),
|
||||||
|
CREATE_SPACE("/createspace", null, "<name> <invitee>*", R.string.command_description_create_space, true, false),
|
||||||
|
ADD_TO_SPACE("/addToSpace", null, "spaceId", R.string.command_description_add_to_space, true, false),
|
||||||
|
JOIN_SPACE("/joinSpace", null, "spaceId", R.string.command_description_join_space, true, false),
|
||||||
|
LEAVE_ROOM("/leave", null, "<roomId?>", R.string.command_description_leave_room, true, false),
|
||||||
|
UPGRADE_ROOM("/upgraderoom", null, "newVersion", R.string.command_description_upgrade_room, true, false);
|
||||||
|
|
||||||
val length
|
val allAliases = arrayOf(command, *aliases.orEmpty())
|
||||||
get() = command.length + 1
|
|
||||||
|
fun matches(inputCommand: CharSequence) = allAliases.any { it.contentEquals(inputCommand, true) }
|
||||||
|
|
||||||
|
fun startsWith(input: CharSequence) =
|
||||||
|
allAliases.any { it.startsWith(input, 1, true) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,12 +33,12 @@ object CommandParser {
|
||||||
* @param textMessage the text message
|
* @param textMessage the text message
|
||||||
* @return a parsed slash command (ok or error)
|
* @return a parsed slash command (ok or error)
|
||||||
*/
|
*/
|
||||||
fun parseSplashCommand(textMessage: CharSequence, isInThreadTimeline: Boolean): ParsedCommand {
|
fun parseSlashCommand(textMessage: CharSequence, isInThreadTimeline: Boolean): ParsedCommand {
|
||||||
// check if it has the Slash marker
|
// check if it has the Slash marker
|
||||||
if (!textMessage.startsWith("/")) {
|
if (!textMessage.startsWith("/")) {
|
||||||
return ParsedCommand.ErrorNotACommand
|
return ParsedCommand.ErrorNotACommand
|
||||||
} else {
|
} else {
|
||||||
Timber.v("parseSplashCommand")
|
Timber.v("parseSlashCommand")
|
||||||
|
|
||||||
// "/" only
|
// "/" only
|
||||||
if (textMessage.length == 1) {
|
if (textMessage.length == 1) {
|
||||||
|
@ -53,7 +53,7 @@ object CommandParser {
|
||||||
val messageParts = try {
|
val messageParts = try {
|
||||||
textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
|
textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## manageSplashCommand() : split failed")
|
Timber.e(e, "## manageSlashCommand() : split failed")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,10 +62,10 @@ object CommandParser {
|
||||||
return ParsedCommand.ErrorEmptySlashCommand
|
return ParsedCommand.ErrorEmptySlashCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the command is not supported by threads return error
|
val slashCommand = messageParts.first()
|
||||||
|
val message = textMessage.substring(slashCommand.length).trim()
|
||||||
|
|
||||||
if (BuildConfig.THREADING_ENABLED && isInThreadTimeline) {
|
if (BuildConfig.THREADING_ENABLED && isInThreadTimeline) {
|
||||||
val slashCommand = messageParts.first()
|
|
||||||
val notSupportedCommandsInThreads = Command.values().filter {
|
val notSupportedCommandsInThreads = Command.values().filter {
|
||||||
!it.isThreadCommand
|
!it.isThreadCommand
|
||||||
}.map {
|
}.map {
|
||||||
|
@ -76,35 +76,29 @@ object CommandParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (val slashCommand = messageParts.first()) {
|
return when {
|
||||||
Command.PLAIN.command -> {
|
Command.PLAIN.matches(slashCommand) -> {
|
||||||
val text = textMessage.substring(Command.PLAIN.command.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.SendPlainText(message = message)
|
||||||
if (text.isNotEmpty()) {
|
|
||||||
ParsedCommand.SendPlainText(text)
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.PLAIN)
|
ParsedCommand.ErrorSyntax(Command.PLAIN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.CHANGE_DISPLAY_NAME.command -> {
|
Command.CHANGE_DISPLAY_NAME.matches(slashCommand) -> {
|
||||||
val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME.command.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.ChangeDisplayName(displayName = message)
|
||||||
if (newDisplayName.isNotEmpty()) {
|
|
||||||
ParsedCommand.ChangeDisplayName(newDisplayName)
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME)
|
ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.CHANGE_DISPLAY_NAME_FOR_ROOM.command -> {
|
Command.CHANGE_DISPLAY_NAME_FOR_ROOM.matches(slashCommand) -> {
|
||||||
val newDisplayName = textMessage.substring(Command.CHANGE_DISPLAY_NAME_FOR_ROOM.command.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.ChangeDisplayNameForRoom(displayName = message)
|
||||||
if (newDisplayName.isNotEmpty()) {
|
|
||||||
ParsedCommand.ChangeDisplayNameForRoom(newDisplayName)
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME_FOR_ROOM)
|
ParsedCommand.ErrorSyntax(Command.CHANGE_DISPLAY_NAME_FOR_ROOM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.ROOM_AVATAR.command -> {
|
Command.ROOM_AVATAR.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
val url = messageParts[1]
|
val url = messageParts[1]
|
||||||
|
|
||||||
|
@ -117,7 +111,7 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.ROOM_AVATAR)
|
ParsedCommand.ErrorSyntax(Command.ROOM_AVATAR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.CHANGE_AVATAR_FOR_ROOM.command -> {
|
Command.CHANGE_AVATAR_FOR_ROOM.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
val url = messageParts[1]
|
val url = messageParts[1]
|
||||||
|
|
||||||
|
@ -130,40 +124,42 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.CHANGE_AVATAR_FOR_ROOM)
|
ParsedCommand.ErrorSyntax(Command.CHANGE_AVATAR_FOR_ROOM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.TOPIC.command -> {
|
Command.TOPIC.matches(slashCommand) -> {
|
||||||
val newTopic = textMessage.substring(Command.TOPIC.command.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.ChangeTopic(topic = message)
|
||||||
if (newTopic.isNotEmpty()) {
|
|
||||||
ParsedCommand.ChangeTopic(newTopic)
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.TOPIC)
|
ParsedCommand.ErrorSyntax(Command.TOPIC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.EMOTE.command -> {
|
Command.EMOTE.matches(slashCommand) -> {
|
||||||
val message = textMessage.subSequence(Command.EMOTE.command.length, textMessage.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.SendEmote(message)
|
||||||
ParsedCommand.SendEmote(message)
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.EMOTE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command.RAINBOW.command -> {
|
Command.RAINBOW.matches(slashCommand) -> {
|
||||||
val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.SendRainbow(message)
|
||||||
ParsedCommand.SendRainbow(message)
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.RAINBOW)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command.RAINBOW_EMOTE.command -> {
|
Command.RAINBOW_EMOTE.matches(slashCommand) -> {
|
||||||
val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.SendRainbowEmote(message)
|
||||||
ParsedCommand.SendRainbowEmote(message)
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.RAINBOW_EMOTE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command.JOIN_ROOM.command -> {
|
Command.JOIN_ROOM.matches(slashCommand) -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val roomAlias = messageParts[1]
|
val roomAlias = messageParts[1]
|
||||||
|
|
||||||
if (roomAlias.isNotEmpty()) {
|
if (roomAlias.isNotEmpty()) {
|
||||||
ParsedCommand.JoinRoom(
|
ParsedCommand.JoinRoom(
|
||||||
roomAlias,
|
roomAlias,
|
||||||
textMessage.substring(Command.JOIN_ROOM.length + roomAlias.length)
|
trimParts(textMessage, messageParts.take(2))
|
||||||
.trim()
|
|
||||||
.takeIf { it.isNotBlank() }
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
||||||
|
@ -172,23 +168,21 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
ParsedCommand.ErrorSyntax(Command.JOIN_ROOM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.PART.command -> {
|
Command.PART.matches(slashCommand) -> {
|
||||||
when (messageParts.size) {
|
when (messageParts.size) {
|
||||||
1 -> ParsedCommand.PartRoom(null)
|
1 -> ParsedCommand.PartRoom(null)
|
||||||
2 -> ParsedCommand.PartRoom(messageParts[1])
|
2 -> ParsedCommand.PartRoom(messageParts[1])
|
||||||
else -> ParsedCommand.ErrorSyntax(Command.PART)
|
else -> ParsedCommand.ErrorSyntax(Command.PART)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.ROOM_NAME.command -> {
|
Command.ROOM_NAME.matches(slashCommand) -> {
|
||||||
val newRoomName = textMessage.substring(Command.ROOM_NAME.command.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.ChangeRoomName(name = message)
|
||||||
if (newRoomName.isNotEmpty()) {
|
|
||||||
ParsedCommand.ChangeRoomName(newRoomName)
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.ROOM_NAME)
|
ParsedCommand.ErrorSyntax(Command.ROOM_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.INVITE.command -> {
|
Command.INVITE.matches(slashCommand) -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
|
@ -196,9 +190,7 @@ object CommandParser {
|
||||||
MatrixPatterns.isUserId(userId) -> {
|
MatrixPatterns.isUserId(userId) -> {
|
||||||
ParsedCommand.Invite(
|
ParsedCommand.Invite(
|
||||||
userId,
|
userId,
|
||||||
textMessage.substring(Command.INVITE.length + userId.length)
|
trimParts(textMessage, messageParts.take(2))
|
||||||
.trim()
|
|
||||||
.takeIf { it.isNotBlank() }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
userId.isEmail() -> {
|
userId.isEmail() -> {
|
||||||
|
@ -215,34 +207,30 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.INVITE)
|
ParsedCommand.ErrorSyntax(Command.INVITE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.KICK_USER.command -> {
|
Command.REMOVE_USER.matches(slashCommand) -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
ParsedCommand.KickUser(
|
ParsedCommand.RemoveUser(
|
||||||
userId,
|
userId,
|
||||||
textMessage.substring(Command.KICK_USER.length + userId.length)
|
trimParts(textMessage, messageParts.take(2))
|
||||||
.trim()
|
|
||||||
.takeIf { it.isNotBlank() }
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.KICK_USER)
|
ParsedCommand.ErrorSyntax(Command.REMOVE_USER)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.KICK_USER)
|
ParsedCommand.ErrorSyntax(Command.REMOVE_USER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.BAN_USER.command -> {
|
Command.BAN_USER.matches(slashCommand) -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
ParsedCommand.BanUser(
|
ParsedCommand.BanUser(
|
||||||
userId,
|
userId,
|
||||||
textMessage.substring(Command.BAN_USER.length + userId.length)
|
trimParts(textMessage, messageParts.take(2))
|
||||||
.trim()
|
|
||||||
.takeIf { it.isNotBlank() }
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.BAN_USER)
|
ParsedCommand.ErrorSyntax(Command.BAN_USER)
|
||||||
|
@ -251,16 +239,14 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.BAN_USER)
|
ParsedCommand.ErrorSyntax(Command.BAN_USER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.UNBAN_USER.command -> {
|
Command.UNBAN_USER.matches(slashCommand) -> {
|
||||||
if (messageParts.size >= 2) {
|
if (messageParts.size >= 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
ParsedCommand.UnbanUser(
|
ParsedCommand.UnbanUser(
|
||||||
userId,
|
userId,
|
||||||
textMessage.substring(Command.UNBAN_USER.length + userId.length)
|
trimParts(textMessage, messageParts.take(2))
|
||||||
.trim()
|
|
||||||
.takeIf { it.isNotBlank() }
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
|
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
|
||||||
|
@ -269,7 +255,7 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
|
ParsedCommand.ErrorSyntax(Command.UNBAN_USER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.IGNORE_USER.command -> {
|
Command.IGNORE_USER.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
|
@ -282,7 +268,7 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.IGNORE_USER)
|
ParsedCommand.ErrorSyntax(Command.IGNORE_USER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.UNIGNORE_USER.command -> {
|
Command.UNIGNORE_USER.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
|
@ -295,7 +281,7 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.UNIGNORE_USER)
|
ParsedCommand.ErrorSyntax(Command.UNIGNORE_USER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.SET_USER_POWER_LEVEL.command -> {
|
Command.SET_USER_POWER_LEVEL.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 3) {
|
if (messageParts.size == 3) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
if (MatrixPatterns.isUserId(userId)) {
|
if (MatrixPatterns.isUserId(userId)) {
|
||||||
|
@ -315,7 +301,7 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
|
ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.RESET_USER_POWER_LEVEL.command -> {
|
Command.RESET_USER_POWER_LEVEL.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
|
@ -328,7 +314,7 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
|
ParsedCommand.ErrorSyntax(Command.SET_USER_POWER_LEVEL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.MARKDOWN.command -> {
|
Command.MARKDOWN.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
when {
|
when {
|
||||||
"on".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(true)
|
"on".equals(messageParts[1], true) -> ParsedCommand.SetMarkdown(true)
|
||||||
|
@ -339,31 +325,34 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.MARKDOWN)
|
ParsedCommand.ErrorSyntax(Command.MARKDOWN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.CLEAR_SCALAR_TOKEN.command -> {
|
Command.CLEAR_SCALAR_TOKEN.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 1) {
|
if (messageParts.size == 1) {
|
||||||
ParsedCommand.ClearScalarToken
|
ParsedCommand.ClearScalarToken
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN)
|
ParsedCommand.ErrorSyntax(Command.CLEAR_SCALAR_TOKEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.SPOILER.command -> {
|
Command.SPOILER.matches(slashCommand) -> {
|
||||||
val message = textMessage.substring(Command.SPOILER.command.length).trim()
|
if (message.isNotEmpty()) {
|
||||||
ParsedCommand.SendSpoiler(message)
|
ParsedCommand.SendSpoiler(message)
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.SPOILER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command.SHRUG.command -> {
|
Command.SHRUG.matches(slashCommand) -> {
|
||||||
val message = textMessage.substring(Command.SHRUG.command.length).trim()
|
|
||||||
|
|
||||||
ParsedCommand.SendShrug(message)
|
ParsedCommand.SendShrug(message)
|
||||||
}
|
}
|
||||||
Command.LENNY.command -> {
|
Command.LENNY.matches(slashCommand) -> {
|
||||||
val message = textMessage.substring(Command.LENNY.command.length).trim()
|
|
||||||
|
|
||||||
ParsedCommand.SendLenny(message)
|
ParsedCommand.SendLenny(message)
|
||||||
}
|
}
|
||||||
Command.DISCARD_SESSION.command -> {
|
Command.DISCARD_SESSION.matches(slashCommand) -> {
|
||||||
ParsedCommand.DiscardSession
|
if (messageParts.size == 1) {
|
||||||
|
ParsedCommand.DiscardSession
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.DISCARD_SESSION)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Command.WHOIS.command -> {
|
Command.WHOIS.matches(slashCommand) -> {
|
||||||
if (messageParts.size == 2) {
|
if (messageParts.size == 2) {
|
||||||
val userId = messageParts[1]
|
val userId = messageParts[1]
|
||||||
|
|
||||||
|
@ -376,57 +365,57 @@ object CommandParser {
|
||||||
ParsedCommand.ErrorSyntax(Command.WHOIS)
|
ParsedCommand.ErrorSyntax(Command.WHOIS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command.CONFETTI.command -> {
|
Command.CONFETTI.matches(slashCommand) -> {
|
||||||
val message = textMessage.substring(Command.CONFETTI.command.length).trim()
|
|
||||||
ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message)
|
ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message)
|
||||||
}
|
}
|
||||||
Command.SNOWFALL.command -> {
|
Command.SNOWFALL.matches(slashCommand) -> {
|
||||||
val message = textMessage.substring(Command.SNOWFALL.command.length).trim()
|
|
||||||
ParsedCommand.SendChatEffect(ChatEffect.SNOWFALL, message)
|
ParsedCommand.SendChatEffect(ChatEffect.SNOWFALL, message)
|
||||||
}
|
}
|
||||||
Command.CREATE_SPACE.command -> {
|
Command.CREATE_SPACE.matches(slashCommand) -> {
|
||||||
val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim()
|
if (messageParts.size >= 2) {
|
||||||
val split = rawCommand.split(" ").map { it.trim() }
|
|
||||||
if (split.isEmpty()) {
|
|
||||||
ParsedCommand.ErrorSyntax(Command.CREATE_SPACE)
|
|
||||||
} else {
|
|
||||||
ParsedCommand.CreateSpace(
|
ParsedCommand.CreateSpace(
|
||||||
split[0],
|
messageParts[1],
|
||||||
split.subList(1, split.size)
|
messageParts.drop(2)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
Command.ADD_TO_SPACE.command -> {
|
|
||||||
val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim()
|
|
||||||
ParsedCommand.AddToSpace(
|
|
||||||
rawCommand
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command.JOIN_SPACE.command -> {
|
|
||||||
val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim()
|
|
||||||
ParsedCommand.JoinSpace(
|
|
||||||
spaceIdOrAlias
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command.LEAVE_ROOM.command -> {
|
|
||||||
val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim()
|
|
||||||
ParsedCommand.LeaveRoom(
|
|
||||||
spaceIdOrAlias
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Command.UPGRADE_ROOM.command -> {
|
|
||||||
val newVersion = textMessage.substring(Command.UPGRADE_ROOM.command.length).trim()
|
|
||||||
if (newVersion.isEmpty()) {
|
|
||||||
ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
|
|
||||||
} else {
|
} else {
|
||||||
ParsedCommand.UpgradeRoom(newVersion)
|
ParsedCommand.ErrorSyntax(Command.CREATE_SPACE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
Command.ADD_TO_SPACE.matches(slashCommand) -> {
|
||||||
|
if (messageParts.size == 1) {
|
||||||
|
ParsedCommand.AddToSpace(spaceId = message)
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.ADD_TO_SPACE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command.JOIN_SPACE.matches(slashCommand) -> {
|
||||||
|
if (messageParts.size == 1) {
|
||||||
|
ParsedCommand.JoinSpace(spaceIdOrAlias = message)
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.JOIN_SPACE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command.LEAVE_ROOM.matches(slashCommand) -> {
|
||||||
|
ParsedCommand.LeaveRoom(roomId = message)
|
||||||
|
}
|
||||||
|
Command.UPGRADE_ROOM.matches(slashCommand) -> {
|
||||||
|
if (message.isNotEmpty()) {
|
||||||
|
ParsedCommand.UpgradeRoom(newVersion = message)
|
||||||
|
} else {
|
||||||
|
ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
// Unknown command
|
// Unknown command
|
||||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun trimParts(message: CharSequence, messageParts: List<String>): String? {
|
||||||
|
val partsSize = messageParts.sumOf { it.length }
|
||||||
|
val gapsNumber = messageParts.size - 1
|
||||||
|
return message.substring(partsSize + gapsNumber).trim().takeIf { it.isNotEmpty() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ sealed class ParsedCommand {
|
||||||
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
|
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
|
||||||
class PartRoom(val roomAlias: String?) : ParsedCommand()
|
class PartRoom(val roomAlias: String?) : ParsedCommand()
|
||||||
class ChangeTopic(val topic: String) : ParsedCommand()
|
class ChangeTopic(val topic: String) : ParsedCommand()
|
||||||
class KickUser(val userId: String, val reason: String?) : ParsedCommand()
|
class RemoveUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||||
class ChangeDisplayName(val displayName: String) : ParsedCommand()
|
class ChangeDisplayName(val displayName: String) : ParsedCommand()
|
||||||
class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand()
|
class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand()
|
||||||
class ChangeRoomAvatar(val url: String) : ParsedCommand()
|
class ChangeRoomAvatar(val url: String) : ParsedCommand()
|
||||||
|
|
|
@ -26,10 +26,10 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.flow.throttleFirst
|
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||||
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
|
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
|
|
@ -25,10 +25,10 @@ import androidx.core.text.toSpannable
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.flow.throttleFirst
|
|
||||||
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
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||||
|
|
|
@ -27,9 +27,9 @@ import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
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.hideKeyboard
|
import im.vector.app.core.extensions.hideKeyboard
|
||||||
import im.vector.app.core.flow.throttleFirst
|
|
||||||
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.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||||
|
|
|
@ -25,10 +25,10 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import com.airbnb.mvrx.parentFragmentViewModel
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
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.flow.throttleFirst
|
|
||||||
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
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||||
|
|
|
@ -33,12 +33,12 @@ import com.airbnb.mvrx.withState
|
||||||
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.flow.throttleFirst
|
|
||||||
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
|
||||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||||
import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
|
import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
|
|
@ -174,8 +174,8 @@ class RoomDevToolViewModel @AssistedInject constructor(
|
||||||
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
|
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
|
||||||
|
|
||||||
room.sendStateEvent(
|
room.sendStateEvent(
|
||||||
state.selectedEvent?.type ?: "",
|
state.selectedEvent?.type.orEmpty(),
|
||||||
state.selectedEvent?.stateKey,
|
state.selectedEvent?.stateKey.orEmpty(),
|
||||||
json
|
json
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@ -213,7 +213,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
|
||||||
if (isState) {
|
if (isState) {
|
||||||
room.sendStateEvent(
|
room.sendStateEvent(
|
||||||
eventType,
|
eventType,
|
||||||
state.sendEventDraft.stateKey,
|
state.sendEventDraft.stateKey.orEmpty(),
|
||||||
json
|
json
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import im.vector.app.RoomGroupingMethod
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.extensions.singletonEntryPoint
|
import im.vector.app.core.extensions.singletonEntryPoint
|
||||||
import im.vector.app.core.flow.throttleFirst
|
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.call.dialpad.DialPadLookup
|
import im.vector.app.features.call.dialpad.DialPadLookup
|
||||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||||
|
@ -37,6 +36,7 @@ import im.vector.app.features.invite.AutoAcceptInvites
|
||||||
import im.vector.app.features.invite.showInvites
|
import im.vector.app.features.invite.showInvites
|
||||||
import im.vector.app.features.settings.VectorDataStore
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.ui.UiStateRepository
|
import im.vector.app.features.ui.UiStateRepository
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
@ -197,7 +197,7 @@ class HomeDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomGroupingMethod() {
|
private fun observeRoomGroupingMethod() {
|
||||||
appStateHandler.selectedRoomGroupingObservable
|
appStateHandler.selectedRoomGroupingFlow
|
||||||
.setOnEach {
|
.setOnEach {
|
||||||
copy(
|
copy(
|
||||||
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||||
|
@ -206,7 +206,7 @@ class HomeDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummaries() {
|
private fun observeRoomSummaries() {
|
||||||
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().flatMapLatest {
|
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest {
|
||||||
// we use it as a trigger to all changes in room, but do not really load
|
// we use it as a trigger to all changes in room, but do not really load
|
||||||
// the actual models
|
// the actual models
|
||||||
session.getPagedRoomSummariesLive(
|
session.getPagedRoomSummariesLive(
|
||||||
|
|
|
@ -50,7 +50,7 @@ class PromoteRestrictedViewModel @AssistedInject constructor(
|
||||||
) : VectorViewModel<ActiveSpaceViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<ActiveSpaceViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().execute { state ->
|
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().execute { state ->
|
||||||
val groupingMethod = state.invoke()?.orNull()
|
val groupingMethod = state.invoke()?.orNull()
|
||||||
val isSpaceMode = groupingMethod is RoomGroupingMethod.BySpace
|
val isSpaceMode = groupingMethod is RoomGroupingMethod.BySpace
|
||||||
val currentSpace = (groupingMethod as? RoomGroupingMethod.BySpace)?.spaceSummary
|
val currentSpace = (groupingMethod as? RoomGroupingMethod.BySpace)?.spaceSummary
|
||||||
|
|
|
@ -26,12 +26,12 @@ import im.vector.app.AppStateHandler
|
||||||
import im.vector.app.RoomGroupingMethod
|
import im.vector.app.RoomGroupingMethod
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.flow.throttleFirst
|
|
||||||
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.invite.AutoAcceptInvites
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import im.vector.lib.core.utils.flow.throttleFirst
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
@ -107,8 +107,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
||||||
}
|
}
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(),
|
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged(),
|
||||||
appStateHandler.selectedRoomGroupingObservable.flatMapLatest {
|
appStateHandler.selectedRoomGroupingFlow.flatMapLatest {
|
||||||
session.getPagedRoomSummariesLive(
|
session.getPagedRoomSummariesLive(
|
||||||
roomSummaryQueryParams {
|
roomSummaryQueryParams {
|
||||||
this.memberships = Membership.activeMemberships()
|
this.memberships = Membership.activeMemberships()
|
||||||
|
|
|
@ -2256,7 +2256,7 @@ class TimelineFragment @Inject constructor(
|
||||||
userId == session.myUserId) {
|
userId == session.myUserId) {
|
||||||
// Empty composer, current user: start an emote
|
// Empty composer, current user: start an emote
|
||||||
views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
|
views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
|
||||||
views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.length)
|
views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1)
|
||||||
} else {
|
} else {
|
||||||
val roomMember = timelineViewModel.getMember(userId)
|
val roomMember = timelineViewModel.getMember(userId)
|
||||||
// TODO move logic outside of fragment
|
// TODO move logic outside of fragment
|
||||||
|
|
|
@ -33,7 +33,6 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.flow.chunk
|
|
||||||
import im.vector.app.core.mvrx.runCatchingToAsync
|
import im.vector.app.core.mvrx.runCatchingToAsync
|
||||||
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
|
||||||
|
@ -55,6 +54,7 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorDataStore
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import im.vector.lib.core.utils.flow.chunk
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
|
@ -183,7 +183,9 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
withState { state ->
|
withState { state ->
|
||||||
when (state.sendMode) {
|
when (state.sendMode) {
|
||||||
is SendMode.Regular -> {
|
is SendMode.Regular -> {
|
||||||
when (val slashCommandResult = CommandParser.parseSplashCommand(action.text, state.isInThreadTimeline())) {
|
when (val slashCommandResult = CommandParser.parseSplashCommand(
|
||||||
|
textMessage = action.text,
|
||||||
|
isInThreadTimeline = state.isInThreadTimeline())) {
|
||||||
is ParsedCommand.ErrorNotACommand -> {
|
is ParsedCommand.ErrorNotACommand -> {
|
||||||
// Send the text message to the room
|
// Send the text message to the room
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
|
@ -257,8 +259,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.UnignoreUser -> {
|
is ParsedCommand.UnignoreUser -> {
|
||||||
handleUnignoreSlashCommand(slashCommandResult)
|
handleUnignoreSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
is ParsedCommand.KickUser -> {
|
is ParsedCommand.RemoveUser -> {
|
||||||
handleKickSlashCommand(slashCommandResult)
|
handleRemoveSlashCommand(slashCommandResult)
|
||||||
}
|
}
|
||||||
is ParsedCommand.JoinRoom -> {
|
is ParsedCommand.JoinRoom -> {
|
||||||
handleJoinToAnotherRoomSlashCommand(slashCommandResult)
|
handleJoinToAnotherRoomSlashCommand(slashCommandResult)
|
||||||
|
@ -625,7 +627,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
launchSlashCommandFlowSuspendable {
|
launchSlashCommandFlowSuspendable {
|
||||||
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent)
|
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,9 +651,9 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleKickSlashCommand(kick: ParsedCommand.KickUser) {
|
private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
|
||||||
launchSlashCommandFlowSuspendable {
|
launchSlashCommandFlowSuspendable {
|
||||||
room.kick(kick.userId, kick.reason)
|
room.remove(removeUser.userId, removeUser.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -692,7 +694,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
||||||
launchSlashCommandFlowSuspendable {
|
launchSlashCommandFlowSuspendable {
|
||||||
room.sendStateEvent(EventType.STATE_ROOM_AVATAR, null, RoomAvatarContent(changeAvatar.url).toContent())
|
room.sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,11 @@ import android.media.AudioAttributes
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import im.vector.app.BuildConfig
|
import im.vector.app.BuildConfig
|
||||||
import im.vector.app.core.utils.CountUpTimer
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
||||||
import im.vector.app.features.voice.VoiceFailure
|
import im.vector.app.features.voice.VoiceFailure
|
||||||
import im.vector.app.features.voice.VoiceRecorder
|
import im.vector.app.features.voice.VoiceRecorder
|
||||||
import im.vector.app.features.voice.VoiceRecorderProvider
|
import im.vector.app.features.voice.VoiceRecorderProvider
|
||||||
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
import im.vector.lib.multipicker.entity.MultiPickerAudioType
|
||||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
|
|
@ -26,10 +26,10 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.hardware.vibrate
|
import im.vector.app.core.hardware.vibrate
|
||||||
import im.vector.app.core.time.Clock
|
import im.vector.app.core.time.Clock
|
||||||
import im.vector.app.core.utils.CountUpTimer
|
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
|
import im.vector.app.databinding.ViewVoiceMessageRecorderBinding
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
||||||
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ class RoomListSectionBuilderGroup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appStateHandler.selectedRoomGroupingObservable
|
appStateHandler.selectedRoomGroupingFlow
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach { groupingMethod ->
|
.onEach { groupingMethod ->
|
||||||
val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
|
val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
|
||||||
|
|
|
@ -132,7 +132,7 @@ class RoomListSectionBuilderSpace(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appStateHandler.selectedRoomGroupingObservable
|
appStateHandler.selectedRoomGroupingFlow
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach { groupingMethod ->
|
.onEach { groupingMethod ->
|
||||||
val selectedSpace = groupingMethod.orNull()?.space()
|
val selectedSpace = groupingMethod.orNull()?.space()
|
||||||
|
@ -222,7 +222,7 @@ class RoomListSectionBuilderSpace(
|
||||||
|
|
||||||
// add suggested rooms
|
// add suggested rooms
|
||||||
val suggestedRoomsFlow = // MutableLiveData<List<SpaceChildInfo>>()
|
val suggestedRoomsFlow = // MutableLiveData<List<SpaceChildInfo>>()
|
||||||
appStateHandler.selectedRoomGroupingObservable
|
appStateHandler.selectedRoomGroupingFlow
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest { groupingMethod ->
|
.flatMapLatest { groupingMethod ->
|
||||||
val selectedSpace = groupingMethod.orNull()?.space()
|
val selectedSpace = groupingMethod.orNull()?.space()
|
||||||
|
|
|
@ -92,7 +92,7 @@ class RoomListViewModel @AssistedInject constructor(
|
||||||
init {
|
init {
|
||||||
observeMembershipChanges()
|
observeMembershipChanges()
|
||||||
|
|
||||||
appStateHandler.selectedRoomGroupingObservable
|
appStateHandler.selectedRoomGroupingFlow
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.execute {
|
.execute {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
@ -317,8 +317,8 @@ class DefaultNavigator @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openCreateRoom(context: Context, initialName: String) {
|
override fun openCreateRoom(context: Context, initialName: String, openAfterCreate: Boolean) {
|
||||||
val intent = CreateRoomActivity.getIntent(context, initialName)
|
val intent = CreateRoomActivity.getIntent(context = context, initialName = initialName, openAfterCreate = openAfterCreate)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ interface Navigator {
|
||||||
|
|
||||||
fun openMatrixToBottomSheet(context: Context, link: String)
|
fun openMatrixToBottomSheet(context: Context, link: String)
|
||||||
|
|
||||||
fun openCreateRoom(context: Context, initialName: String = "")
|
fun openCreateRoom(context: Context, initialName: String = "", openAfterCreate: Boolean = true)
|
||||||
|
|
||||||
fun openCreateDirectRoom(context: Context)
|
fun openCreateDirectRoom(context: Context)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,14 +14,11 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.rx
|
package im.vector.app.features.onboarding
|
||||||
|
|
||||||
data class SecretsSynchronisationInfo(
|
enum class FtueUseCase {
|
||||||
val isBackupSetup: Boolean,
|
FRIENDS_FAMILY,
|
||||||
val isCrossSigningEnabled: Boolean,
|
TEAMS,
|
||||||
val isCrossSigningTrusted: Boolean,
|
COMMUNITIES,
|
||||||
val allPrivateKeysKnown: Boolean,
|
SKIP
|
||||||
val megolmBackupAvailable: Boolean,
|
}
|
||||||
val megolmSecretKnown: Boolean,
|
|
||||||
val isMegolmKeyIn4S: Boolean
|
|
||||||
)
|
|
|
@ -31,6 +31,8 @@ sealed class OnboardingAction : VectorViewModelAction {
|
||||||
|
|
||||||
data class UpdateServerType(val serverType: ServerType) : OnboardingAction()
|
data class UpdateServerType(val serverType: ServerType) : OnboardingAction()
|
||||||
data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction()
|
data class UpdateHomeServer(val homeServerUrl: String) : OnboardingAction()
|
||||||
|
data class UpdateUseCase(val useCase: FtueUseCase) : OnboardingAction()
|
||||||
|
object ResetUseCase : OnboardingAction()
|
||||||
data class UpdateSignMode(val signMode: SignMode) : OnboardingAction()
|
data class UpdateSignMode(val signMode: SignMode) : OnboardingAction()
|
||||||
data class LoginWithToken(val loginToken: String) : OnboardingAction()
|
data class LoginWithToken(val loginToken: String) : OnboardingAction()
|
||||||
data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction()
|
data class WebLoginSuccess(val credentials: Credentials) : OnboardingAction()
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.onboarding
|
package im.vector.app.features.onboarding
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.ScreenOrientationLocker
|
||||||
import im.vector.app.databinding.ActivityLoginBinding
|
import im.vector.app.databinding.ActivityLoginBinding
|
||||||
import im.vector.app.features.VectorFeatures
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.login2.LoginViewModel2
|
import im.vector.app.features.login2.LoginViewModel2
|
||||||
|
@ -24,6 +25,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class OnboardingVariantFactory @Inject constructor(
|
class OnboardingVariantFactory @Inject constructor(
|
||||||
private val vectorFeatures: VectorFeatures,
|
private val vectorFeatures: VectorFeatures,
|
||||||
|
private val orientationLocker: ScreenOrientationLocker,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(activity: OnboardingActivity,
|
fun create(activity: OnboardingActivity,
|
||||||
|
@ -37,7 +39,8 @@ class OnboardingVariantFactory @Inject constructor(
|
||||||
onboardingViewModel = onboardingViewModel.value,
|
onboardingViewModel = onboardingViewModel.value,
|
||||||
activity = activity,
|
activity = activity,
|
||||||
supportFragmentManager = activity.supportFragmentManager,
|
supportFragmentManager = activity.supportFragmentManager,
|
||||||
vectorFeatures = vectorFeatures
|
vectorFeatures = vectorFeatures,
|
||||||
|
orientationLocker = orientationLocker
|
||||||
)
|
)
|
||||||
VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant(
|
VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant(
|
||||||
views = views,
|
views = views,
|
||||||
|
|
|
@ -34,6 +34,7 @@ sealed class OnboardingViewEvents : VectorViewEvents {
|
||||||
|
|
||||||
// Navigation event
|
// Navigation event
|
||||||
|
|
||||||
|
object OpenUseCaseSelection : OnboardingViewEvents()
|
||||||
object OpenServerSelection : OnboardingViewEvents()
|
object OpenServerSelection : OnboardingViewEvents()
|
||||||
data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents()
|
data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents()
|
||||||
object OnLoginFlowRetrieved : OnboardingViewEvents()
|
object OnLoginFlowRetrieved : OnboardingViewEvents()
|
||||||
|
|
|
@ -35,6 +35,7 @@ import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.utils.ensureTrailingSlash
|
import im.vector.app.core.utils.ensureTrailingSlash
|
||||||
|
import im.vector.app.features.VectorFeatures
|
||||||
import im.vector.app.features.login.HomeServerConnectionConfigFactory
|
import im.vector.app.features.login.HomeServerConnectionConfigFactory
|
||||||
import im.vector.app.features.login.LoginConfig
|
import im.vector.app.features.login.LoginConfig
|
||||||
import im.vector.app.features.login.LoginMode
|
import im.vector.app.features.login.LoginMode
|
||||||
|
@ -71,7 +72,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
||||||
private val reAuthHelper: ReAuthHelper,
|
private val reAuthHelper: ReAuthHelper,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val homeServerHistoryService: HomeServerHistoryService
|
private val homeServerHistoryService: HomeServerHistoryService,
|
||||||
|
private val vectorFeatures: VectorFeatures
|
||||||
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
|
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -123,6 +125,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
when (action) {
|
when (action) {
|
||||||
is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
|
is OnboardingAction.OnGetStarted -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
|
||||||
is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
|
is OnboardingAction.OnIAlreadyHaveAnAccount -> handleSplashAction(action.resetLoginConfig, action.onboardingFlow)
|
||||||
|
is OnboardingAction.UpdateUseCase -> handleUpdateUseCase()
|
||||||
|
OnboardingAction.ResetUseCase -> resetUseCase()
|
||||||
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
|
is OnboardingAction.UpdateServerType -> handleUpdateServerType(action)
|
||||||
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
|
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||||
is OnboardingAction.InitWith -> handleInitWith(action)
|
is OnboardingAction.InitWith -> handleInitWith(action)
|
||||||
|
@ -154,15 +158,28 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
if (homeServerConnectionConfig == null) {
|
if (homeServerConnectionConfig == null) {
|
||||||
// Url is invalid, in this case, just use the regular flow
|
// Url is invalid, in this case, just use the regular flow
|
||||||
Timber.w("Url from config url was invalid: $configUrl")
|
Timber.w("Url from config url was invalid: $configUrl")
|
||||||
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
continueToPageAfterSplash(onboardingFlow)
|
||||||
} else {
|
} else {
|
||||||
getLoginFlow(homeServerConnectionConfig, ServerType.Other)
|
getLoginFlow(homeServerConnectionConfig, ServerType.Other)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
continueToPageAfterSplash(onboardingFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
|
||||||
|
val nextOnboardingStep = when (onboardingFlow) {
|
||||||
|
OnboardingFlow.SignUp -> if (vectorFeatures.isOnboardingUseCaseEnabled()) {
|
||||||
|
OnboardingViewEvents.OpenUseCaseSelection
|
||||||
|
} else {
|
||||||
|
OnboardingViewEvents.OpenServerSelection
|
||||||
|
}
|
||||||
|
OnboardingFlow.SignIn,
|
||||||
|
OnboardingFlow.SignInSignUp -> OnboardingViewEvents.OpenServerSelection
|
||||||
|
}
|
||||||
|
_viewEvents.post(nextOnboardingStep)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) {
|
private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) {
|
||||||
// It happens when we get the login flow, or during direct authentication.
|
// It happens when we get the login flow, or during direct authentication.
|
||||||
// So alter the homeserver config and retrieve again the login flow
|
// So alter the homeserver config and retrieve again the login flow
|
||||||
|
@ -441,6 +458,15 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleUpdateUseCase() {
|
||||||
|
// TODO act on the use case selection
|
||||||
|
_viewEvents.post(OnboardingViewEvents.OpenServerSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetUseCase() {
|
||||||
|
// TODO remove stored use case
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) {
|
private fun handleUpdateServerType(action: OnboardingAction.UpdateServerType) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
@ -49,7 +49,8 @@ private const val CAROUSEL_TRANSITION_TIME_MS = 500L
|
||||||
class FtueAuthSplashCarouselFragment @Inject constructor(
|
class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
private val vectorPreferences: VectorPreferences,
|
private val vectorPreferences: VectorPreferences,
|
||||||
private val vectorFeatures: VectorFeatures,
|
private val vectorFeatures: VectorFeatures,
|
||||||
private val carouselController: SplashCarouselController
|
private val carouselController: SplashCarouselController,
|
||||||
|
private val carouselStateFactory: SplashCarouselStateFactory
|
||||||
) : AbstractFtueAuthFragment<FragmentFtueSplashCarouselBinding>() {
|
) : AbstractFtueAuthFragment<FragmentFtueSplashCarouselBinding>() {
|
||||||
|
|
||||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueSplashCarouselBinding {
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueSplashCarouselBinding {
|
||||||
|
@ -65,11 +66,11 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
val carouselAdapter = carouselController.adapter
|
val carouselAdapter = carouselController.adapter
|
||||||
views.splashCarousel.adapter = carouselAdapter
|
views.splashCarousel.adapter = carouselAdapter
|
||||||
TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach()
|
TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach()
|
||||||
carouselController.setData(SplashCarouselState())
|
carouselController.setData(carouselStateFactory.create())
|
||||||
|
|
||||||
views.loginSplashSubmit.debouncedClicks { getStarted() }
|
views.loginSplashSubmit.debouncedClicks { getStarted() }
|
||||||
views.loginSplashAlreadyHaveAccount.apply {
|
views.loginSplashAlreadyHaveAccount.apply {
|
||||||
isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled()
|
isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()
|
||||||
debouncedClicks { alreadyHaveAnAccount() }
|
debouncedClicks { alreadyHaveAnAccount() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,17 +81,26 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
"Branch: ${BuildConfig.GIT_BRANCH_NAME}"
|
"Branch: ${BuildConfig.GIT_BRANCH_NAME}"
|
||||||
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
|
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
|
||||||
}
|
}
|
||||||
|
views.splashCarousel.registerAutomaticUntilInteractionTransitions()
|
||||||
|
}
|
||||||
|
|
||||||
views.splashCarousel.apply {
|
private fun ViewPager2.registerAutomaticUntilInteractionTransitions() {
|
||||||
var scheduledTransition: Job? = null
|
var scheduledTransition: Job? = null
|
||||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
private var hasUserManuallyInteractedWithCarousel: Boolean = false
|
||||||
scheduledTransition?.cancel()
|
|
||||||
|
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||||
|
hasUserManuallyInteractedWithCarousel = !isFakeDragging
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
scheduledTransition?.cancel()
|
||||||
|
// only schedule automatic transitions whilst the user has not interacted with the carousel
|
||||||
|
if (!hasUserManuallyInteractedWithCarousel) {
|
||||||
scheduledTransition = scheduleCarouselTransition()
|
scheduledTransition = scheduleCarouselTransition()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
scheduledTransition = scheduleCarouselTransition()
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ViewPager2.scheduleCarouselTransition(): Job {
|
private fun ViewPager2.scheduleCarouselTransition(): Job {
|
||||||
|
@ -102,7 +112,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStarted() {
|
private fun getStarted() {
|
||||||
val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
val getStartedFlow = if (vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
||||||
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
|
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ class FtueAuthSplashFragment @Inject constructor(
|
||||||
private fun setupViews() {
|
private fun setupViews() {
|
||||||
views.loginSplashSubmit.debouncedClicks { getStarted() }
|
views.loginSplashSubmit.debouncedClicks { getStarted() }
|
||||||
views.loginSplashAlreadyHaveAccount.apply {
|
views.loginSplashAlreadyHaveAccount.apply {
|
||||||
isVisible = vectorFeatures.isAlreadyHaveAccountSplashEnabled()
|
isVisible = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()
|
||||||
debouncedClicks { alreadyHaveAnAccount() }
|
debouncedClicks { alreadyHaveAnAccount() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ class FtueAuthSplashFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStarted() {
|
private fun getStarted() {
|
||||||
val getStartedFlow = if (vectorFeatures.isAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
val getStartedFlow = if (vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()) OnboardingFlow.SignUp else OnboardingFlow.SignInSignUp
|
||||||
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
|
viewModel.handle(OnboardingAction.OnGetStarted(resetLoginConfig = false, onboardingFlow = getStartedFlow))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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.onboarding.ftueauth
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.setTextWithColoredPart
|
||||||
|
import im.vector.app.databinding.FragmentFtueAuthUseCaseBinding
|
||||||
|
import im.vector.app.features.login.ServerType
|
||||||
|
import im.vector.app.features.onboarding.FtueUseCase
|
||||||
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class FtueAuthUseCaseFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentFtueAuthUseCaseBinding>() {
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthUseCaseBinding {
|
||||||
|
return FragmentFtueAuthUseCaseBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViews() {
|
||||||
|
views.useCaseOptionOne.setUseCase(R.string.ftue_auth_use_case_option_one, FtueUseCase.FRIENDS_FAMILY)
|
||||||
|
views.useCaseOptionTwo.setUseCase(R.string.ftue_auth_use_case_option_two, FtueUseCase.TEAMS)
|
||||||
|
views.useCaseOptionThree.setUseCase(R.string.ftue_auth_use_case_option_three, FtueUseCase.COMMUNITIES)
|
||||||
|
|
||||||
|
views.useCaseSkip.setTextWithColoredPart(
|
||||||
|
fullTextRes = R.string.ftue_auth_use_case_skip,
|
||||||
|
coloredTextRes = R.string.ftue_auth_use_case_skip_partial,
|
||||||
|
underline = false,
|
||||||
|
colorAttribute = R.attr.colorAccent,
|
||||||
|
onClick = { viewModel.handle(OnboardingAction.UpdateUseCase(FtueUseCase.SKIP)) }
|
||||||
|
)
|
||||||
|
|
||||||
|
views.useCaseConnectToServer.setOnClickListener {
|
||||||
|
viewModel.handle(OnboardingAction.UpdateServerType(ServerType.Other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetViewModel() {
|
||||||
|
viewModel.handle(OnboardingAction.ResetUseCase)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TextView.setUseCase(@StringRes label: Int, useCase: FtueUseCase) {
|
||||||
|
setText(label)
|
||||||
|
debouncedClicks {
|
||||||
|
viewModel.handle(OnboardingAction.UpdateUseCase(useCase))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.app.features.onboarding.ftueauth
|
package im.vector.app.features.onboarding.ftueauth
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -31,6 +32,7 @@ import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
|
||||||
import im.vector.app.core.extensions.addFragment
|
import im.vector.app.core.extensions.addFragment
|
||||||
import im.vector.app.core.extensions.addFragmentToBackstack
|
import im.vector.app.core.extensions.addFragmentToBackstack
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
|
import im.vector.app.core.platform.ScreenOrientationLocker
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivityLoginBinding
|
import im.vector.app.databinding.ActivityLoginBinding
|
||||||
import im.vector.app.features.VectorFeatures
|
import im.vector.app.features.VectorFeatures
|
||||||
|
@ -62,7 +64,8 @@ class FtueAuthVariant(
|
||||||
private val onboardingViewModel: OnboardingViewModel,
|
private val onboardingViewModel: OnboardingViewModel,
|
||||||
private val activity: VectorBaseActivity<ActivityLoginBinding>,
|
private val activity: VectorBaseActivity<ActivityLoginBinding>,
|
||||||
private val supportFragmentManager: FragmentManager,
|
private val supportFragmentManager: FragmentManager,
|
||||||
private val vectorFeatures: VectorFeatures
|
private val vectorFeatures: VectorFeatures,
|
||||||
|
private val orientationLocker: ScreenOrientationLocker,
|
||||||
) : OnboardingVariant {
|
) : OnboardingVariant {
|
||||||
|
|
||||||
private val enterAnim = R.anim.enter_fade_in
|
private val enterAnim = R.anim.enter_fade_in
|
||||||
|
@ -91,6 +94,7 @@ class FtueAuthVariant(
|
||||||
}
|
}
|
||||||
|
|
||||||
with(activity) {
|
with(activity) {
|
||||||
|
orientationLocker.lockPhonesToPortrait(this)
|
||||||
onboardingViewModel.onEach {
|
onboardingViewModel.onEach {
|
||||||
updateWithState(it)
|
updateWithState(it)
|
||||||
}
|
}
|
||||||
|
@ -109,7 +113,7 @@ class FtueAuthVariant(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addFirstFragment() {
|
private fun addFirstFragment() {
|
||||||
val splashFragment = when (vectorFeatures.isSplashCarouselEnabled()) {
|
val splashFragment = when (vectorFeatures.isOnboardingSplashCarouselEnabled()) {
|
||||||
true -> FtueAuthSplashCarouselFragment::class.java
|
true -> FtueAuthSplashCarouselFragment::class.java
|
||||||
else -> FtueAuthSplashFragment::class.java
|
else -> FtueAuthSplashFragment::class.java
|
||||||
}
|
}
|
||||||
|
@ -151,13 +155,16 @@ class FtueAuthVariant(
|
||||||
activity.addFragmentToBackstack(views.loginFragmentContainer,
|
activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||||
FtueAuthServerSelectionFragment::class.java,
|
FtueAuthServerSelectionFragment::class.java,
|
||||||
option = { ft ->
|
option = { ft ->
|
||||||
activity.findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
if (vectorFeatures.isOnboardingUseCaseEnabled()) {
|
||||||
// Disable transition of text
|
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||||
// findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
} else {
|
||||||
// No transition here now actually
|
activity.findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
// findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
// TODO Disabled because it provokes a flickering
|
||||||
// TODO Disabled because it provokes a flickering
|
// Disable transition of text
|
||||||
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
// findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
|
// No transition here now actually
|
||||||
|
// findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(viewEvents)
|
is OnboardingViewEvents.OnServerSelectionDone -> onServerSelectionDone(viewEvents)
|
||||||
is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(viewEvents)
|
is OnboardingViewEvents.OnSignModeSelected -> onSignModeSelected(viewEvents)
|
||||||
|
@ -208,6 +215,11 @@ class FtueAuthVariant(
|
||||||
is OnboardingViewEvents.Loading ->
|
is OnboardingViewEvents.Loading ->
|
||||||
// This is handled by the Fragments
|
// This is handled by the Fragments
|
||||||
Unit
|
Unit
|
||||||
|
OnboardingViewEvents.OpenUseCaseSelection -> {
|
||||||
|
activity.addFragmentToBackstack(views.loginFragmentContainer,
|
||||||
|
FtueAuthUseCaseFragment::class.java,
|
||||||
|
option = commonOption)
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ abstract class SplashCarouselItem : VectorEpoxyModel<SplashCarouselItem.Holder>(
|
||||||
|
|
||||||
holder.view.setBackgroundResource(item.pageBackground)
|
holder.view.setBackgroundResource(item.pageBackground)
|
||||||
holder.image.setImageResource(item.image)
|
holder.image.setImageResource(item.image)
|
||||||
holder.title.setText(item.title)
|
holder.title.text = item.title.charSequence
|
||||||
holder.body.setText(item.body)
|
holder.body.setText(item.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,38 +18,13 @@ package im.vector.app.features.onboarding.ftueauth
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import im.vector.app.R
|
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||||
|
|
||||||
data class SplashCarouselState(
|
data class SplashCarouselState(
|
||||||
val items: List<Item> = listOf(
|
val items: List<Item>
|
||||||
Item(
|
|
||||||
R.string.ftue_auth_carousel_1_title,
|
|
||||||
R.string.ftue_auth_carousel_1_body,
|
|
||||||
R.drawable.onboarding_carousel_conversations,
|
|
||||||
R.drawable.bg_carousel_page_1
|
|
||||||
),
|
|
||||||
Item(
|
|
||||||
R.string.ftue_auth_carousel_2_title,
|
|
||||||
R.string.ftue_auth_carousel_2_body,
|
|
||||||
R.drawable.onboarding_carousel_ems,
|
|
||||||
R.drawable.bg_carousel_page_2
|
|
||||||
),
|
|
||||||
Item(
|
|
||||||
R.string.ftue_auth_carousel_3_title,
|
|
||||||
R.string.ftue_auth_carousel_3_body,
|
|
||||||
R.drawable.onboarding_carousel_connect,
|
|
||||||
R.drawable.bg_carousel_page_3
|
|
||||||
),
|
|
||||||
Item(
|
|
||||||
R.string.ftue_auth_carousel_4_title,
|
|
||||||
R.string.ftue_auth_carousel_4_body,
|
|
||||||
R.drawable.onboarding_carousel_universal,
|
|
||||||
R.drawable.bg_carousel_page_4
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
data class Item(
|
data class Item(
|
||||||
@StringRes val title: Int,
|
val title: EpoxyCharSequence,
|
||||||
@StringRes val body: Int,
|
@StringRes val body: Int,
|
||||||
@DrawableRes val image: Int,
|
@DrawableRes val image: Int,
|
||||||
@DrawableRes val pageBackground: Int
|
@DrawableRes val pageBackground: Int
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue