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