mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Merge pull request #4363 from vector-im/feature/fga/rx_flow_migration
Feature/fga/rx flow migration
This commit is contained in:
commit
f3655d4664
117 changed files with 1101 additions and 1003 deletions
1
changelog.d/4219.misc
Normal file
1
changelog.d/4219.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Finish migration from RxJava to Flow
|
|
@ -17,7 +17,7 @@ def arrow = "0.8.2"
|
|||
def markwon = "4.6.2"
|
||||
def moshi = "1.12.0"
|
||||
def lifecycle = "2.2.0"
|
||||
def rxBinding = "3.1.0"
|
||||
def flowBinding = "1.2.0"
|
||||
def epoxy = "4.6.2"
|
||||
def mavericks = "2.4.0"
|
||||
def glide = "4.12.0"
|
||||
|
@ -41,7 +41,8 @@ ext.libs = [
|
|||
jetbrains : [
|
||||
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
|
||||
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
|
||||
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines"
|
||||
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines",
|
||||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||
],
|
||||
androidx : [
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.3.1",
|
||||
|
@ -102,7 +103,6 @@ ext.libs = [
|
|||
'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy",
|
||||
'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy",
|
||||
'mavericks' : "com.airbnb.android:mavericks:$mavericks",
|
||||
'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks",
|
||||
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
||||
],
|
||||
mockk : [
|
||||
|
@ -115,13 +115,13 @@ ext.libs = [
|
|||
'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer",
|
||||
'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer",
|
||||
'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
|
||||
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer"
|
||||
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer",
|
||||
'flowBinding' : "io.github.reactivecircus.flowbinding:flowbinding-android:$flowBinding",
|
||||
'flowBindingAppcompat' : "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowBinding",
|
||||
'flowBindingMaterial' : "io.github.reactivecircus.flowbinding:flowbinding-material:$flowBinding"
|
||||
],
|
||||
jakewharton : [
|
||||
'timber' : "com.jakewharton.timber:timber:5.0.1",
|
||||
'rxbinding' : "com.jakewharton.rxbinding3:rxbinding:$rxBinding",
|
||||
'rxbindingAppcompat' : "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxBinding",
|
||||
'rxbindingMaterial' : "com.jakewharton.rxbinding3:rxbinding-material:$rxBinding"
|
||||
'timber' : "com.jakewharton.timber:timber:5.0.1"
|
||||
],
|
||||
jsonwebtoken: [
|
||||
'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt",
|
||||
|
|
41
docs/rx_flow_migration.md
Normal file
41
docs/rx_flow_migration.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
Useful links:
|
||||
- https://github.com/ReactiveCircus/FlowBinding
|
||||
- https://ivanisidrowu.github.io/kotlin/2020/08/09/Kotlin-Flow-Migration-And-Testing.html
|
||||
|
||||
|
||||
Rx is now completely removed from Element dependencies.
|
||||
Some examples of the changes:
|
||||
|
||||
```
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { handleQuickActions(it) }
|
||||
.disposeOnDestroyView()
|
||||
```
|
||||
|
||||
became
|
||||
|
||||
```
|
||||
sharedActionViewModel
|
||||
.stream()
|
||||
.onEach { handleQuickActions(it) }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
```
|
||||
|
||||
Inside fragment use
|
||||
```
|
||||
launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
```
|
||||
Inside activity use
|
||||
```
|
||||
launchIn(lifecycleScope)
|
||||
```
|
||||
Inside viewModel use
|
||||
```
|
||||
launchIn(viewModelScope)
|
||||
```
|
||||
|
||||
Also be aware that when using these scopes the coroutine is launched on Dispatchers.Main by default.
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ 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.ThreePid
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
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
|
||||
|
@ -44,10 +45,10 @@ import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
|||
|
||||
class FlowSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>> {
|
||||
return session.getRoomSummariesLive(queryParams).asFlow()
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow<List<RoomSummary>> {
|
||||
return session.getRoomSummariesLive(queryParams, sortOrder).asFlow()
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
session.getRoomSummaries(queryParams)
|
||||
session.getRoomSummaries(queryParams, sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,6 @@ def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
|
|||
android {
|
||||
|
||||
|
||||
|
||||
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
|
||||
// Ref: https://issuetracker.google.com/issues/144111441
|
||||
ndkVersion "21.3.6528147"
|
||||
|
@ -333,7 +332,6 @@ configurations {
|
|||
dependencies {
|
||||
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation project(":matrix-sdk-android-rx")
|
||||
implementation project(":matrix-sdk-android-flow")
|
||||
implementation project(":diff-match-patch")
|
||||
implementation project(":multipicker")
|
||||
|
@ -374,23 +372,16 @@ dependencies {
|
|||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.36'
|
||||
|
||||
// rx
|
||||
implementation libs.rx.rxKotlin
|
||||
implementation libs.rx.rxAndroid
|
||||
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.1'
|
||||
// RXBinding
|
||||
implementation libs.jakewharton.rxbinding
|
||||
implementation libs.jakewharton.rxbindingAppcompat
|
||||
implementation libs.jakewharton.rxbindingMaterial
|
||||
// FlowBinding
|
||||
implementation libs.github.flowBinding
|
||||
implementation libs.github.flowBindingAppcompat
|
||||
implementation libs.github.flowBindingMaterial
|
||||
|
||||
implementation libs.airbnb.epoxy
|
||||
implementation libs.airbnb.epoxyGlide
|
||||
kapt libs.airbnb.epoxyProcessor
|
||||
implementation libs.airbnb.epoxyPaging
|
||||
implementation libs.airbnb.mavericks
|
||||
//TODO: remove when entirely migrated to Flow
|
||||
implementation libs.airbnb.mavericksRx
|
||||
|
||||
|
||||
// Work
|
||||
implementation libs.androidx.work
|
||||
|
@ -471,7 +462,7 @@ dependencies {
|
|||
gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||
|
||||
implementation "androidx.emoji:emoji-appcompat:1.1.0"
|
||||
implementation ('com.github.BillCarsonFr:JsonViewer:0.7')
|
||||
implementation('com.github.BillCarsonFr:JsonViewer:0.7')
|
||||
|
||||
// WebRTC
|
||||
// org.webrtc:google-webrtc is for development purposes only
|
||||
|
@ -512,6 +503,9 @@ dependencies {
|
|||
// Plant Timber tree for test
|
||||
testImplementation libs.tests.timberJunitRule
|
||||
testImplementation libs.airbnb.mavericksTesting
|
||||
testImplementation(libs.jetbrains.coroutinesTest) {
|
||||
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
|
||||
}
|
||||
|
||||
// Activate when you want to check for leaks, from time to time.
|
||||
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
|
||||
|
@ -525,6 +519,9 @@ dependencies {
|
|||
androidTestImplementation libs.androidx.espressoIntents
|
||||
androidTestImplementation libs.tests.kluent
|
||||
androidTestImplementation libs.androidx.coreTesting
|
||||
androidTestImplementation(libs.jetbrains.coroutinesTest) {
|
||||
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
|
||||
}
|
||||
// Plant Timber tree for test
|
||||
androidTestImplementation libs.tests.timberJunitRule
|
||||
// "The one who serves a great Espresso"
|
||||
|
|
|
@ -279,25 +279,9 @@ SOFTWARE.
|
|||
Copyright 2012 The Dagger Authors
|
||||
</li>
|
||||
<li>
|
||||
<b>rxkotlin</b>
|
||||
<b>FlowBinding</b>
|
||||
<br/>
|
||||
Copyright io.reactivex.
|
||||
</li>
|
||||
<li>
|
||||
<b>rxandroid</b>
|
||||
<br/>
|
||||
Copyright io.reactivex.
|
||||
</li>
|
||||
<li>
|
||||
<b>rxrelay</b>
|
||||
<br/>
|
||||
Copyright 2014 Netflix, Inc.
|
||||
Copyright 2015 Jake Wharton
|
||||
</li>
|
||||
<li>
|
||||
<b>rxbinding</b>
|
||||
<br/>
|
||||
Copyright (C) 2015 Jake Wharton
|
||||
Copyright 2019 Yang Chen
|
||||
</li>
|
||||
<li>
|
||||
<b>Epoxy</b>
|
||||
|
|
|
@ -24,8 +24,13 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.utils.BehaviorDataSource
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -54,10 +59,10 @@ class AppStateHandler @Inject constructor(
|
|||
private val activeSessionHolder: ActiveSessionHolder
|
||||
) : LifecycleObserver {
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomGroupingMethod>>(Option.empty())
|
||||
|
||||
val selectedRoomGroupingObservable = selectedSpaceDataSource.observe()
|
||||
val selectedRoomGroupingObservable = selectedSpaceDataSource.stream()
|
||||
|
||||
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
||||
// XXX we should somehow make it live :/ just a work around
|
||||
|
@ -105,9 +110,9 @@ class AppStateHandler @Inject constructor(
|
|||
}
|
||||
|
||||
private fun observeActiveSession() {
|
||||
sessionDataSource.observe()
|
||||
sessionDataSource.stream()
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
// sessionDataSource could already return a session while activeSession holder still returns null
|
||||
it.orNull()?.let { session ->
|
||||
if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) {
|
||||
|
@ -116,9 +121,8 @@ class AppStateHandler @Inject constructor(
|
|||
setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
compositeDisposable.add(it)
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
fun safeActiveSpaceId(): String? {
|
||||
|
@ -136,7 +140,7 @@ class AppStateHandler @Inject constructor(
|
|||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun entersBackground() {
|
||||
compositeDisposable.clear()
|
||||
coroutineScope.coroutineContext.cancelChildren()
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) {
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
|
|
|
@ -43,7 +43,6 @@ import dagger.hilt.android.HiltAndroidApp
|
|||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.configureAndStart
|
||||
import im.vector.app.core.extensions.startSyncing
|
||||
import im.vector.app.core.rx.RxConfig
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.configuration.VectorConfiguration
|
||||
import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog
|
||||
|
@ -93,7 +92,6 @@ class VectorApplication :
|
|||
@Inject lateinit var versionProvider: VersionProvider
|
||||
@Inject lateinit var notificationUtils: NotificationUtils
|
||||
@Inject lateinit var appStateHandler: AppStateHandler
|
||||
@Inject lateinit var rxConfig: RxConfig
|
||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||
@Inject lateinit var pinLocker: PinLocker
|
||||
@Inject lateinit var callManager: WebRtcCallManager
|
||||
|
@ -118,7 +116,6 @@ class VectorApplication :
|
|||
appContext = this
|
||||
invitesAcceptor.initialize()
|
||||
vectorUncaughtExceptionHandler.activate(this)
|
||||
rxConfig.setupRxPlugin()
|
||||
|
||||
// Remove Log handler statically added by Jitsi
|
||||
Timber.forest()
|
||||
|
|
|
@ -111,8 +111,8 @@ import im.vector.app.features.roomprofile.members.RoomMemberListFragment
|
|||
import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment
|
||||
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleChooseRestrictedFragment
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleFragment
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment
|
||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
||||
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
|
||||
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
|
||||
|
|
|
@ -137,6 +137,6 @@ object VectorStaticModule {
|
|||
@Provides
|
||||
@JvmStatic
|
||||
fun providesCoroutineDispatchers(): CoroutineDispatchers {
|
||||
return CoroutineDispatchers(io = Dispatchers.IO)
|
||||
return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,4 +19,6 @@ package im.vector.app.core.dispatchers
|
|||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import javax.inject.Inject
|
||||
|
||||
data class CoroutineDispatchers @Inject constructor(val io: CoroutineDispatcher)
|
||||
data class CoroutineDispatchers @Inject constructor(
|
||||
val io: CoroutineDispatcher,
|
||||
val computation: CoroutineDispatcher)
|
||||
|
|
102
vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt
Normal file
102
vector/src/main/java/im/vector/app/core/flow/TimingOperators.kt
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.flow
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.produce
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
fun <T> Flow<T>.chunk(durationInMillis: Long): Flow<List<T>> {
|
||||
require(durationInMillis > 0) { "Duration should be greater than 0" }
|
||||
return flow {
|
||||
coroutineScope {
|
||||
val events = ArrayList<T>()
|
||||
val ticker = fixedPeriodTicker(durationInMillis)
|
||||
try {
|
||||
val upstreamValues = produce(capacity = Channel.CONFLATED) {
|
||||
collect { value -> send(value) }
|
||||
}
|
||||
while (isActive) {
|
||||
var hasTimedOut = false
|
||||
select<Unit> {
|
||||
upstreamValues.onReceive {
|
||||
events.add(it)
|
||||
}
|
||||
ticker.onReceive {
|
||||
hasTimedOut = true
|
||||
}
|
||||
}
|
||||
if (hasTimedOut && events.isNotEmpty()) {
|
||||
emit(events.toList())
|
||||
events.clear()
|
||||
}
|
||||
}
|
||||
} catch (e: ClosedReceiveChannelException) {
|
||||
// drain remaining events
|
||||
if (events.isNotEmpty()) emit(events.toList())
|
||||
} finally {
|
||||
ticker.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
|
||||
var windowStartTime = System.currentTimeMillis()
|
||||
var emitted = false
|
||||
collect { value ->
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val delta = currentTime - windowStartTime
|
||||
if (delta >= windowDuration) {
|
||||
windowStartTime += delta / windowDuration * windowDuration
|
||||
emitted = false
|
||||
}
|
||||
if (!emitted) {
|
||||
emit(value)
|
||||
emitted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun tickerFlow(scope: CoroutineScope, delayMillis: Long, initialDelayMillis: Long = delayMillis): Flow<Unit> {
|
||||
return scope.fixedPeriodTicker(delayMillis, initialDelayMillis).consumeAsFlow()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel<Unit> {
|
||||
require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" }
|
||||
require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" }
|
||||
return produce(capacity = 0) {
|
||||
delay(initialDelayMillis)
|
||||
while (true) {
|
||||
channel.send(Unit)
|
||||
delay(delayMillis)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,12 +39,12 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.FragmentFactory
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.bumptech.glide.util.Util
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
|
@ -59,6 +59,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
|
|||
import im.vector.app.core.extensions.restart
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
|
@ -77,13 +78,12 @@ import im.vector.app.features.settings.VectorPreferences
|
|||
import im.vector.app.features.themes.ActivityOtherThemes
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.receivers.DebugReceiver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView {
|
||||
|
@ -104,13 +104,12 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.stream()
|
||||
.onEach {
|
||||
hideWaitingView()
|
||||
observer(it)
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -119,10 +118,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
|
||||
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||
clicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { onClicked() }
|
||||
.disposeOnDestroy()
|
||||
.throttleFirst(300)
|
||||
.onEach { onClicked() }
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -133,6 +131,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
private lateinit var sessionListener: SessionListener
|
||||
protected lateinit var bugReporter: BugReporter
|
||||
private lateinit var pinLocker: PinLocker
|
||||
|
||||
@Inject
|
||||
lateinit var rageShake: RageShake
|
||||
lateinit var navigator: Navigator
|
||||
|
@ -150,7 +149,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
// For debug only
|
||||
private var debugReceiver: DebugReceiver? = null
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
private val restorables = ArrayList<Restorable>()
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
|
@ -175,10 +173,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
return this
|
||||
}
|
||||
|
||||
protected fun Disposable.disposeOnDestroy() {
|
||||
uiDisposables.add(this)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Timber.i("onCreate Activity ${javaClass.simpleName}")
|
||||
|
@ -302,8 +296,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Timber.i("onDestroy Activity ${javaClass.simpleName}")
|
||||
|
||||
uiDisposables.dispose()
|
||||
}
|
||||
|
||||
private val pinStartForActivityResult = registerStartForActivityResult { activityResult ->
|
||||
|
|
|
@ -26,21 +26,21 @@ import android.view.ViewGroup
|
|||
import android.widget.FrameLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import im.vector.app.core.di.ActivityEntryPoint
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Add Mavericks capabilities, handle DI and bindings.
|
||||
|
@ -108,14 +108,12 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
|
|||
|
||||
@CallSuper
|
||||
override fun onDestroyView() {
|
||||
uiDisposables.clear()
|
||||
_binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroy() {
|
||||
uiDisposables.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -164,27 +162,15 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
|
|||
arguments = args?.let { Bundle().apply { putParcelable(Mavericks.KEY_ARG, it) } }
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Disposable
|
||||
* ========================================================================================== */
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
|
||||
protected fun Disposable.disposeOnDestroyView(): Disposable {
|
||||
uiDisposables.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Views
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||
clicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { onClicked() }
|
||||
.disposeOnDestroyView()
|
||||
.throttleFirst(300)
|
||||
.onEach { onClicked() }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -193,11 +179,10 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
|
|||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.stream()
|
||||
.onEach {
|
||||
observer(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,12 @@ import androidx.annotation.MainThread
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.airbnb.mvrx.MavericksView
|
||||
import com.bumptech.glide.util.Util.assertMainThread
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jakewharton.rxbinding3.view.clicks
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActivityEntryPoint
|
||||
|
@ -42,13 +42,13 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
|||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView {
|
||||
|
||||
|
@ -148,7 +148,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
|||
@CallSuper
|
||||
override fun onDestroyView() {
|
||||
Timber.i("onDestroyView Fragment ${javaClass.simpleName}")
|
||||
uiDisposables.clear()
|
||||
_binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
@ -156,7 +155,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
|||
@CallSuper
|
||||
override fun onDestroy() {
|
||||
Timber.i("onDestroy Fragment ${javaClass.simpleName}")
|
||||
uiDisposables.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -221,29 +219,18 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
|||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Disposable
|
||||
* ========================================================================================== */
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
|
||||
protected fun Disposable.disposeOnDestroyView() {
|
||||
uiDisposables.add(this)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* ViewEvents
|
||||
* ========================================================================================== */
|
||||
|
||||
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
|
||||
viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.stream()
|
||||
.onEach {
|
||||
dismissLoadingDialog()
|
||||
observer(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -252,10 +239,9 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
|||
|
||||
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||
clicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { onClicked() }
|
||||
.disposeOnDestroyView()
|
||||
.throttleFirst(300)
|
||||
.onEach { onClicked() }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
|
|
@ -16,53 +16,17 @@
|
|||
|
||||
package im.vector.app.core.platform
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.BaseMvRxViewModel
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.MavericksViewModel
|
||||
import im.vector.app.core.utils.DataSource
|
||||
import im.vector.app.core.utils.PublishDataSource
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
abstract class VectorViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S) :
|
||||
BaseMvRxViewModel<S>(initialState) {
|
||||
|
||||
interface Factory<S : MavericksState> {
|
||||
fun create(state: S): BaseMvRxViewModel<S>
|
||||
}
|
||||
MavericksViewModel<S>(initialState) {
|
||||
|
||||
// Used to post transient events to the View
|
||||
protected val _viewEvents = PublishDataSource<VE>()
|
||||
val viewEvents: DataSource<VE> = _viewEvents
|
||||
|
||||
/**
|
||||
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
|
||||
* so you can use this in a switchMap or a flatMap
|
||||
*/
|
||||
// False positive
|
||||
@Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER")
|
||||
fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> {
|
||||
setState { stateReducer(Loading()) }
|
||||
return map { Success(it) as Async<T> }
|
||||
.onErrorReturn { Fail(it) }
|
||||
.doOnSuccess { setState { stateReducer(it) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
|
||||
* so you can use this in a switchMap or a flatMap
|
||||
*/
|
||||
// False positive
|
||||
@Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER")
|
||||
fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> {
|
||||
setState { stateReducer(Loading()) }
|
||||
return map { Success(it) as Async<T> }
|
||||
.onErrorReturn { Fail(it) }
|
||||
.doOnNext { setState { stateReducer(it) } }
|
||||
}
|
||||
|
||||
abstract fun handle(action: VA)
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.rx
|
||||
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class RxConfig @Inject constructor(
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) {
|
||||
|
||||
/**
|
||||
* Make sure unhandled Rx error does not crash the app in production
|
||||
*/
|
||||
fun setupRxPlugin() {
|
||||
RxJavaPlugins.setErrorHandler { throwable ->
|
||||
Timber.e(throwable, "RxError")
|
||||
// is InterruptedException -> fine, some blocking code was interrupted by a dispose call
|
||||
if (throwable !is InterruptedException) {
|
||||
// Avoid crash in production, except if user wants it
|
||||
if (vectorPreferences.failFast()) {
|
||||
throw throwable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,23 +16,36 @@
|
|||
|
||||
package im.vector.app.core.utils
|
||||
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import im.vector.app.core.flow.tickerFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class CountUpTimer(private val intervalInMs: Long = 1_000) {
|
||||
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
private val elapsedTime: AtomicLong = AtomicLong()
|
||||
private val resumed: AtomicBoolean = AtomicBoolean(false)
|
||||
|
||||
private val disposable = Observable.interval(intervalInMs / 10, TimeUnit.MILLISECONDS)
|
||||
.filter { resumed.get() }
|
||||
.map { elapsedTime.addAndGet(intervalInMs / 10) }
|
||||
.filter { it % intervalInMs == 0L }
|
||||
.subscribe {
|
||||
tickListener?.onTick(it)
|
||||
}
|
||||
init {
|
||||
startCounter()
|
||||
}
|
||||
|
||||
private fun startCounter() {
|
||||
tickerFlow(coroutineScope, intervalInMs / 10)
|
||||
.filter { resumed.get() }
|
||||
.map { elapsedTime.addAndGet(intervalInMs / 10) }
|
||||
.filter { it % intervalInMs == 0L }
|
||||
.onEach {
|
||||
tickListener?.onTick(it)
|
||||
}.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
var tickListener: TickListener? = null
|
||||
|
||||
|
@ -49,7 +62,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) {
|
|||
}
|
||||
|
||||
fun stop() {
|
||||
disposable.dispose()
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
interface TickListener {
|
||||
|
|
|
@ -16,13 +16,12 @@
|
|||
|
||||
package im.vector.app.core.utils
|
||||
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import com.jakewharton.rxrelay2.PublishRelay
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
interface DataSource<T> {
|
||||
fun observe(): Observable<T>
|
||||
fun stream(): Flow<T>
|
||||
}
|
||||
|
||||
interface MutableDataSource<T> : DataSource<T> {
|
||||
|
@ -34,25 +33,17 @@ interface MutableDataSource<T> : DataSource<T> {
|
|||
*/
|
||||
open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableDataSource<T> {
|
||||
|
||||
private val behaviorRelay = createRelay()
|
||||
private val mutableFlow = MutableSharedFlow<T>(replay = 1)
|
||||
|
||||
val currentValue: T?
|
||||
get() = behaviorRelay.value
|
||||
get() = mutableFlow.replayCache.firstOrNull()
|
||||
|
||||
override fun observe(): Observable<T> {
|
||||
return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread())
|
||||
override fun stream(): Flow<T> {
|
||||
return mutableFlow
|
||||
}
|
||||
|
||||
override fun post(value: T) {
|
||||
behaviorRelay.accept(value!!)
|
||||
}
|
||||
|
||||
private fun createRelay(): BehaviorRelay<T> {
|
||||
return if (defaultValue == null) {
|
||||
BehaviorRelay.create()
|
||||
} else {
|
||||
BehaviorRelay.createDefault(defaultValue)
|
||||
}
|
||||
mutableFlow.tryEmit(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,13 +52,13 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
|
|||
*/
|
||||
open class PublishDataSource<T> : MutableDataSource<T> {
|
||||
|
||||
private val publishRelay = PublishRelay.create<T>()
|
||||
private val mutableFlow = MutableSharedFlow<T>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
override fun observe(): Observable<T> {
|
||||
return publishRelay.hide().observeOn(AndroidSchedulers.mainThread())
|
||||
override fun stream(): Flow<T> {
|
||||
return mutableFlow
|
||||
}
|
||||
|
||||
override fun post(value: T) {
|
||||
publishRelay.accept(value!!)
|
||||
mutableFlow.tryEmit(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.utils
|
||||
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.internal.functions.Functions
|
||||
import timber.log.Timber
|
||||
|
||||
fun <T> Single<T>.subscribeLogError(): Disposable {
|
||||
return subscribe(Functions.emptyConsumer(), { Timber.e(it) })
|
||||
}
|
||||
|
||||
fun Completable.subscribeLogError(): Disposable {
|
||||
return subscribe({}, { Timber.e(it) })
|
||||
}
|
|
@ -39,7 +39,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
callViewModel.subscribe(this) {
|
||||
callViewModel.onEach {
|
||||
renderState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.content.getSystemService
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
|
@ -61,7 +62,8 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
|
|||
import im.vector.app.features.home.room.detail.RoomDetailArgs
|
||||
import io.github.hyuwah.draggableviewlib.DraggableView
|
||||
import io.github.hyuwah.draggableviewlib.setupDraggable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||
|
@ -130,7 +132,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
setSupportActionBar(views.callToolbar)
|
||||
configureCallViews()
|
||||
|
||||
callViewModel.subscribe(this) {
|
||||
callViewModel.onEach {
|
||||
renderState(it)
|
||||
}
|
||||
|
||||
|
@ -141,12 +143,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
|||
}
|
||||
|
||||
callViewModel.viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.stream()
|
||||
.onEach {
|
||||
handleViewEvents(it)
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall ->
|
||||
if (isVideoCall) {
|
||||
|
|
|
@ -68,7 +68,7 @@ class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMee
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
jitsiViewModel.subscribe(this) {
|
||||
jitsiViewModel.onEach {
|
||||
renderState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ package im.vector.app.features.call.webrtc
|
|||
import android.content.Context
|
||||
import android.hardware.camera2.CameraManager
|
||||
import androidx.core.content.getSystemService
|
||||
import im.vector.app.core.flow.chunk
|
||||
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.TextUtils.formatDuration
|
||||
import im.vector.app.features.call.CameraEventsHandlerAdapter
|
||||
import im.vector.app.features.call.CameraProxy
|
||||
|
@ -35,14 +37,16 @@ import im.vector.app.features.call.utils.awaitSetLocalDescription
|
|||
import im.vector.app.features.call.utils.awaitSetRemoteDescription
|
||||
import im.vector.app.features.call.utils.mapToCallCandidate
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import io.reactivex.subjects.ReplaySubject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
|
@ -85,7 +89,6 @@ import org.webrtc.VideoTrack
|
|||
import timber.log.Timber
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Provider
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
|
@ -157,7 +160,7 @@ class WebRtcCall(
|
|||
private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD
|
||||
private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null
|
||||
|
||||
private val timer = CountUpTimer(Duration.ofSeconds(1).toMillis()).apply {
|
||||
private val timer = CountUpTimer(1000L).apply {
|
||||
tickListener = object : CountUpTimer.TickListener {
|
||||
override fun onTick(milliseconds: Long) {
|
||||
val formattedDuration = formatDuration(Duration.ofMillis(milliseconds))
|
||||
|
@ -197,26 +200,33 @@ class WebRtcCall(
|
|||
private var localSurfaceRenderers: MutableList<WeakReference<SurfaceViewRenderer>> = ArrayList()
|
||||
private var remoteSurfaceRenderers: MutableList<WeakReference<SurfaceViewRenderer>> = ArrayList()
|
||||
|
||||
private val iceCandidateSource: PublishSubject<IceCandidate> = PublishSubject.create()
|
||||
private val iceCandidateDisposable = iceCandidateSource
|
||||
.buffer(300, TimeUnit.MILLISECONDS)
|
||||
.subscribe {
|
||||
// omit empty :/
|
||||
if (it.isNotEmpty()) {
|
||||
Timber.tag(loggerTag.value).v("Sending local ice candidates to call")
|
||||
// it.forEach { peerConnection?.addIceCandidate(it) }
|
||||
mxCall.sendLocalCallCandidates(it.mapToCallCandidate())
|
||||
}
|
||||
}
|
||||
private val localIceCandidateSource = PublishDataSource<IceCandidate>()
|
||||
private var localIceCandidateJob: Job? = null
|
||||
|
||||
private val remoteCandidateSource: ReplaySubject<IceCandidate> = ReplaySubject.create()
|
||||
private var remoteIceCandidateDisposable: Disposable? = null
|
||||
private val remoteCandidateSource: MutableSharedFlow<IceCandidate> = MutableSharedFlow(replay = Int.MAX_VALUE)
|
||||
private var remoteIceCandidateJob: Job? = null
|
||||
|
||||
init {
|
||||
setupLocalIceCanditate()
|
||||
mxCall.addListener(this)
|
||||
}
|
||||
|
||||
fun onIceCandidate(iceCandidate: IceCandidate) = iceCandidateSource.onNext(iceCandidate)
|
||||
private fun setupLocalIceCanditate() {
|
||||
sessionScope?.let {
|
||||
localIceCandidateJob = localIceCandidateSource.stream()
|
||||
.chunk(300)
|
||||
.onEach {
|
||||
// omit empty :/
|
||||
if (it.isNotEmpty()) {
|
||||
Timber.tag(loggerTag.value).v("Sending local ice candidates to call")
|
||||
// it.forEach { peerConnection?.addIceCandidate(it) }
|
||||
mxCall.sendLocalCallCandidates(it.mapToCallCandidate())
|
||||
}
|
||||
}.launchIn(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun onIceCandidate(iceCandidate: IceCandidate) = localIceCandidateSource.post(iceCandidate)
|
||||
|
||||
fun onRenegotiationNeeded(restartIce: Boolean) {
|
||||
sessionScope?.launch(dispatcher) {
|
||||
|
@ -438,12 +448,15 @@ class WebRtcCall(
|
|||
createLocalStream()
|
||||
attachViewRenderersInternal()
|
||||
Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource")
|
||||
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
|
||||
Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
|
||||
peerConnection?.addIceCandidate(it)
|
||||
}, {
|
||||
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
|
||||
})
|
||||
remoteIceCandidateJob = remoteCandidateSource
|
||||
.onEach {
|
||||
Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
|
||||
peerConnection?.addIceCandidate(it)
|
||||
}
|
||||
.catch {
|
||||
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
|
||||
}
|
||||
.launchIn(this)
|
||||
// Now we wait for negotiation callback
|
||||
}
|
||||
|
||||
|
@ -488,12 +501,13 @@ class WebRtcCall(
|
|||
mxCall.accept(it.description)
|
||||
}
|
||||
Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource")
|
||||
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
|
||||
Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
|
||||
peerConnection?.addIceCandidate(it)
|
||||
}, {
|
||||
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
|
||||
})
|
||||
remoteIceCandidateJob = remoteCandidateSource
|
||||
.onEach {
|
||||
Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
|
||||
peerConnection?.addIceCandidate(it)
|
||||
}.catch {
|
||||
Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
private suspend fun getTurnServer(): TurnServerResponse? {
|
||||
|
@ -761,8 +775,8 @@ class WebRtcCall(
|
|||
videoCapturer?.stopCapture()
|
||||
videoCapturer?.dispose()
|
||||
videoCapturer = null
|
||||
remoteIceCandidateDisposable?.dispose()
|
||||
iceCandidateDisposable?.dispose()
|
||||
remoteIceCandidateJob?.cancel()
|
||||
localIceCandidateJob?.cancel()
|
||||
peerConnection?.close()
|
||||
peerConnection?.dispose()
|
||||
localAudioSource?.dispose()
|
||||
|
@ -852,7 +866,7 @@ class WebRtcCall(
|
|||
}
|
||||
Timber.tag(loggerTag.value).v("onCallIceCandidateReceived for call ${mxCall.callId} sdp: ${it.candidate}")
|
||||
val iceCandidate = IceCandidate(it.sdpMid, it.sdpMLineIndex, it.candidate)
|
||||
remoteCandidateSource.onNext(iceCandidate)
|
||||
remoteCandidateSource.emit(iceCandidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,9 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.checkedChanges
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
|
@ -37,9 +36,13 @@ import im.vector.app.features.userdirectory.UserListAction
|
|||
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListViewModel
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import java.util.concurrent.TimeUnit
|
||||
import reactivecircus.flowbinding.android.widget.checkedChanges
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class ContactsBookFragment @Inject constructor(
|
||||
|
@ -83,21 +86,21 @@ class ContactsBookFragment @Inject constructor(
|
|||
|
||||
private fun setupOnlyBoundContactsView() {
|
||||
views.phoneBookOnlyBoundContacts.checkedChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun setupFilterView() {
|
||||
views.phoneBookFilter
|
||||
.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.subscribe {
|
||||
.debounce(300)
|
||||
.onEach {
|
||||
contactsBookViewModel.handle(ContactsBookAction.FilterWith(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
|
@ -46,6 +47,8 @@ import im.vector.app.features.userdirectory.UserListFragment
|
|||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
import java.net.HttpURLConnection
|
||||
|
@ -64,8 +67,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
|
||||
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
.stream()
|
||||
.onEach { action ->
|
||||
when (action) {
|
||||
UserListSharedAction.Close -> finish()
|
||||
UserListSharedAction.GoBack -> onBackPressed()
|
||||
|
@ -74,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
|||
UserListSharedAction.AddByQrCode -> openAddByQrCode()
|
||||
}.exhaustive
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
R.id.container,
|
||||
|
|
|
@ -63,7 +63,7 @@ class SharedSecureStorageActivity :
|
|||
|
||||
viewModel.observeViewEvents { observeViewEvents(it) }
|
||||
|
||||
viewModel.subscribe(this) { renderState(it) }
|
||||
viewModel.onEach { renderState(it) }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -22,17 +22,19 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
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.utils.startImportTextFromFileIntent
|
||||
import im.vector.app.databinding.FragmentSsssAccessFromKeyBinding
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import java.util.concurrent.TimeUnit
|
||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment<FragmentSsssAccessFromKeyBinding>() {
|
||||
|
@ -48,22 +50,21 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
|
|||
views.ssssRestoreWithKeyText.text = getString(R.string.enter_secret_storage_input_key)
|
||||
|
||||
views.ssssKeyEnterEdittext.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.throttleFirst(300)
|
||||
.onEach {
|
||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.ssssKeyEnterEdittext.textChanges()
|
||||
.skipInitialValue()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.ssssKeyEnterTil.error = null
|
||||
views.ssssKeySubmit.isEnabled = it.isNotBlank()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.ssssKeyUseFile.debouncedClicks { startImportTextFromFileIntent(requireContext(), importFileStartForActivityResult) }
|
||||
|
||||
|
|
|
@ -22,15 +22,17 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class SharedSecuredStoragePassphraseFragment @Inject constructor(
|
||||
|
@ -60,21 +62,20 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor(
|
|||
// .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink))
|
||||
|
||||
views.ssssPassphraseEnterEdittext.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.throttleFirst(300)
|
||||
.onEach {
|
||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.ssssPassphraseEnterEdittext.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.ssssPassphraseEnterTil.error = null
|
||||
views.ssssPassphraseSubmit.isEnabled = it.isNotBlank()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.ssssPassphraseReset.views.bottomSheetActionClickableZone.debouncedClicks {
|
||||
sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll)
|
||||
|
|
|
@ -55,7 +55,7 @@ class SharedSecuredStorageResetAllFragment @Inject constructor() :
|
|||
}
|
||||
}
|
||||
|
||||
sharedViewModel.subscribe(this) { state ->
|
||||
sharedViewModel.onEach { state ->
|
||||
views.ssssResetOtherDevices.setTextOrHide(
|
||||
state.activeDeviceCount
|
||||
.takeIf { it > 0 }
|
||||
|
|
|
@ -22,16 +22,18 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.view.isGone
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.parentFragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class BootstrapConfirmPassphraseFragment @Inject constructor() :
|
||||
|
@ -58,21 +60,20 @@ class BootstrapConfirmPassphraseFragment @Inject constructor() :
|
|||
}
|
||||
|
||||
views.ssssPassphraseEnterEdittext.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.throttleFirst(300)
|
||||
.onEach {
|
||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.ssssPassphraseEnterEdittext.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.ssssPassphraseEnterTil.error = null
|
||||
sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: ""))
|
||||
sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
sharedViewModel.observeViewEvents {
|
||||
// when (it) {
|
||||
|
|
|
@ -21,16 +21,18 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.parentFragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class BootstrapEnterPassphraseFragment @Inject constructor() :
|
||||
|
@ -53,22 +55,21 @@ class BootstrapEnterPassphraseFragment @Inject constructor() :
|
|||
views.ssssPassphraseEnterEdittext.setText(it.passphrase ?: "")
|
||||
}
|
||||
views.ssssPassphraseEnterEdittext.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.throttleFirst(300)
|
||||
.onEach {
|
||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.ssssPassphraseEnterEdittext.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
// ssss_passphrase_enter_til.error = null
|
||||
sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
|
||||
sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it.toString()))
|
||||
// ssss_passphrase_submit.isEnabled = it.isNotBlank()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
sharedViewModel.observeViewEvents {
|
||||
// when (it) {
|
||||
|
|
|
@ -27,22 +27,24 @@ import android.view.ViewGroup
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.parentFragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.editorActionEvents
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
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.resources.ColorProvider
|
||||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||
import im.vector.app.databinding.FragmentBootstrapMigrateBackupBinding
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
|
||||
import java.util.concurrent.TimeUnit
|
||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class BootstrapMigrateBackupFragment @Inject constructor(
|
||||
|
@ -63,22 +65,21 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
|||
views.bootstrapMigrateEditText.setText(it.passphrase ?: "")
|
||||
}
|
||||
views.bootstrapMigrateEditText.editorActionEvents()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.throttleFirst(300)
|
||||
.onEach {
|
||||
if (it.actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.bootstrapMigrateEditText.textChanges()
|
||||
.skipInitialValue()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.bootstrapRecoveryKeyEnterTil.error = null
|
||||
// sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: ""))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
// sharedViewModel.observeViewEvents {}
|
||||
views.bootstrapMigrateContinueButton.debouncedClicks { submit() }
|
||||
|
|
|
@ -66,7 +66,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), FragmentManager.OnBackStac
|
|||
|
||||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
viewModel.subscribe(this) {
|
||||
viewModel.onEach {
|
||||
renderState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,15 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentDevtoolsEditorBinding
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomDevToolEditFragment @Inject constructor() :
|
||||
|
@ -44,10 +47,10 @@ class RoomDevToolEditFragment @Inject constructor() :
|
|||
}
|
||||
views.editText.textChanges()
|
||||
.skipInitialValue()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
sharedViewModel.handle(RoomDevToolAction.UpdateContentText(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
|
@ -24,10 +24,10 @@ import android.view.inputmethod.EditorInfo
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
|
@ -37,7 +37,10 @@ import im.vector.app.core.resources.ColorProvider
|
|||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
import im.vector.app.databinding.FragmentSetIdentityServerBinding
|
||||
import im.vector.app.features.discovery.DiscoverySharedViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetIdentityServerFragment @Inject constructor(
|
||||
|
@ -90,11 +93,11 @@ class SetIdentityServerFragment @Inject constructor(
|
|||
|
||||
views.identityServerSetDefaultAlternativeTextInput
|
||||
.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.identityServerSetDefaultAlternativeTil.error = null
|
||||
views.identityServerSetDefaultAlternativeSubmit.isEnabled = it.isNotEmpty()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.identityServerSetDefaultSubmit.debouncedClicks {
|
||||
viewModel.handle(SetIdentityServerAction.UseDefaultIdentityServer)
|
||||
|
|
|
@ -73,6 +73,8 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
|||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||
|
@ -178,8 +180,8 @@ class HomeActivity :
|
|||
}
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
.stream()
|
||||
.onEach { sharedAction ->
|
||||
when (sharedAction) {
|
||||
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
|
||||
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
|
@ -222,7 +224,7 @@ class HomeActivity :
|
|||
}
|
||||
}.exhaustive
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
val args = intent.getParcelableExtra<HomeActivityArgs>(Mavericks.KEY_ARG)
|
||||
|
||||
|
@ -243,13 +245,12 @@ class HomeActivity :
|
|||
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
|
||||
}.exhaustive
|
||||
}
|
||||
homeActivityViewModel.subscribe(this) { renderState(it) }
|
||||
homeActivityViewModel.onEach { renderState(it) }
|
||||
|
||||
shortcutsHandler.observeRoomsAndBuildShortcuts()
|
||||
.disposeOnDestroy()
|
||||
shortcutsHandler.observeRoomsAndBuildShortcuts(lifecycleScope)
|
||||
|
||||
if (!vectorPreferences.didPromoteNewRestrictedFeature()) {
|
||||
promoteRestrictedViewModel.subscribe(this) {
|
||||
promoteRestrictedViewModel.onEach {
|
||||
if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic &&
|
||||
it.activeSpaceSummary.otherMemberIds.isNotEmpty()) {
|
||||
// It's a private space with some members show this once
|
||||
|
|
|
@ -299,7 +299,7 @@ class HomeDetailFragment @Inject constructor(
|
|||
|
||||
private fun setupKeysBackupBanner() {
|
||||
serverBackupStatusViewModel
|
||||
.subscribe(this) {
|
||||
.onEach {
|
||||
when (val banState = it.bannerState.invoke()) {
|
||||
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
|
||||
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.RoomGroupingMethod
|
|||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
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.features.call.dialpad.DialPadLookup
|
||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||
|
@ -36,10 +37,13 @@ import im.vector.app.features.invite.AutoAcceptInvites
|
|||
import im.vector.app.features.invite.showInvites
|
||||
import im.vector.app.features.settings.VectorDataStore
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
|
@ -50,9 +54,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
|
|||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* View model used to update the home bottom bar notification counts, observe the sync state and
|
||||
|
@ -66,7 +68,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val appStateHandler: AppStateHandler,
|
||||
private val autoAcceptInvites: AutoAcceptInvites) :
|
||||
VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
|
||||
VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
|
||||
CallProtocolsChecker.Listener {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -194,18 +196,15 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
|
||||
private fun observeRoomGroupingMethod() {
|
||||
appStateHandler.selectedRoomGroupingObservable
|
||||
.subscribe {
|
||||
setState {
|
||||
copy(
|
||||
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||
)
|
||||
}
|
||||
.setOnEach {
|
||||
copy(
|
||||
roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||
)
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeRoomSummaries() {
|
||||
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().switchMap {
|
||||
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().flatMapLatest {
|
||||
// we use it as a trigger to all changes in room, but do not really load
|
||||
// the actual models
|
||||
session.getPagedRoomSummariesLive(
|
||||
|
@ -213,11 +212,10 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
memberships = Membership.activeMemberships()
|
||||
},
|
||||
sortOrder = RoomSortOrder.NONE
|
||||
).asObservable()
|
||||
).asFlow()
|
||||
}
|
||||
.observeOn(Schedulers.computation())
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.subscribe {
|
||||
.throttleFirst(300)
|
||||
.onEach {
|
||||
when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
// TODO!!
|
||||
|
@ -274,6 +272,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
}
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
|||
import im.vector.app.core.platform.EmptyAction
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
|
|
@ -22,54 +22,62 @@ import android.os.Build
|
|||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||
import im.vector.app.features.pin.PinCodeStore
|
||||
import im.vector.app.features.pin.PinCodeStoreListener
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.disposables.Disposables
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
class ShortcutsHandler @Inject constructor(
|
||||
private val context: Context,
|
||||
private val appDispatchers: CoroutineDispatchers,
|
||||
private val shortcutCreator: ShortcutCreator,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val pinCodeStore: PinCodeStore
|
||||
) : PinCodeStoreListener {
|
||||
|
||||
private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context)
|
||||
|
||||
// Value will be set correctly if necessary
|
||||
private var hasPinCode = true
|
||||
private var hasPinCode = AtomicBoolean(true)
|
||||
|
||||
fun observeRoomsAndBuildShortcuts(): Disposable {
|
||||
fun observeRoomsAndBuildShortcuts(coroutineScope: CoroutineScope): Job {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
// No op
|
||||
return Disposables.empty()
|
||||
return Job()
|
||||
}
|
||||
|
||||
hasPinCode = pinCodeStore.getEncodedPin() != null
|
||||
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty()
|
||||
return session.getRoomSummariesLive(
|
||||
hasPinCode.set(pinCodeStore.getEncodedPin() != null)
|
||||
val session = activeSessionHolder.getSafeActiveSession() ?: return Job()
|
||||
return session.flow().liveRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
},
|
||||
sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY
|
||||
)
|
||||
.asObservable()
|
||||
.doOnSubscribe { pinCodeStore.addListener(this) }
|
||||
.doFinally { pinCodeStore.removeListener(this) }
|
||||
.subscribe { rooms ->
|
||||
.onStart { pinCodeStore.addListener(this@ShortcutsHandler) }
|
||||
.onCompletion { pinCodeStore.removeListener(this@ShortcutsHandler) }
|
||||
.onEach { rooms ->
|
||||
// Remove dead shortcuts (i.e. deleted rooms)
|
||||
removeDeadShortcut(rooms.map { it.roomId })
|
||||
|
||||
// Create shortcuts
|
||||
createShortcuts(rooms)
|
||||
}
|
||||
.flowOn(appDispatchers.computation)
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
private fun removeDeadShortcut(roomIds: List<String>) {
|
||||
|
@ -89,7 +97,7 @@ class ShortcutsHandler @Inject constructor(
|
|||
}
|
||||
|
||||
private fun createShortcuts(rooms: List<RoomSummary>) {
|
||||
if (hasPinCode) {
|
||||
if (hasPinCode.get()) {
|
||||
// No shortcut in this case (privacy)
|
||||
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||
} else {
|
||||
|
@ -127,7 +135,7 @@ class ShortcutsHandler @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onPinSetUpChange(isConfigured: Boolean) {
|
||||
hasPinCode = isConfigured
|
||||
hasPinCode.set(isConfigured)
|
||||
if (isConfigured) {
|
||||
// Remove shortcuts immediately
|
||||
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.home
|
||||
|
||||
import androidx.lifecycle.asFlow
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
|
@ -25,13 +26,17 @@ import im.vector.app.AppStateHandler
|
|||
import im.vector.app.RoomGroupingMethod
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
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.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
|
@ -39,8 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
|
|||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
data class UnreadMessagesState(
|
||||
val homeSpaceUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0),
|
||||
|
@ -57,7 +60,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
|||
private val vectorPreferences: VectorPreferences,
|
||||
appStateHandler: AppStateHandler,
|
||||
private val autoAcceptInvites: AutoAcceptInvites) :
|
||||
VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<UnreadMessagesSharedViewModel, UnreadMessagesState> {
|
||||
|
@ -75,8 +78,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
|||
this.memberships = listOf(Membership.JOIN)
|
||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
|
||||
}, sortOrder = RoomSortOrder.NONE
|
||||
).asObservable()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
).asFlow()
|
||||
.throttleFirst(300)
|
||||
.execute {
|
||||
val counts = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
|
@ -103,91 +106,91 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia
|
|||
)
|
||||
}
|
||||
|
||||
Observable.combineLatest(
|
||||
combine(
|
||||
appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(),
|
||||
appStateHandler.selectedRoomGroupingObservable.switchMap {
|
||||
appStateHandler.selectedRoomGroupingObservable.flatMapLatest {
|
||||
session.getPagedRoomSummariesLive(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = Membership.activeMemberships()
|
||||
}, sortOrder = RoomSortOrder.NONE
|
||||
).asObservable()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(Schedulers.computation())
|
||||
},
|
||||
{ groupingMethod, _ ->
|
||||
when (groupingMethod.orNull()) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
// currently not supported
|
||||
CountInfo(
|
||||
RoomAggregateNotificationCount(0, 0),
|
||||
RoomAggregateNotificationCount(0, 0)
|
||||
)
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
val selectedSpace = appStateHandler.safeActiveSpaceId()
|
||||
|
||||
val inviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.getRoomSummaries(
|
||||
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
|
||||
).size
|
||||
}
|
||||
|
||||
val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.getRoomSummaries(
|
||||
spaceSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.INVITE)
|
||||
}
|
||||
).size
|
||||
}
|
||||
|
||||
val totalCount = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf {
|
||||
!vectorPreferences.prefSpacesShowAllRoomInHome()
|
||||
} ?: ActiveSpaceFilter.None
|
||||
}
|
||||
)
|
||||
|
||||
val counts = RoomAggregateNotificationCount(
|
||||
totalCount.notificationCount + inviteCount,
|
||||
totalCount.highlightCount + inviteCount
|
||||
)
|
||||
val rootCounts = session.spaceService().getRootSpaceSummaries()
|
||||
.filter {
|
||||
// filter out current selection
|
||||
it.roomId != selectedSpace
|
||||
}
|
||||
|
||||
CountInfo(
|
||||
homeCount = counts,
|
||||
otherCount = RoomAggregateNotificationCount(
|
||||
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
|
||||
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
|
||||
spaceInviteCount,
|
||||
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
|
||||
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
|
||||
spaceInviteCount
|
||||
)
|
||||
)
|
||||
}
|
||||
null -> {
|
||||
CountInfo(
|
||||
RoomAggregateNotificationCount(0, 0),
|
||||
RoomAggregateNotificationCount(0, 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
).asFlow()
|
||||
.throttleFirst(300)
|
||||
}
|
||||
).execute {
|
||||
copy(
|
||||
homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0),
|
||||
otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0)
|
||||
)
|
||||
) { groupingMethod, _ ->
|
||||
when (groupingMethod.orNull()) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
// currently not supported
|
||||
CountInfo(
|
||||
RoomAggregateNotificationCount(0, 0),
|
||||
RoomAggregateNotificationCount(0, 0)
|
||||
)
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
val selectedSpace = appStateHandler.safeActiveSpaceId()
|
||||
|
||||
val inviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.getRoomSummaries(
|
||||
roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) }
|
||||
).size
|
||||
}
|
||||
|
||||
val spaceInviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
} else {
|
||||
session.getRoomSummaries(
|
||||
spaceSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.INVITE)
|
||||
}
|
||||
).size
|
||||
}
|
||||
|
||||
val totalCount = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = listOf(Membership.JOIN)
|
||||
this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf {
|
||||
!vectorPreferences.prefSpacesShowAllRoomInHome()
|
||||
} ?: ActiveSpaceFilter.None
|
||||
}
|
||||
)
|
||||
|
||||
val counts = RoomAggregateNotificationCount(
|
||||
totalCount.notificationCount + inviteCount,
|
||||
totalCount.highlightCount + inviteCount
|
||||
)
|
||||
val rootCounts = session.spaceService().getRootSpaceSummaries()
|
||||
.filter {
|
||||
// filter out current selection
|
||||
it.roomId != selectedSpace
|
||||
}
|
||||
|
||||
CountInfo(
|
||||
homeCount = counts,
|
||||
otherCount = RoomAggregateNotificationCount(
|
||||
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
|
||||
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
|
||||
spaceInviteCount,
|
||||
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
|
||||
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
|
||||
spaceInviteCount
|
||||
)
|
||||
)
|
||||
}
|
||||
null -> {
|
||||
CountInfo(
|
||||
RoomAggregateNotificationCount(0, 0),
|
||||
RoomAggregateNotificationCount(0, 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.execute {
|
||||
copy(
|
||||
homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0),
|
||||
otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import androidx.core.view.GravityCompat
|
|||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
@ -40,6 +41,8 @@ import im.vector.app.features.navigation.Navigator
|
|||
import im.vector.app.features.room.RequireActiveMembershipAction
|
||||
import im.vector.app.features.room.RequireActiveMembershipViewEvents
|
||||
import im.vector.app.features.room.RequireActiveMembershipViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RoomDetailActivity :
|
||||
|
@ -97,13 +100,13 @@ class RoomDetailActivity :
|
|||
sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
.stream()
|
||||
.onEach { sharedAction ->
|
||||
when (sharedAction) {
|
||||
is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction)
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
requireActiveMembershipViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
|
|
|
@ -67,8 +67,6 @@ import com.airbnb.mvrx.args
|
|||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jakewharton.rxbinding3.view.focusChanges
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import com.vanniktech.emoji.EmojiPopup
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
||||
|
@ -185,6 +183,10 @@ import im.vector.app.features.widgets.WidgetActivity
|
|||
import im.vector.app.features.widgets.WidgetArgs
|
||||
import im.vector.app.features.widgets.WidgetKind
|
||||
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import nl.dionsegijn.konfetti.models.Shape
|
||||
|
@ -216,10 +218,11 @@ import org.matrix.android.sdk.api.util.MimeTypes
|
|||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||
import reactivecircus.flowbinding.android.view.focusChanges
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import timber.log.Timber
|
||||
import java.net.URL
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
|
@ -366,11 +369,11 @@ class RoomDetailFragment @Inject constructor(
|
|||
}
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe {
|
||||
.stream()
|
||||
.onEach {
|
||||
handleActions(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
knownCallsViewModel
|
||||
.liveKnownCalls
|
||||
|
@ -1358,19 +1361,19 @@ class RoomDetailFragment @Inject constructor(
|
|||
private fun observerUserTyping() {
|
||||
views.composerLayout.views.composerEditText.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(300, TimeUnit.MILLISECONDS)
|
||||
.debounce(300)
|
||||
.map { it.isNotEmpty() }
|
||||
.subscribe {
|
||||
.onEach {
|
||||
Timber.d("Typing: User is typing: $it")
|
||||
textComposerViewModel.handle(TextComposerAction.UserIsTyping(it))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.composerLayout.views.composerEditText.focusChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
roomDetailViewModel.handle(RoomDetailAction.ComposerFocusChange(it))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun sendUri(uri: Uri): Boolean {
|
||||
|
|
|
@ -27,16 +27,17 @@ import com.airbnb.mvrx.MavericksViewModelFactory
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
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.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.BehaviorDataSource
|
||||
import im.vector.app.features.attachments.toContentAttachmentData
|
||||
import im.vector.app.features.call.conference.ConferenceEvent
|
||||
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
|
||||
|
@ -56,13 +57,14 @@ import im.vector.app.features.session.coroutineScope
|
|||
import im.vector.app.features.settings.VectorDataStore
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.voice.VoicePlayerHelper
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -98,7 +100,6 @@ import org.matrix.android.sdk.flow.flow
|
|||
import org.matrix.android.sdk.flow.unwrap
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class RoomDetailViewModel @AssistedInject constructor(
|
||||
|
@ -123,8 +124,8 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
private val eventId = initialState.eventId
|
||||
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>()
|
||||
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||
private val invisibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsInvisible>()
|
||||
private val visibleEventsSource = BehaviorDataSource<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||
private var timelineEvents = MutableSharedFlow<List<TimelineEvent>>(0)
|
||||
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId)
|
||||
|
||||
|
@ -562,7 +563,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleEventInvisible(action: RoomDetailAction.TimelineEventTurnsInvisible) {
|
||||
invisibleEventsObservable.accept(action)
|
||||
invisibleEventsSource.post(action)
|
||||
}
|
||||
|
||||
fun getMember(userId: String): RoomMemberSummary? {
|
||||
|
@ -711,12 +712,12 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
if (action.event.root.sendState.isSent()) { // ignore pending/local events
|
||||
visibleEventsObservable.accept(action)
|
||||
visibleEventsSource.post(action)
|
||||
}
|
||||
// We need to update this with the related m.replace also (to move read receipt)
|
||||
action.event.annotations?.editSummary?.sourceEvents?.forEach {
|
||||
room.getTimeLineEvent(it)?.let { event ->
|
||||
visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event))
|
||||
visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -864,11 +865,13 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
private fun observeEventDisplayedActions() {
|
||||
// We are buffering scroll events for one second
|
||||
// and keep the most recent one to set the read receipt on.
|
||||
visibleEventsObservable
|
||||
.buffer(1, TimeUnit.SECONDS)
|
||||
|
||||
visibleEventsSource
|
||||
.stream()
|
||||
.chunk(1000)
|
||||
.filter { it.isNotEmpty() }
|
||||
.subscribeBy(onNext = { actions ->
|
||||
val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy
|
||||
.onEach { actions ->
|
||||
val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@onEach
|
||||
val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent
|
||||
if (trackUnreadMessages.get()) {
|
||||
if (globalMostRecentDisplayedEvent == null) {
|
||||
|
@ -882,8 +885,9 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
tryOrNull { room.setReadReceipt(eventId) }
|
||||
}
|
||||
}
|
||||
})
|
||||
.disposeOnClear()
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun handleMarkAllAsRead() {
|
||||
|
|
|
@ -104,7 +104,7 @@ class TextComposerViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeToStateInternal() {
|
||||
selectSubscribe(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ ->
|
||||
onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ ->
|
||||
updateIsSendButtonVisibility(false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,18 +31,16 @@ import im.vector.app.core.platform.EmptyAction
|
|||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
|
||||
import org.matrix.android.sdk.rx.RxRoom
|
||||
import org.matrix.android.sdk.rx.unwrap
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import org.matrix.android.sdk.flow.unwrap
|
||||
|
||||
data class DisplayReactionsViewState(
|
||||
val eventId: String,
|
||||
val roomId: String,
|
||||
val mapReactionKeyToMemberList: Async<List<ReactionInfo>> = Uninitialized) :
|
||||
MavericksState {
|
||||
MavericksState {
|
||||
|
||||
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||
}
|
||||
|
@ -81,39 +79,31 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
|||
}
|
||||
|
||||
private fun observeEventAnnotationSummaries() {
|
||||
RxRoom(room)
|
||||
room.flow()
|
||||
.liveAnnotationSummary(eventId)
|
||||
.unwrap()
|
||||
.flatMapSingle { summaries ->
|
||||
Observable
|
||||
.fromIterable(summaries.reactionsSummary)
|
||||
// .filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
|
||||
.toReactionInfoList()
|
||||
.map { annotationsSummary ->
|
||||
annotationsSummary.reactionsSummary
|
||||
.flatMap { reactionsSummary ->
|
||||
reactionsSummary.sourceEvents.map {
|
||||
val event = room.getTimeLineEvent(it)
|
||||
?: throw RuntimeException("Your eventId is not valid")
|
||||
ReactionInfo(
|
||||
event.root.eventId!!,
|
||||
reactionsSummary.key,
|
||||
event.root.senderId ?: "",
|
||||
event.senderInfo.disambiguatedDisplayName,
|
||||
dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.execute {
|
||||
copy(mapReactionKeyToMemberList = it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Observable<ReactionAggregatedSummary>.toReactionInfoList(): Single<List<ReactionInfo>> {
|
||||
return flatMap { summary ->
|
||||
Observable
|
||||
.fromIterable(summary.sourceEvents)
|
||||
.map {
|
||||
val event = room.getTimeLineEvent(it)
|
||||
?: throw RuntimeException("Your eventId is not valid")
|
||||
ReactionInfo(
|
||||
event.root.eventId!!,
|
||||
summary.key,
|
||||
event.root.senderId ?: "",
|
||||
event.senderInfo.disambiguatedDisplayName,
|
||||
dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||
|
||||
)
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {
|
||||
// No op
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import androidx.core.util.Predicate
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import io.reactivex.functions.Predicate
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -49,6 +50,8 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
|||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.extensions.orTrue
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -118,9 +121,9 @@ class RoomListFragment @Inject constructor(
|
|||
views.createChatFabMenu.listener = this
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { handleQuickActions(it) }
|
||||
.disposeOnDestroyView()
|
||||
.stream()
|
||||
.onEach { handleQuickActions(it) }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
roomListViewModel.onEach(RoomListViewState::roomMembershipChanges) { ms ->
|
||||
// it's for invites local echo
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import io.reactivex.functions.Predicate
|
||||
import androidx.core.util.Predicate
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -20,6 +20,4 @@ import im.vector.app.features.home.RoomListDisplayMode
|
|||
|
||||
interface RoomListSectionBuilder {
|
||||
fun buildSections(mode: RoomListDisplayMode): List<RoomsSection>
|
||||
|
||||
fun dispose()
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.asFlow
|
||||
import im.vector.app.AppStateHandler
|
||||
import im.vector.app.R
|
||||
import im.vector.app.RoomGroupingMethod
|
||||
|
@ -24,17 +25,21 @@ import im.vector.app.core.resources.StringProvider
|
|||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.showInvites
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
|
||||
class RoomListSectionBuilderGroup(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider,
|
||||
private val appStateHandler: AppStateHandler,
|
||||
|
@ -42,8 +47,6 @@ class RoomListSectionBuilderGroup(
|
|||
private val onUpdatable: (UpdatableLivePageResult) -> Unit
|
||||
) : RoomListSectionBuilder {
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
override fun buildSections(mode: RoomListDisplayMode): List<RoomsSection> {
|
||||
val activeGroupAwareQueries = mutableListOf<UpdatableLivePageResult>()
|
||||
val sections = mutableListOf<RoomsSection>()
|
||||
|
@ -103,16 +106,14 @@ class RoomListSectionBuilderGroup(
|
|||
|
||||
appStateHandler.selectedRoomGroupingObservable
|
||||
.distinctUntilChanged()
|
||||
.subscribe { groupingMethod ->
|
||||
.onEach { groupingMethod ->
|
||||
val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId
|
||||
activeGroupAwareQueries.onEach { updater ->
|
||||
updater.updateQuery { query ->
|
||||
query.copy(activeGroupId = selectedGroupId)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
disposables.add(it)
|
||||
}
|
||||
}.launchIn(coroutineScope)
|
||||
|
||||
return sections
|
||||
}
|
||||
|
@ -251,15 +252,14 @@ class RoomListSectionBuilderGroup(
|
|||
}.livePagedList
|
||||
.let { livePagedList ->
|
||||
// use it also as a source to update count
|
||||
livePagedList.asObservable()
|
||||
.observeOn(Schedulers.computation())
|
||||
.subscribe {
|
||||
livePagedList.asFlow()
|
||||
.onEach {
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
|
||||
}.also {
|
||||
disposables.add(it)
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(coroutineScope)
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
|
@ -280,8 +280,4 @@ class RoomListSectionBuilderGroup(
|
|||
.build()
|
||||
.let { block(it) }
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
disposables.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.paging.PagedList
|
||||
import com.airbnb.mvrx.Async
|
||||
|
@ -29,12 +30,15 @@ import im.vector.app.features.home.RoomListDisplayMode
|
|||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.invite.showInvites
|
||||
import im.vector.app.space
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.rxkotlin.Observables
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
|
@ -44,7 +48,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
|||
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import timber.log.Timber
|
||||
|
||||
class RoomListSectionBuilderSpace(
|
||||
private val session: Session,
|
||||
|
@ -57,8 +61,6 @@ class RoomListSectionBuilderSpace(
|
|||
private val onlyOrphansInHome: Boolean = false
|
||||
) : RoomListSectionBuilder {
|
||||
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
private val pagedListConfig = PagedList.Config.Builder()
|
||||
.setPageSize(10)
|
||||
.setInitialLoadSizeHint(20)
|
||||
|
@ -132,14 +134,12 @@ class RoomListSectionBuilderSpace(
|
|||
|
||||
appStateHandler.selectedRoomGroupingObservable
|
||||
.distinctUntilChanged()
|
||||
.subscribe { groupingMethod ->
|
||||
.onEach { groupingMethod ->
|
||||
val selectedSpace = groupingMethod.orNull()?.space()
|
||||
activeSpaceAwareQueries.onEach { updater ->
|
||||
updater.updateForSpaceId(selectedSpace?.roomId)
|
||||
}
|
||||
}.also {
|
||||
disposables.add(it)
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
return sections
|
||||
}
|
||||
|
@ -221,13 +221,13 @@ class RoomListSectionBuilderSpace(
|
|||
}
|
||||
|
||||
// add suggested rooms
|
||||
val suggestedRoomsObservable = // MutableLiveData<List<SpaceChildInfo>>()
|
||||
val suggestedRoomsFlow = // MutableLiveData<List<SpaceChildInfo>>()
|
||||
appStateHandler.selectedRoomGroupingObservable
|
||||
.distinctUntilChanged()
|
||||
.switchMap { groupingMethod ->
|
||||
.flatMapLatest { groupingMethod ->
|
||||
val selectedSpace = groupingMethod.orNull()?.space()
|
||||
if (selectedSpace == null) {
|
||||
Observable.just(emptyList())
|
||||
flowOf(emptyList())
|
||||
} else {
|
||||
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
|
||||
val spaceSum = tryOrNull {
|
||||
|
@ -240,24 +240,23 @@ class RoomListSectionBuilderSpace(
|
|||
session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true
|
||||
}
|
||||
emit(filtered)
|
||||
}.asObservable()
|
||||
}.asFlow()
|
||||
}
|
||||
}
|
||||
|
||||
val liveSuggestedRooms = MutableLiveData<SuggestedRoomInfo>()
|
||||
Observables.combineLatest(
|
||||
suggestedRoomsObservable,
|
||||
suggestedRoomJoiningState.asObservable()
|
||||
combine(
|
||||
suggestedRoomsFlow,
|
||||
suggestedRoomJoiningState.asFlow()
|
||||
) { rooms, joinStates ->
|
||||
SuggestedRoomInfo(
|
||||
rooms,
|
||||
joinStates
|
||||
)
|
||||
}.subscribe {
|
||||
}.onEach {
|
||||
liveSuggestedRooms.postValue(it)
|
||||
}.also {
|
||||
disposables.add(it)
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
sectionName = stringProvider.getString(R.string.suggested_header),
|
||||
|
@ -373,9 +372,9 @@ class RoomListSectionBuilderSpace(
|
|||
}.livePagedList
|
||||
.let { livePagedList ->
|
||||
// use it also as a source to update count
|
||||
livePagedList.asObservable()
|
||||
.observeOn(Schedulers.computation())
|
||||
.subscribe {
|
||||
livePagedList.asFlow()
|
||||
.onEach {
|
||||
Timber.v("Thread space list: ${Thread.currentThread()}")
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(
|
||||
|
@ -387,9 +386,9 @@ class RoomListSectionBuilderSpace(
|
|||
)
|
||||
}
|
||||
)
|
||||
}.also {
|
||||
disposables.add(it)
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
sections.add(
|
||||
RoomsSection(
|
||||
|
@ -432,8 +431,4 @@ class RoomListSectionBuilderSpace(
|
|||
RoomListViewModel.SpaceFilterStrategy.NONE -> this
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
disposables.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,6 +135,7 @@ class RoomListViewModel @AssistedInject constructor(
|
|||
)
|
||||
} else {
|
||||
RoomListSectionBuilderGroup(
|
||||
viewModelScope,
|
||||
session,
|
||||
stringProvider,
|
||||
appStateHandler,
|
||||
|
@ -335,9 +336,4 @@ class RoomListViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
roomListSectionBuilder.dispose()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -41,6 +42,8 @@ import im.vector.app.features.userdirectory.UserListFragment
|
|||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||
import im.vector.app.features.userdirectory.UserListSharedAction
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import java.net.HttpURLConnection
|
||||
|
@ -63,8 +66,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
|||
|
||||
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
.stream()
|
||||
.onEach { sharedAction ->
|
||||
when (sharedAction) {
|
||||
UserListSharedAction.Close -> finish()
|
||||
UserListSharedAction.GoBack -> onBackPressed()
|
||||
|
@ -75,7 +78,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
R.id.container,
|
||||
|
|
|
@ -18,11 +18,14 @@ package im.vector.app.features.invite
|
|||
|
||||
import im.vector.app.ActiveSessionDataSource
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -51,7 +54,8 @@ class InvitesAcceptor @Inject constructor(
|
|||
private val autoAcceptInvites: AutoAcceptInvites
|
||||
) : Session.Listener {
|
||||
|
||||
private lateinit var activeSessionDisposable: Disposable
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private val shouldRejectRoomIds = mutableSetOf<String>()
|
||||
private val activeSessionIds = mutableSetOf<String>()
|
||||
private val semaphore = Semaphore(1)
|
||||
|
@ -61,13 +65,14 @@ class InvitesAcceptor @Inject constructor(
|
|||
}
|
||||
|
||||
private fun observeActiveSession() {
|
||||
activeSessionDisposable = sessionDataSource.observe()
|
||||
sessionDataSource.stream()
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
it.orNull()?.let { session ->
|
||||
onSessionActive(session)
|
||||
}
|
||||
}
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
private fun onSessionActive(session: Session) {
|
||||
|
|
|
@ -85,10 +85,9 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
|
|||
addFirstFragment()
|
||||
}
|
||||
|
||||
loginViewModel
|
||||
.subscribe(this) {
|
||||
updateWithState(it)
|
||||
}
|
||||
loginViewModel.onEach {
|
||||
updateWithState(it)
|
||||
}
|
||||
|
||||
loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
|
||||
|
||||
|
|
|
@ -25,21 +25,24 @@ import android.view.inputmethod.EditorInfo
|
|||
import androidx.autofill.HintConstants
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentLoginBinding
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -224,20 +227,18 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
|||
|
||||
private fun setupSubmitButton() {
|
||||
views.loginSubmit.setOnClickListener { submit() }
|
||||
Observable
|
||||
.combineLatest(
|
||||
views.loginField.textChanges().map { it.trim().isNotEmpty() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() },
|
||||
{ isLoginNotEmpty, isPasswordNotEmpty ->
|
||||
isLoginNotEmpty && isPasswordNotEmpty
|
||||
}
|
||||
)
|
||||
.subscribeBy {
|
||||
combine(
|
||||
views.loginField.textChanges().map { it.trim().isNotEmpty() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() }
|
||||
) { isLoginNotEmpty, isPasswordNotEmpty ->
|
||||
isLoginNotEmpty && isPasswordNotEmpty
|
||||
}
|
||||
.onEach {
|
||||
views.loginFieldTil.error = null
|
||||
views.passwordFieldTil.error = null
|
||||
views.loginSubmit.isEnabled = it
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun forgetPasswordClicked() {
|
||||
|
|
|
@ -25,19 +25,22 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.isEmail
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.databinding.FragmentLoginGenericTextInputFormBinding
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.is401
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
enum class TextInputFormFragmentMode {
|
||||
|
@ -93,10 +96,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
|
|||
|
||||
private fun setupTil() {
|
||||
views.loginGenericTextInputFormTextInput.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.loginGenericTextInputFormTil.error = null
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
|
@ -195,10 +198,10 @@ class LoginGenericTextInputFormFragment @Inject constructor() : AbstractLoginFra
|
|||
private fun setupSubmitButton() {
|
||||
views.loginGenericTextInputFormSubmit.isEnabled = false
|
||||
views.loginGenericTextInputFormTextInput.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun isInputValid(input: CharSequence): Boolean {
|
||||
|
|
|
@ -20,19 +20,22 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.core.extensions.isEmail
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentLoginResetPasswordBinding
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -59,21 +62,18 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment<F
|
|||
|
||||
private fun setupSubmitButton() {
|
||||
views.resetPasswordSubmit.setOnClickListener { submit() }
|
||||
|
||||
Observable
|
||||
.combineLatest(
|
||||
views.resetPasswordEmail.textChanges().map { it.isEmail() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() },
|
||||
{ isEmail, isPasswordNotEmpty ->
|
||||
isEmail && isPasswordNotEmpty
|
||||
}
|
||||
)
|
||||
.subscribeBy {
|
||||
combine(
|
||||
views.resetPasswordEmail.textChanges().map { it.isEmail() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() }
|
||||
) { isEmail, isPasswordNotEmpty ->
|
||||
isEmail && isPasswordNotEmpty
|
||||
}
|
||||
.onEach {
|
||||
views.resetPasswordEmailTil.error = null
|
||||
views.passwordFieldTil.error = null
|
||||
views.resetPasswordSubmit.isEnabled = it
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
|
|
|
@ -25,15 +25,18 @@ import android.view.inputmethod.EditorInfo
|
|||
import android.widget.ArrayAdapter
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.utils.ensureProtocol
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import im.vector.app.databinding.FragmentLoginServerUrlFormBinding
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -61,11 +64,11 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
|
|||
|
||||
private fun setupHomeServerField() {
|
||||
views.loginServerUrlFormHomeServerUrl.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.loginServerUrlFormHomeServerUrlTil.error = null
|
||||
views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
|
|
|
@ -92,10 +92,9 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
|
|||
addFirstFragment()
|
||||
}
|
||||
|
||||
loginViewModel
|
||||
.subscribe(this) {
|
||||
updateWithState(it)
|
||||
}
|
||||
loginViewModel.onEach {
|
||||
updateWithState(it)
|
||||
}
|
||||
|
||||
loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
|
||||
|
||||
|
@ -201,19 +200,19 @@ open class LoginActivity2 : VectorBaseActivity<ActivityLoginBinding>(), ToolbarC
|
|||
// Go back to the login fragment
|
||||
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
|
||||
}
|
||||
is LoginViewEvents2.OnSendEmailSuccess ->
|
||||
is LoginViewEvents2.OnSendEmailSuccess ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginWaitForEmailFragment2::class.java,
|
||||
LoginWaitForEmailFragmentArgument(event.email),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
is LoginViewEvents2.OpenSigninPasswordScreen -> {
|
||||
is LoginViewEvents2.OpenSigninPasswordScreen -> {
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginFragmentSigninPassword2::class.java,
|
||||
tag = FRAGMENT_LOGIN_TAG,
|
||||
option = commonOption)
|
||||
}
|
||||
is LoginViewEvents2.OpenSignupPasswordScreen -> {
|
||||
is LoginViewEvents2.OpenSignupPasswordScreen -> {
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginFragmentSignupPassword2::class.java,
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
|
|
|
@ -24,17 +24,20 @@ import android.view.ViewGroup
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.databinding.FragmentLoginSigninPassword2Binding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
|
@ -121,11 +124,11 @@ class LoginFragmentSigninPassword2 @Inject constructor(
|
|||
views.passwordField
|
||||
.textChanges()
|
||||
.map { it.isNotEmpty() }
|
||||
.subscribeBy {
|
||||
.onEach {
|
||||
views.passwordFieldTil.error = null
|
||||
views.loginSubmit.isEnabled = it
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun forgetPasswordClicked() {
|
||||
|
|
|
@ -22,13 +22,16 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.autofill.HintConstants
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.databinding.FragmentLoginSigninUsername2Binding
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -83,11 +86,11 @@ class LoginFragmentSigninUsername2 @Inject constructor() : AbstractLoginFragment
|
|||
views.loginSubmit.setOnClickListener { submit() }
|
||||
views.loginField.textChanges()
|
||||
.map { it.trim().isNotEmpty() }
|
||||
.subscribeBy {
|
||||
.onEach {
|
||||
views.loginFieldTil.error = null
|
||||
views.loginSubmit.isEnabled = it
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
|
|
|
@ -23,12 +23,14 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.autofill.HintConstants
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.databinding.FragmentLoginSignupPassword2Binding
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -87,11 +89,11 @@ class LoginFragmentSignupPassword2 @Inject constructor() : AbstractLoginFragment
|
|||
private fun setupSubmitButton() {
|
||||
views.loginSubmit.setOnClickListener { submit() }
|
||||
views.passwordField.textChanges()
|
||||
.subscribeBy { password ->
|
||||
.onEach { password ->
|
||||
views.passwordFieldTil.error = null
|
||||
views.loginSubmit.isEnabled = password.isNotEmpty()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
|
|
|
@ -24,14 +24,17 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.view.isVisible
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentLoginSignupUsername2Binding
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.SocialLoginButtonsView
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -111,12 +114,12 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
|
|||
views.loginSubmit.setOnClickListener { submit() }
|
||||
views.loginField.textChanges()
|
||||
.map { it.trim() }
|
||||
.subscribeBy { text ->
|
||||
.onEach { text ->
|
||||
val isNotEmpty = text.isNotEmpty()
|
||||
views.loginFieldTil.error = null
|
||||
views.loginSubmit.isEnabled = isNotEmpty
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun resetViewModel() {
|
||||
|
|
|
@ -24,7 +24,7 @@ import android.view.ViewGroup
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.view.isVisible
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
|
@ -32,11 +32,14 @@ import im.vector.app.core.extensions.toReducedUrl
|
|||
import im.vector.app.databinding.FragmentLoginSigninToAny2Binding
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import im.vector.app.features.login.SocialLoginButtonsView
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -136,20 +139,18 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
|
|||
|
||||
private fun setupSubmitButton() {
|
||||
views.loginSubmit.setOnClickListener { submit() }
|
||||
Observable
|
||||
.combineLatest(
|
||||
views.loginField.textChanges().map { it.trim().isNotEmpty() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() },
|
||||
{ isLoginNotEmpty, isPasswordNotEmpty ->
|
||||
isLoginNotEmpty && isPasswordNotEmpty
|
||||
}
|
||||
)
|
||||
.subscribeBy {
|
||||
combine(
|
||||
views.loginField.textChanges().map { it.trim().isNotEmpty() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() }
|
||||
) { isLoginNotEmpty, isPasswordNotEmpty ->
|
||||
isLoginNotEmpty && isPasswordNotEmpty
|
||||
}
|
||||
.onEach {
|
||||
views.loginFieldTil.error = null
|
||||
views.passwordFieldTil.error = null
|
||||
views.loginSubmit.isEnabled = it
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun forgetPasswordClicked() {
|
||||
|
|
|
@ -24,10 +24,10 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.google.i18n.phonenumbers.NumberParseException
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.isEmail
|
||||
|
@ -36,9 +36,12 @@ import im.vector.app.core.extensions.toReducedUrl
|
|||
import im.vector.app.databinding.FragmentLoginGenericTextInputForm2Binding
|
||||
import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument
|
||||
import im.vector.app.features.login.TextInputFormFragmentMode
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.is401
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -82,10 +85,10 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
|
|||
|
||||
private fun setupTil() {
|
||||
views.loginGenericTextInputFormTextInput.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.loginGenericTextInputFormTil.error = null
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun setupUi() {
|
||||
|
@ -189,11 +192,11 @@ class LoginGenericTextInputFormFragment2 @Inject constructor() : AbstractLoginFr
|
|||
private fun setupSubmitButton() {
|
||||
views.loginGenericTextInputFormSubmit.isEnabled = false
|
||||
views.loginGenericTextInputFormTextInput.textChanges()
|
||||
.subscribe { text ->
|
||||
.onEach { text ->
|
||||
views.loginGenericTextInputFormSubmit.isEnabled = isInputValid(text)
|
||||
text?.let { updateSubmitButtons(it) }
|
||||
updateSubmitButtons(text)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun updateSubmitButtons(text: CharSequence) {
|
||||
|
|
|
@ -23,8 +23,8 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.hidePassword
|
||||
|
@ -32,8 +32,11 @@ import im.vector.app.core.extensions.isEmail
|
|||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.core.utils.autoResetTextInputLayoutErrors
|
||||
import im.vector.app.databinding.FragmentLoginResetPassword2Binding
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -78,19 +81,16 @@ class LoginResetPasswordFragment2 @Inject constructor() : AbstractLoginFragment2
|
|||
|
||||
private fun setupSubmitButton() {
|
||||
views.resetPasswordSubmit.setOnClickListener { submit() }
|
||||
|
||||
Observable
|
||||
.combineLatest(
|
||||
views.resetPasswordEmail.textChanges().map { it.isEmail() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() },
|
||||
{ isEmail, isPasswordNotEmpty ->
|
||||
isEmail && isPasswordNotEmpty
|
||||
}
|
||||
)
|
||||
.subscribeBy {
|
||||
combine(
|
||||
views.resetPasswordEmail.textChanges().map { it.isEmail() },
|
||||
views.passwordField.textChanges().map { it.isNotEmpty() }
|
||||
) { isEmail, isPasswordNotEmpty ->
|
||||
isEmail && isPasswordNotEmpty
|
||||
}
|
||||
.onEach {
|
||||
views.resetPasswordSubmit.isEnabled = it
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun submit() {
|
||||
|
|
|
@ -24,15 +24,18 @@ import android.view.ViewGroup
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.utils.ensureProtocol
|
||||
import im.vector.app.databinding.FragmentLoginServerUrlForm2Binding
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
@ -60,11 +63,11 @@ class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2
|
|||
|
||||
private fun setupHomeServerField() {
|
||||
views.loginServerUrlFormHomeServerUrl.textChanges()
|
||||
.subscribe {
|
||||
.onEach {
|
||||
views.loginServerUrlFormHomeServerUrlTil.error = null
|
||||
views.loginServerUrlFormSubmit.isEnabled = it.isNotBlank()
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.loginServerUrlFormHomeServerUrl.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
|
|
|
@ -33,8 +33,8 @@ class PowerLevelsFlowFactory(private val room: Room) {
|
|||
fun createFlow(): Flow<PowerLevelsContent> {
|
||||
return room.flow()
|
||||
.liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||
.flowOn(Dispatchers.Default)
|
||||
.mapOptional { it.content.toModel<PowerLevelsContent>() }
|
||||
.flowOn(Dispatchers.Default)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,18 +28,18 @@ import androidx.core.view.isVisible
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.jakewharton.rxbinding3.widget.queryTextChanges
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.EmojiCompatFontProvider
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.observeEvent
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivityEmojiReactionPickerBinding
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import reactivecircus.flowbinding.android.widget.queryTextChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -167,13 +167,11 @@ class EmojiReactionPickerActivity : VectorBaseActivity<ActivityEmojiReactionPick
|
|||
}
|
||||
|
||||
searchView.queryTextChanges()
|
||||
.throttleWithTimeout(600, TimeUnit.MILLISECONDS)
|
||||
.doOnError { err -> Timber.e(err) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { query ->
|
||||
.throttleFirst(600)
|
||||
.onEach { query ->
|
||||
onQueryText(query.toString())
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
searchItem.expandActionView()
|
||||
return true
|
||||
|
|
|
@ -77,8 +77,8 @@ class RequireActiveMembershipViewModel @AssistedInject constructor(
|
|||
room.flow()
|
||||
.liveRoomSummary()
|
||||
.unwrap()
|
||||
.flowOn(Dispatchers.Default)
|
||||
.map { mapToLeftViewEvent(room, it) }
|
||||
.flowOn(Dispatchers.Default)
|
||||
}
|
||||
.unwrap()
|
||||
.onEach { event ->
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.view.ViewGroup
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
|
@ -37,12 +36,14 @@ import im.vector.app.core.utils.toast
|
|||
import im.vector.app.databinding.FragmentPublicRoomsBinding
|
||||
import im.vector.app.features.permalink.NavigationInterceptor
|
||||
import im.vector.app.features.permalink.PermalinkHandler
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
import reactivecircus.flowbinding.appcompat.queryTextChanges
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -79,11 +80,11 @@ class PublicRoomsFragment @Inject constructor(
|
|||
setupRecyclerView()
|
||||
|
||||
views.publicRoomsFilter.queryTextChanges()
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.subscribeBy {
|
||||
.debounce(500)
|
||||
.onEach {
|
||||
viewModel.handle(RoomDirectoryAction.FilterWith(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.publicRoomsCreateNewRoom.debouncedClicks {
|
||||
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
@ -33,6 +34,8 @@ import im.vector.app.features.navigation.Navigator
|
|||
import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs
|
||||
import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment
|
||||
import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -55,8 +58,8 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matri
|
|||
}
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
.stream()
|
||||
.onEach { sharedAction ->
|
||||
when (sharedAction) {
|
||||
is RoomDirectorySharedAction.Back -> popBackstack()
|
||||
is RoomDirectorySharedAction.CreateRoom -> {
|
||||
|
@ -74,7 +77,7 @@ class RoomDirectoryActivity : VectorBaseActivity<ActivitySimpleBinding>(), Matri
|
|||
is RoomDirectorySharedAction.Close -> finish()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
override fun initUiAndData() {
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory.createroom
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
|
@ -28,6 +29,8 @@ import im.vector.app.core.platform.VectorBaseActivity
|
|||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
|
||||
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
/**
|
||||
* Simple container for [CreateRoomFragment]
|
||||
|
@ -62,14 +65,14 @@ class CreateRoomActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarC
|
|||
super.onCreate(savedInstanceState)
|
||||
sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
.stream()
|
||||
.onEach { sharedAction ->
|
||||
when (sharedAction) {
|
||||
is RoomDirectorySharedAction.Back,
|
||||
is RoomDirectorySharedAction.Close -> finish()
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
|
@ -44,6 +45,8 @@ import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
|
|||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.toOption
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||
|
@ -103,11 +106,11 @@ class CreateRoomFragment @Inject constructor(
|
|||
private fun setupRoomJoinRuleSharedActionViewModel() {
|
||||
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
|
||||
roomJoinRuleSharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
.stream()
|
||||
.onEach { action ->
|
||||
viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
|
|
|
@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
|
@ -41,6 +42,8 @@ import im.vector.app.features.roomprofile.notifications.RoomNotificationSettings
|
|||
import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment
|
||||
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
|
||||
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -93,8 +96,8 @@ class RoomProfileActivity :
|
|||
}
|
||||
}
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { sharedAction ->
|
||||
.stream()
|
||||
.onEach { sharedAction ->
|
||||
when (sharedAction) {
|
||||
RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
|
||||
RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
|
||||
|
@ -105,7 +108,7 @@ class RoomProfileActivity :
|
|||
RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings()
|
||||
}.exhaustive
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
requireActiveMembershipViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.view.ViewGroup
|
|||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -52,6 +53,8 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
|
|||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
|
@ -124,9 +127,9 @@ class RoomProfileFragment @Inject constructor(
|
|||
}.exhaustive
|
||||
}
|
||||
roomListQuickActionsSharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { handleQuickActions(it) }
|
||||
.disposeOnDestroyView()
|
||||
.stream()
|
||||
.onEach { handleQuickActions(it) }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
setupClicks()
|
||||
setupLongClicks()
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -38,6 +39,8 @@ import im.vector.app.features.roomprofile.RoomProfileArgs
|
|||
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
|
||||
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction
|
||||
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
|
@ -77,9 +80,9 @@ class RoomAliasFragment @Inject constructor(
|
|||
}
|
||||
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { handleAliasAction(it) }
|
||||
.disposeOnDestroyView()
|
||||
.stream()
|
||||
.onEach { handleAliasAction(it) }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) {
|
||||
|
|
|
@ -27,11 +27,9 @@ import im.vector.app.core.extensions.exhaustive
|
|||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -95,7 +93,6 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
|||
|
||||
if (room.isEncrypted()) {
|
||||
room.flow().liveRoomMembers(roomMemberQueryParams)
|
||||
.flowOn(Dispatchers.Main)
|
||||
.flatMapLatest { membersSummary ->
|
||||
session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId })
|
||||
.asFlow()
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package im.vector.app.features.roomprofile.members
|
||||
|
||||
import io.reactivex.functions.Predicate
|
||||
import androidx.core.util.Predicate
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import javax.inject.Inject
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.view.MenuItem
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
|
@ -46,6 +47,8 @@ import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistory
|
|||
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import java.util.UUID
|
||||
|
@ -101,21 +104,21 @@ class RoomSettingsFragment @Inject constructor(
|
|||
private fun setupRoomJoinRuleSharedActionViewModel() {
|
||||
roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java)
|
||||
roomJoinRuleSharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
.stream()
|
||||
.onEach { action ->
|
||||
viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun setupRoomHistoryVisibilitySharedActionViewModel() {
|
||||
roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java)
|
||||
roomHistoryVisibilitySharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
.stream()
|
||||
.onEach { action ->
|
||||
viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun showSuccess() {
|
||||
|
|
|
@ -40,6 +40,7 @@ import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
|
|||
import im.vector.app.features.roomprofile.RoomProfileArgs
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedEvents
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedFragment
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedState
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||
import javax.inject.Inject
|
||||
|
|
|
@ -14,27 +14,26 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.roomprofile.settings.joinrule
|
||||
package im.vector.app.features.roomprofile.settings.joinrule.advanced
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.OnBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentSpaceRestrictedSelectBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.ChooseRestrictedController
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedActions
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import java.util.concurrent.TimeUnit
|
||||
import reactivecircus.flowbinding.appcompat.queryTextChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
|
||||
|
@ -54,11 +53,11 @@ class RoomJoinRuleChooseRestrictedFragment @Inject constructor(
|
|||
controller.listener = this
|
||||
views.recyclerView.configureWith(controller)
|
||||
views.roomsFilter.queryTextChanges()
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.subscribeBy {
|
||||
.debounce(500)
|
||||
.onEach {
|
||||
viewModel.handle(RoomJoinRuleChooseRestrictedActions.FilterWith(it.toString()))
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.okButton.debouncedClicks {
|
||||
parentFragmentManager.popBackStack()
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA
|
|||
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.flow.flow
|
||||
import org.matrix.android.sdk.rx.SecretsSynchronisationInfo
|
||||
|
||||
data class SecretsSynchronisationInfo(
|
||||
val isBackupSetup: Boolean,
|
||||
|
|
|
@ -27,8 +27,6 @@ import im.vector.app.core.error.ErrorFormatter
|
|||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.utils.toast
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -67,31 +65,10 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat() {
|
|||
mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroyView() {
|
||||
uiDisposables.clear()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
uiDisposables.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
abstract fun bindPref()
|
||||
|
||||
abstract var titleRes: Int
|
||||
|
||||
/* ==========================================================================================
|
||||
* Disposable
|
||||
* ========================================================================================== */
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
|
||||
protected fun Disposable.disposeOnDestroyView() {
|
||||
uiDisposables.add(this)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* Protected
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -58,9 +58,6 @@ import im.vector.app.features.pin.PinMode
|
|||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -71,7 +68,6 @@ import org.matrix.android.sdk.api.raw.RawService
|
|||
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse
|
||||
import org.matrix.android.sdk.rx.SecretsSynchronisationInfo
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||
|
@ -86,7 +82,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
|
||||
override var titleRes = R.string.settings_security_and_privacy
|
||||
override val preferenceXmlRes = R.xml.vector_settings_security_privacy
|
||||
private var disposables = mutableListOf<Disposable>()
|
||||
|
||||
// cryptography
|
||||
private val mCryptographyCategory by lazy {
|
||||
|
@ -149,11 +144,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
refreshMyDevice()
|
||||
refreshXSigningStatus()
|
||||
session.liveSecretSynchronisationInfo()
|
||||
.flowOn(Dispatchers.Main)
|
||||
.onEach {
|
||||
refresh4SSection(it)
|
||||
refreshXSigningStatus()
|
||||
}.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
lifecycleScope.launchWhenResumed {
|
||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
|
||||
|
@ -173,14 +168,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
|||
// findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY)
|
||||
// }
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
disposables.forEach {
|
||||
it.dispose()
|
||||
}
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
private fun refresh4SSection(info: SecretsSynchronisationInfo) {
|
||||
// it's a lot of if / else if / else
|
||||
// But it's not yet clear how to manage all cases
|
||||
|
|
|
@ -29,11 +29,12 @@ import dagger.assisted.AssistedInject
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.flow.throttleFirst
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.PublishDataSource
|
||||
import im.vector.app.features.auth.ReAuthActivity
|
||||
import im.vector.app.features.login.ReAuthHelper
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
@ -64,7 +65,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
@ -103,7 +103,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
|
||||
companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
|
||||
|
||||
private val refreshPublisher: PublishSubject<Unit> = PublishSubject.create()
|
||||
private val refreshSource = PublishDataSource<Unit>()
|
||||
|
||||
init {
|
||||
|
||||
|
@ -166,12 +166,12 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
// )
|
||||
// }
|
||||
|
||||
refreshPublisher.throttleFirst(4_000, TimeUnit.MILLISECONDS)
|
||||
.subscribe {
|
||||
refreshSource.stream().throttleFirst(4_000)
|
||||
.onEach {
|
||||
session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
|
||||
session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback())
|
||||
}
|
||||
.disposeOnClear()
|
||||
.launchIn(viewModelScope)
|
||||
// then force download
|
||||
queryRefreshDevicesList()
|
||||
}
|
||||
|
@ -193,7 +193,7 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
* It can be any mobile devices, and any browsers.
|
||||
*/
|
||||
private fun queryRefreshDevicesList() {
|
||||
refreshPublisher.onNext(Unit)
|
||||
refreshSource.post(Unit)
|
||||
}
|
||||
|
||||
override fun handle(action: DevicesAction) {
|
||||
|
|
|
@ -50,7 +50,7 @@ class SoftLogoutActivity : LoginActivity() {
|
|||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
|
||||
softLogoutViewModel.subscribe(this) {
|
||||
softLogoutViewModel.onEach {
|
||||
updateWithState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class SoftLogoutActivity2 : LoginActivity2() {
|
|||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
|
||||
softLogoutViewModel.subscribe(this) {
|
||||
softLogoutViewModel.onEach {
|
||||
updateWithState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class SoftLogoutFragment @Inject constructor(
|
|||
|
||||
setupRecyclerView()
|
||||
|
||||
softLogoutViewModel.subscribe(this) { softLogoutViewState ->
|
||||
softLogoutViewModel.onEach { softLogoutViewState ->
|
||||
softLogoutController.update(softLogoutViewState)
|
||||
when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) {
|
||||
is LoginMode.SsoAndPassword -> {
|
||||
|
|
|
@ -26,12 +26,12 @@ import android.view.ViewGroup
|
|||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.parentFragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.widget.checkedChanges
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
|
@ -43,10 +43,12 @@ import im.vector.app.core.utils.styleMatchingText
|
|||
import im.vector.app.databinding.BottomSheetLeaveSpaceBinding
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.gujun.android.span.span
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import reactivecircus.flowbinding.android.widget.checkedChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -82,8 +84,7 @@ class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetLea
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
views.autoLeaveRadioGroup.checkedChanges()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
.onEach {
|
||||
when (it) {
|
||||
views.leaveAll.id -> {
|
||||
settingsViewModel.handle(SpaceLeaveViewAction.SetAutoLeaveAll)
|
||||
|
@ -100,7 +101,7 @@ class LeaveSpaceBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetLea
|
|||
}
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
views.leaveButton.debouncedClicks {
|
||||
settingsViewModel.handle(SpaceLeaveViewAction.LeaveSpace)
|
||||
|
|
|
@ -71,7 +71,7 @@ class SpaceCreationActivity : SimpleFragmentActivity() {
|
|||
override fun initUiAndData() {
|
||||
super.initUiAndData()
|
||||
|
||||
viewModel.subscribe(this) {
|
||||
viewModel.onEach {
|
||||
renderState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import im.vector.app.group
|
|||
import im.vector.app.space
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
@ -89,14 +90,11 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
|
|||
// observeSelectionState()
|
||||
appStateHandler.selectedRoomGroupingObservable
|
||||
.distinctUntilChanged()
|
||||
.subscribe {
|
||||
setState {
|
||||
copy(
|
||||
selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||
)
|
||||
}
|
||||
.setOnEach {
|
||||
copy(
|
||||
selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null)
|
||||
)
|
||||
}
|
||||
.disposeOnClear()
|
||||
|
||||
session.getGroupSummariesLive(groupSummaryQueryParams {})
|
||||
.asFlow()
|
||||
|
@ -114,7 +112,6 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
|
|||
}, sortOrder = RoomSortOrder.NONE
|
||||
).asFlow()
|
||||
.sample(300)
|
||||
.flowOn(Dispatchers.Default)
|
||||
.onEach {
|
||||
val inviteCount = if (autoAcceptInvites.hideInvites) {
|
||||
0
|
||||
|
@ -140,7 +137,9 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
|
|||
homeAggregateCount = counts
|
||||
)
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
.flowOn(Dispatchers.Default)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handle(action: SpaceListAction) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.spaces
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
|
@ -27,6 +28,8 @@ import im.vector.app.core.platform.VectorBaseActivity
|
|||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
import im.vector.app.features.spaces.preview.SpacePreviewArgs
|
||||
import im.vector.app.features.spaces.preview.SpacePreviewFragment
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||
|
@ -39,8 +42,8 @@ class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
|||
super.onCreate(savedInstanceState)
|
||||
sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
.stream()
|
||||
.onEach { action ->
|
||||
when (action) {
|
||||
SpacePreviewSharedAction.DismissAction -> finish()
|
||||
SpacePreviewSharedAction.ShowModalLoading -> showWaitingView()
|
||||
|
@ -48,7 +51,7 @@ class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
|||
is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) }
|
||||
}
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
if (isFirstCreation()) {
|
||||
val simpleName = SpacePreviewFragment::class.java.simpleName
|
||||
|
|
|
@ -50,7 +50,7 @@ class CreateSpaceAdd3pidInvitesFragment @Inject constructor(
|
|||
views.recyclerView.configureWith(epoxyController)
|
||||
epoxyController.listener = this
|
||||
|
||||
sharedViewModel.subscribe(this) {
|
||||
sharedViewModel.onEach {
|
||||
invalidateState(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ class CreateSpaceDefaultRoomsFragment @Inject constructor(
|
|||
views.recyclerView.configureWith(epoxyController)
|
||||
epoxyController.listener = this
|
||||
|
||||
sharedViewModel.subscribe(this) {
|
||||
sharedViewModel.onEach {
|
||||
epoxyController.setData(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class CreateSpaceDetailsFragment @Inject constructor(
|
|||
views.recyclerView.configureWith(epoxyController)
|
||||
epoxyController.listener = this
|
||||
|
||||
sharedViewModel.subscribe(this) {
|
||||
sharedViewModel.onEach {
|
||||
epoxyController.setData(it)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.spaces.leave
|
||||
|
||||
import androidx.core.util.Predicate
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
|
@ -27,7 +28,6 @@ import im.vector.app.core.epoxy.noResultItem
|
|||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.spaces.manage.roomSelectionItem
|
||||
import io.reactivex.functions.Predicate
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue