Merge branch 'cross_signing' into xsigning_sdk

This commit is contained in:
Valere 2020-01-28 11:33:54 +01:00
commit 109ff4f908
85 changed files with 1124 additions and 370 deletions

View File

@ -9,6 +9,8 @@
<w>decryptor</w> <w>decryptor</w>
<w>emoji</w> <w>emoji</w>
<w>emojis</w> <w>emojis</w>
<w>fdroid</w>
<w>gplay</w>
<w>hmac</w> <w>hmac</w>
<w>ktlint</w> <w>ktlint</w>
<w>linkified</w> <w>linkified</w>

View File

@ -0,0 +1,35 @@
A full developer contributors list can be found [here](https://github.com/vector-im/riotX-android/graphs/contributors).
# Core team:
Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves.
## Benoit: Android team leader
[@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org)
- Android team leader and project leader, Android developer, GitHub community manager.
- Specialist of the account creation, and many other fun features.
- Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager.
- Release manager on the Play Store
## François: Software architect
[@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org)
- Software architect, Android developer
- First developer on the project.
- Work mainly on the global architecture of the project.
- Specialist of the timeline, and lots of other features.
## Valere: Product manager, Android developer
[@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org)
- Product manager, Android developer
- Specialist on the crypto implementation.
# Other contributors
First of all, we thank all contributors who use RiotX and report problems on this GitHub project or via the integrated rageshake function.
We do not forget all translators, for their work of translating RiotX into many languages. They are also the authors of RiotX.
Feel free to add your name below, when you contribute to the project!

View File

@ -2,10 +2,10 @@ Changes in RiotX 0.14.0 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
- - Enable encryption in unencrypted rooms, from the room settings (#212)
Improvements 🙌: Improvements 🙌:
- - Sharing things to RiotX: sort list by recent room first (#771)
Other changes: Other changes:
- -
@ -17,7 +17,8 @@ Translations 🗣:
- -
Build 🧱: Build 🧱:
- - Ensure builds are reproducible (#842)
- F-Droid: fix the "-dev" issue in version name (#815)
Changes in RiotX 0.13.0 (2020-01-17) Changes in RiotX 0.13.0 (2020-01-17)
=================================================== ===================================================

View File

@ -17,7 +17,7 @@ Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f6
# New Android SDK # New Android SDK
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough. RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the SDK is stable enough.
# Roadmap # Roadmap

View File

@ -74,7 +74,7 @@ android {
} }
static def gitRevision() { static def gitRevision() {
def cmd = "git rev-parse --short HEAD" def cmd = "git rev-parse --short=8 HEAD"
return cmd.execute().text.trim() return cmd.execute().text.trim()
} }

View File

@ -45,7 +45,8 @@ data class RoomSummary(
val readMarkerId: String? = null, val readMarkerId: String? = null,
val userDrafts: List<UserDraft> = emptyList(), val userDrafts: List<UserDraft> = emptyList(),
var isEncrypted: Boolean, var isEncrypted: Boolean,
val typingRoomMemberIds: List<String> = emptyList() val typingRoomMemberIds: List<String> = emptyList(),
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS
) { ) {
val isVersioned: Boolean val isVersioned: Boolean
@ -53,4 +54,8 @@ data class RoomSummary(
val hasNewMessages: Boolean val hasNewMessages: Boolean
get() = notificationCount != 0 get() = notificationCount != 0
companion object {
const val NOT_IN_BREADCRUMBS = -1
}
} }

View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import java.util.* import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor( internal class RoomSummaryMapper @Inject constructor(
@ -74,7 +74,8 @@ internal class RoomSummaryMapper @Inject constructor(
canonicalAlias = roomSummaryEntity.canonicalAlias, canonicalAlias = roomSummaryEntity.canonicalAlias,
aliases = roomSummaryEntity.aliases.toList(), aliases = roomSummaryEntity.aliases.toList(),
isEncrypted = roomSummaryEntity.isEncrypted, isEncrypted = roomSummaryEntity.isEncrypted,
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList() typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex
) )
} }
} }

View File

@ -17,35 +17,37 @@
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.VersioningState
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", internal open class RoomSummaryEntity(
var displayName: String? = "", @PrimaryKey var roomId: String = "",
var avatarUrl: String? = "", var displayName: String? = "",
var topic: String? = "", var avatarUrl: String? = "",
var latestPreviewableEvent: TimelineEventEntity? = null, var topic: String? = "",
var heroes: RealmList<String> = RealmList(), var latestPreviewableEvent: TimelineEventEntity? = null,
var joinedMembersCount: Int? = 0, var heroes: RealmList<String> = RealmList(),
var invitedMembersCount: Int? = 0, var joinedMembersCount: Int? = 0,
var isDirect: Boolean = false, var invitedMembersCount: Int? = 0,
var directUserId: String? = null, var isDirect: Boolean = false,
var otherMemberIds: RealmList<String> = RealmList(), var directUserId: String? = null,
var notificationCount: Int = 0, var otherMemberIds: RealmList<String> = RealmList(),
var highlightCount: Int = 0, var notificationCount: Int = 0,
var readMarkerId: String? = null, var highlightCount: Int = 0,
var hasUnreadMessages: Boolean = false, var readMarkerId: String? = null,
var tags: RealmList<RoomTagEntity> = RealmList(), var hasUnreadMessages: Boolean = false,
var userDrafts: UserDraftsEntity? = null, var tags: RealmList<RoomTagEntity> = RealmList(),
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, var userDrafts: UserDraftsEntity? = null,
var canonicalAlias: String? = null, var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,
var aliases: RealmList<String> = RealmList(), var canonicalAlias: String? = null,
// this is required for querying var aliases: RealmList<String> = RealmList(),
var flatAliases: String = "", // this is required for querying
var isEncrypted: Boolean = false, var flatAliases: String = "",
var typingUserIds: RealmList<String> = RealmList() var isEncrypted: Boolean = false,
var typingUserIds: RealmList<String> = RealmList()
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name
@ -66,7 +68,5 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
versioningStateStr = value.name versioningStateStr = value.name
} }
companion object { companion object
const val NOT_IN_BREADCRUMBS = -1
}
} }

View File

@ -152,7 +152,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
return RoomSummaryEntity.where(realm) return RoomSummaryEntity.where(realm)
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
} }

View File

@ -21,16 +21,26 @@ import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.pushrules.RuleSetKey import im.vector.matrix.android.api.pushrules.RuleSetKey
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.getDirectRooms import im.vector.matrix.android.internal.database.query.getDirectRooms
import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.accountdata.* import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import io.realm.Realm import io.realm.Realm
@ -177,10 +187,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(
// Update the room summaries // Update the room summaries
// Reset all the indexes... // Reset all the indexes...
RoomSummaryEntity.where(realm) RoomSummaryEntity.where(realm)
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
.findAll() .findAll()
.forEach { .forEach {
it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS it.breadcrumbsIndex = RoomSummary.NOT_IN_BREADCRUMBS
} }
// ...and apply new indexes // ...and apply new indexes

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.session.user.accountdata package im.vector.matrix.android.internal.session.user.accountdata
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
@ -50,10 +51,10 @@ internal class DefaultSaveBreadcrumbsTask @Inject constructor(
// Update the room summaries // Update the room summaries
// Reset all the indexes... // Reset all the indexes...
RoomSummaryEntity.where(realm) RoomSummaryEntity.where(realm)
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
.findAll() .findAll()
.forEach { .forEach {
it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS it.breadcrumbsIndex = RoomSummary.NOT_IN_BREADCRUMBS
} }
// ...and apply new indexes // ...and apply new indexes

View File

@ -45,7 +45,7 @@ def getVersionCode() {
} }
static def gitRevision() { static def gitRevision() {
def cmd = "git rev-parse --short HEAD" def cmd = "git rev-parse --short=8 HEAD"
return cmd.execute().text.trim() return cmd.execute().text.trim()
} }
@ -66,7 +66,8 @@ static def gitBranchName() {
} }
} }
static def getVersionSuffix() { // For Google Play build, build on any other branch than master will have a "-dev" suffix
static def getGplayVersionSuffix() {
if (gitBranchName() == "master") { if (gitBranchName() == "master") {
return "" return ""
} else { } else {
@ -74,6 +75,20 @@ static def getVersionSuffix() {
} }
} }
static def gitTag() {
def cmd = "git describe --exact-match --tags"
return cmd.execute().text.trim()
}
// For F-Droid build, build on a not tagged commit will have a "-dev" suffix
static def getFdroidVersionSuffix() {
if (gitTag() == "") {
return "-dev"
} else {
return ""
}
}
project.android.buildTypes.all { buildType -> project.android.buildTypes.all { buildType ->
buildType.javaCompileOptions.annotationProcessorOptions.arguments = buildType.javaCompileOptions.annotationProcessorOptions.arguments =
[ [
@ -102,8 +117,6 @@ android {
// Other branches (master, features, etc.) will have version code based on application version. // Other branches (master, features, etc.) will have version code based on application version.
versionCode project.getVersionCode() versionCode project.getVersionCode()
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getVersionSuffix()}"
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
resValue "string", "git_revision", "\"${gitRevision()}\"" resValue "string", "git_revision", "\"${gitRevision()}\""
@ -190,6 +203,8 @@ android {
gplay { gplay {
dimension "store" dimension "store"
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
resValue "bool", "isGplay", "true" resValue "bool", "isGplay", "true"
buildConfigField "boolean", "ALLOW_FCM_USE", "true" buildConfigField "boolean", "ALLOW_FCM_USE", "true"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"" buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
@ -199,6 +214,8 @@ android {
fdroid { fdroid {
dimension "store" dimension "store"
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
resValue "bool", "isGplay", "false" resValue "bool", "isGplay", "false"
buildConfigField "boolean", "ALLOW_FCM_USE", "false" buildConfigField "boolean", "ALLOW_FCM_USE", "false"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"" buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""

View File

@ -61,6 +61,7 @@ import im.vector.riotx.features.roommemberprofile.devices.DeviceListFragment
import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionFragment import im.vector.riotx.features.roommemberprofile.devices.DeviceTrustInfoActionFragment
import im.vector.riotx.features.roomprofile.RoomProfileFragment import im.vector.riotx.features.roomprofile.RoomProfileFragment
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
import im.vector.riotx.features.settings.VectorSettingsLabsFragment import im.vector.riotx.features.settings.VectorSettingsLabsFragment
@ -278,6 +279,11 @@ interface FragmentModule {
@FragmentKey(RoomMemberListFragment::class) @FragmentKey(RoomMemberListFragment::class)
fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomSettingsFragment::class)
fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(RoomMemberProfileFragment::class) @FragmentKey(RoomMemberProfileFragment::class)

View File

@ -54,6 +54,9 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.view.setOnClickListener(listener) holder.view.setOnClickListener(listener)
if (listener == null) {
holder.view.isClickable = false
}
holder.editable.isVisible = editable holder.editable.isVisible = editable
holder.title.text = title holder.title.text = title
val tintColor = if (destructive) { val tintColor = if (destructive) {

View File

@ -19,6 +19,7 @@ package im.vector.riotx.core.epoxy.profiles
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import im.vector.riotx.core.epoxy.ClickListener
import im.vector.riotx.core.epoxy.dividerItem import im.vector.riotx.core.epoxy.dividerItem
fun EpoxyController.buildProfileSection(title: String) { fun EpoxyController.buildProfileSection(title: String) {
@ -38,7 +39,7 @@ fun EpoxyController.buildProfileAction(
@DrawableRes editableRes: Int? = null, @DrawableRes editableRes: Int? = null,
destructive: Boolean = false, destructive: Boolean = false,
divider: Boolean = true, divider: Boolean = true,
action: (() -> Unit)? = null action: ClickListener? = null
) { ) {
profileActionItem { profileActionItem {
iconRes(icon) iconRes(icon)
@ -50,12 +51,8 @@ fun EpoxyController.buildProfileAction(
} }
destructive(destructive) destructive(destructive)
title(title) title(title)
apply { listener { _ ->
action?.let { action?.invoke()
listener { _ ->
it()
}
}
} }
} }

View File

@ -0,0 +1,20 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.extensions
// Trick to ensure that when block is exhaustive
val <T> T.exhaustive: T get() = this

View File

@ -22,10 +22,15 @@ import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.view.* import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
@ -35,11 +40,13 @@ import com.airbnb.mvrx.BaseMvRxFragment
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.bumptech.glide.util.Util.assertMainThread import com.bumptech.glide.util.Util.assertMainThread
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import im.vector.riotx.R
import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.DaggerScreenComponent
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.navigation.Navigator
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import timber.log.Timber import timber.log.Timber
@ -120,6 +127,14 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
mUnBinder = ButterKnife.bind(this, view) mUnBinder = ButterKnife.bind(this, view)
} }
open fun showLoading(message: CharSequence?) {
showLoadingDialog(message)
}
open fun showFailure(throwable: Throwable) {
displayErrorDialog(throwable)
}
@CallSuper @CallSuper
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
@ -182,10 +197,10 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
} }
} }
protected fun showLoadingDialog(message: CharSequence, cancelable: Boolean = false) { protected fun showLoadingDialog(message: CharSequence? = null, cancelable: Boolean = false) {
progress = ProgressDialog(requireContext()).apply { progress = ProgressDialog(requireContext()).apply {
setCancelable(cancelable) setCancelable(cancelable)
setMessage(message) setMessage(message ?: getString(R.string.please_wait))
setProgressStyle(ProgressDialog.STYLE_SPINNER) setProgressStyle(ProgressDialog.STYLE_SPINNER)
show() show()
} }
@ -220,6 +235,21 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
return this return this
} }
/* ==========================================================================================
* ViewEvents
* ========================================================================================== */
protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
viewEvents
.observe()
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
dismissLoadingDialog()
observer(it)
}
.disposeOnDestroyView()
}
/* ========================================================================================== /* ==========================================================================================
* MENU MANAGEMENT * MENU MANAGEMENT
* ========================================================================================== */ * ========================================================================================== */
@ -233,4 +263,16 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
inflater.inflate(menuRes, menu) inflater.inflate(menuRes, menu)
} }
} }
/* ==========================================================================================
* Common Dialogs
* ========================================================================================== */
protected fun displayErrorDialog(throwable: Throwable) {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
.setPositiveButton(R.string.ok, null)
.show()
}
} }

View File

@ -0,0 +1,27 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.core.platform
/**
* Interface for View Events
*/
interface VectorViewEvents
/**
* To use when no view events is associated to the ViewModel
*/
object EmptyViewEvents : VectorViewEvents

View File

@ -16,20 +16,23 @@
package im.vector.riotx.core.platform package im.vector.riotx.core.platform
import androidx.lifecycle.LiveData import com.airbnb.mvrx.Async
import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.BaseMvRxViewModel
import com.airbnb.mvrx.* import com.airbnb.mvrx.Fail
import im.vector.riotx.core.utils.LiveEvent import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Success
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
abstract class VectorViewModel<S : MvRxState, A : VectorViewModelAction>(initialState: S) abstract class VectorViewModel<S : MvRxState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S)
: BaseMvRxViewModel<S>(initialState, false) { : BaseMvRxViewModel<S>(initialState, false) {
// Generic handling of any request error // Used to post transient events to the View
protected val _requestErrorLiveData = MutableLiveData<LiveEvent<Throwable>>() protected val _viewEvents = PublishDataSource<VE>()
val requestErrorLiveData: LiveData<LiveEvent<Throwable>> val viewEvents: DataSource<VE> = _viewEvents
get() = _requestErrorLiveData
/** /**
* This method does the same thing as the execute function, but it doesn't subscribe to the stream * This method does the same thing as the execute function, but it doesn't subscribe to the stream
@ -53,5 +56,5 @@ abstract class VectorViewModel<S : MvRxState, A : VectorViewModelAction>(initial
.doOnNext { setState { stateReducer(it) } } .doOnNext { setState { stateReducer(it) } }
} }
abstract fun handle(action: A) abstract fun handle(action: VA)
} }

View File

@ -23,7 +23,11 @@ import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.* import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.setupAsSearch
import im.vector.riotx.core.extensions.showKeyboard
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.* import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
import javax.inject.Inject import javax.inject.Inject

View File

@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import io.reactivex.Single import io.reactivex.Single
@ -51,7 +52,7 @@ data class SelectUserAction(
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
initialState: CreateDirectRoomViewState, initialState: CreateDirectRoomViewState,
private val session: Session) private val session: Session)
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction>(initialState) { : VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -24,11 +24,12 @@ import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
session: Session session: Session
) : VectorViewModel<KeysBackupSettingViewState, KeyBackupSettingsAction>(initialState), ) : VectorViewModel<KeysBackupSettingViewState, KeyBackupSettingsAction, EmptyViewEvents>(initialState),
KeysBackupStateListener { KeysBackupStateListener {
@AssistedInject.Factory @AssistedInject.Factory

View File

@ -43,6 +43,7 @@ import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
@ -58,8 +59,8 @@ data class VerificationBottomSheetViewState(
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState, class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
private val session: Session) private val session: Session)
: VectorViewModel<VerificationBottomSheetViewState, VerificationAction>(initialState), : VectorViewModel<VerificationBottomSheetViewState, VerificationAction, EmptyViewEvents>(initialState),
VerificationService.VerificationListener { SasVerificationService.SasVerificationListener {
// Can be used for several actions, for a one shot result // Can be used for several actions, for a one shot result
private val _requestLiveData = MutableLiveData<LiveEvent<Async<VerificationAction>>>() private val _requestLiveData = MutableLiveData<LiveEvent<Async<VerificationAction>>>()

View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
@ -43,7 +44,7 @@ data class VerificationChooseMethodViewState(
class VerificationChooseMethodViewModel @AssistedInject constructor( class VerificationChooseMethodViewModel @AssistedInject constructor(
@Assisted initialState: VerificationChooseMethodViewState, @Assisted initialState: VerificationChooseMethodViewState,
private val session: Session private val session: Session
) : VectorViewModel<VerificationChooseMethodViewState, EmptyAction>(initialState), VerificationService.VerificationListener { ) : VectorViewModel<VerificationChooseMethodViewState, EmptyAction, EmptyViewEvents>(initialState), SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {} override fun transactionCreated(tx: VerificationTransaction) {}

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.matrix.android.api.session.crypto.sas.safeValueOf
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
data class VerificationConclusionViewState( data class VerificationConclusionViewState(
@ -34,7 +35,7 @@ enum class ConclusionState {
} }
class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) class VerificationConclusionViewModel(initialState: VerificationConclusionViewState)
: VectorViewModel<VerificationConclusionViewState, EmptyAction>(initialState) { : VectorViewModel<VerificationConclusionViewState, EmptyAction, EmptyViewEvents>(initialState) {
companion object : MvRxViewModelFactory<VerificationConclusionViewModel, VerificationConclusionViewState> { companion object : MvRxViewModelFactory<VerificationConclusionViewModel, VerificationConclusionViewState> {

View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.crypto.verification.VerificationBottomSheet
@ -43,7 +44,7 @@ data class VerificationEmojiCodeViewState(
class VerificationEmojiCodeViewModel @AssistedInject constructor( class VerificationEmojiCodeViewModel @AssistedInject constructor(
@Assisted initialState: VerificationEmojiCodeViewState, @Assisted initialState: VerificationEmojiCodeViewState,
private val session: Session private val session: Session
) : VectorViewModel<VerificationEmojiCodeViewState, EmptyAction>(initialState), VerificationService.VerificationListener { ) : VectorViewModel<VerificationEmojiCodeViewState, EmptyAction, EmptyViewEvents>(initialState), SasVerificationService.SasVerificationListener {
init { init {
withState { state -> withState { state ->

View File

@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
@ -45,7 +46,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
private val selectedGroupStore: SelectedGroupDataSource, private val selectedGroupStore: SelectedGroupDataSource,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : VectorViewModel<GroupListViewState, GroupListAction>(initialState) { ) : VectorViewModel<GroupListViewState, GroupListAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -24,6 +24,7 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.grouplist.SelectedGroupDataSource import im.vector.riotx.features.grouplist.SelectedGroupDataSource
@ -40,7 +41,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
private val selectedGroupStore: SelectedGroupDataSource, private val selectedGroupStore: SelectedGroupDataSource,
private val homeRoomListStore: HomeRoomListDataSource, private val homeRoomListStore: HomeRoomListDataSource,
private val stringProvider: StringProvider) private val stringProvider: StringProvider)
: VectorViewModel<HomeDetailViewState, HomeDetailAction>(initialState) { : VectorViewModel<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -24,12 +24,13 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState,
private val session: Session) private val session: Session)
: VectorViewModel<BreadcrumbsViewState, EmptyAction>(initialState) { : VectorViewModel<BreadcrumbsViewState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -87,6 +87,7 @@ import im.vector.riotx.R
import im.vector.riotx.core.dialogs.withColoredButton import im.vector.riotx.core.dialogs.withColoredButton
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
@ -309,15 +310,11 @@ class RoomDetailFragment @Inject constructor(
displayRoomDetailActionResult(it) displayRoomDetailActionResult(it)
} }
roomDetailViewModel.viewEvents roomDetailViewModel.observeViewEvents {
.observe() when (it) {
.observeOn(AndroidSchedulers.mainThread()) is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
.subscribe { }.exhaustive
when (it) { }
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
}
}
.disposeOnDestroyView()
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {

View File

@ -16,9 +16,11 @@
package im.vector.riotx.features.home.room.detail package im.vector.riotx.features.home.room.detail
import im.vector.riotx.core.platform.VectorViewEvents
/** /**
* Transient events for RoomDetail * Transient events for RoomDetail
*/ */
sealed class RoomDetailViewEvents { sealed class RoomDetailViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomDetailViewEvents() data class Failure(val throwable: Throwable) : RoomDetailViewEvents()
} }

View File

@ -20,7 +20,12 @@ import android.net.Uri
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import com.jakewharton.rxrelay2.PublishRelay import com.jakewharton.rxrelay2.PublishRelay
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
@ -55,9 +60,7 @@ import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.command.ParsedCommand
@ -82,7 +85,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val typingHelper: TypingHelper, private val typingHelper: TypingHelper,
private val session: Session private val session: Session
) : VectorViewModel<RoomDetailViewState, RoomDetailAction>(initialState), Timeline.Listener { ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
private val eventId = initialState.eventId private val eventId = initialState.eventId
@ -105,9 +108,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
var timeline = room.createTimeline(eventId, timelineSettings) var timeline = room.createTimeline(eventId, timelineSettings)
private set private set
private val _viewEvents = PublishDataSource<RoomDetailViewEvents>()
val viewEvents: DataSource<RoomDetailViewEvents> = _viewEvents
// Can be used for several actions, for a one shot result // Can be used for several actions, for a one shot result
private val _requestLiveData = MutableLiveData<LiveEvent<Async<RoomDetailAction>>>() private val _requestLiveData = MutableLiveData<LiveEvent<Async<RoomDetailAction>>>()
val requestLiveData: LiveData<LiveEvent<Async<RoomDetailAction>>> val requestLiveData: LiveData<LiveEvent<Async<RoomDetailAction>>>
@ -295,6 +295,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
} }
// TODO Cleanup this and use ViewEvents
private val _nonBlockingPopAlert = MutableLiveData<LiveEvent<Pair<Int, List<Any>>>>() private val _nonBlockingPopAlert = MutableLiveData<LiveEvent<Pair<Int, List<Any>>>>()
val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>> val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>>
get() = _nonBlockingPopAlert get() = _nonBlockingPopAlert

View File

@ -32,6 +32,7 @@ import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.canReact import im.vector.riotx.core.extensions.canReact
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
@ -86,7 +87,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private val noticeEventFormatter: NoticeEventFormatter, private val noticeEventFormatter: NoticeEventFormatter,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences private val vectorPreferences: VectorPreferences
) : VectorViewModel<MessageActionState, MessageActionsAction>(initialState) { ) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
private val eventId = initialState.eventId private val eventId = initialState.eventId
private val informationData = initialState.informationData private val informationData = initialState.informationData

View File

@ -15,7 +15,15 @@
*/ */
package im.vector.riotx.features.home.room.detail.timeline.edithistory package im.vector.riotx.features.home.room.detail.timeline.edithistory
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
@ -28,10 +36,11 @@ import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.UUID
data class ViewEditHistoryViewState( data class ViewEditHistoryViewState(
val eventId: String, val eventId: String,
@ -47,7 +56,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
initialState: ViewEditHistoryViewState, initialState: ViewEditHistoryViewState,
val session: Session, val session: Session,
val dateFormatter: VectorDateFormatter val dateFormatter: VectorDateFormatter
) : VectorViewModel<ViewEditHistoryViewState, EmptyAction>(initialState) { ) : VectorViewModel<ViewEditHistoryViewState, EmptyAction, EmptyViewEvents>(initialState) {
private val roomId = initialState.roomId private val roomId = initialState.roomId
private val eventId = initialState.eventId private val eventId = initialState.eventId

View File

@ -16,7 +16,12 @@
package im.vector.riotx.features.home.room.detail.timeline.reactions package im.vector.riotx.features.home.room.detail.timeline.reactions
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
@ -25,6 +30,7 @@ import im.vector.matrix.rx.RxRoom
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
import io.reactivex.Observable import io.reactivex.Observable
@ -54,7 +60,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
initialState: DisplayReactionsViewState, initialState: DisplayReactionsViewState,
private val session: Session, private val session: Session,
private val dateFormatter: VectorDateFormatter private val dateFormatter: VectorDateFormatter
) : VectorViewModel<DisplayReactionsViewState, EmptyAction>(initialState) { ) : VectorViewModel<DisplayReactionsViewState, EmptyAction, EmptyViewEvents>(initialState) {
private val roomId = initialState.roomId private val roomId = initialState.roomId
private val eventId = initialState.eventId private val eventId = initialState.eventId

View File

@ -0,0 +1,44 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.home.room.list
import im.vector.matrix.android.api.session.room.model.RoomSummary
import javax.inject.Inject
class BreadcrumbsRoomComparator @Inject constructor(
private val chronologicalRoomComparator: ChronologicalRoomComparator
) : Comparator<RoomSummary> {
override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int {
val leftBreadcrumbsIndex = leftRoomSummary?.breadcrumbsIndex ?: RoomSummary.NOT_IN_BREADCRUMBS
val rightBreadcrumbsIndex = rightRoomSummary?.breadcrumbsIndex ?: RoomSummary.NOT_IN_BREADCRUMBS
return if (leftBreadcrumbsIndex == RoomSummary.NOT_IN_BREADCRUMBS) {
if (rightBreadcrumbsIndex == RoomSummary.NOT_IN_BREADCRUMBS) {
chronologicalRoomComparator.compare(leftRoomSummary, rightRoomSummary)
} else {
1
}
} else {
if (rightBreadcrumbsIndex == RoomSummary.NOT_IN_BREADCRUMBS) {
-1
} else {
leftBreadcrumbsIndex - rightBreadcrumbsIndex
}
}
}
}

View File

@ -22,26 +22,20 @@ import javax.inject.Inject
class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary> { class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary> {
override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int {
var rightTimestamp = 0L return when {
var leftTimestamp = 0L rightRoomSummary?.latestPreviewableEvent?.root == null -> -1
if (null != leftRoomSummary) { leftRoomSummary?.latestPreviewableEvent?.root == null -> 1
leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0 else -> {
} val rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
if (null != rightRoomSummary) { val leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
} val deltaTimestamp = rightTimestamp - leftTimestamp
return if (rightRoomSummary?.latestPreviewableEvent?.root == null) {
-1 when {
} else if (leftRoomSummary?.latestPreviewableEvent?.root == null) { deltaTimestamp > 0 -> 1
1 deltaTimestamp < 0 -> -1
} else { else -> 0
val deltaTimestamp = rightTimestamp - leftTimestamp }
if (deltaTimestamp > 0) {
1
} else if (deltaTimestamp < 0) {
-1
} else {
0
} }
} }
} }

View File

@ -27,7 +27,11 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.mvrx.* import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
@ -35,6 +39,7 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.StateView
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
@ -46,7 +51,6 @@ import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsShare
import im.vector.riotx.features.home.room.list.widget.FabMenuView import im.vector.riotx.features.home.room.list.widget.FabMenuView
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.share.SharedData
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.* import kotlinx.android.synthetic.main.fragment_room_list.*
import javax.inject.Inject import javax.inject.Inject
@ -98,16 +102,13 @@ class RoomListFragment @Inject constructor(
setupRecyclerView() setupRecyclerView()
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomListViewModel.subscribe { renderState(it) } roomListViewModel.subscribe { renderState(it) }
roomListViewModel.viewEvents roomListViewModel.observeViewEvents {
.observe() when (it) {
.observeOn(AndroidSchedulers.mainThread()) is RoomListViewEvents.Loading -> showLoading(it.message)
.subscribe { is RoomListViewEvents.Failure -> showFailure(it.throwable)
when (it) { is RoomListViewEvents.SelectRoom -> openSelectedRoom(it)
is RoomListViewEvents.SelectRoom -> openSelectedRoom(it) }.exhaustive
is RoomListViewEvents.Failure -> showErrorInSnackbar(it.throwable) }
}
}
.disposeOnDestroyView()
createChatFabMenu.listener = this createChatFabMenu.listener = this
@ -117,6 +118,10 @@ class RoomListFragment @Inject constructor(
.disposeOnDestroyView() .disposeOnDestroyView()
} }
override fun showFailure(throwable: Throwable) {
showErrorInSnackbar(throwable)
}
override fun onDestroyView() { override fun onDestroyView() {
roomController.removeModelBuildListener(modelBuildListener) roomController.removeModelBuildListener(modelBuildListener)
modelBuildListener = null modelBuildListener = null

View File

@ -17,10 +17,14 @@
package im.vector.riotx.features.home.room.list package im.vector.riotx.features.home.room.list
import im.vector.riotx.core.platform.VectorViewEvents
/** /**
* Transient events for RoomList * Transient events for RoomList
*/ */
sealed class RoomListViewEvents { sealed class RoomListViewEvents : VectorViewEvents {
data class Loading(val message: CharSequence? = null) : RoomListViewEvents()
data class Failure(val throwable: Throwable) : RoomListViewEvents() data class Failure(val throwable: Throwable) : RoomListViewEvents()
data class SelectRoom(val roomId: String) : RoomListViewEvents() data class SelectRoom(val roomId: String) : RoomListViewEvents()
} }

View File

@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.DataSource import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.features.home.RoomListDisplayMode
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -34,7 +34,7 @@ import javax.inject.Inject
class RoomListViewModel @Inject constructor(initialState: RoomListViewState, class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val session: Session, private val session: Session,
private val roomSummariesSource: DataSource<List<RoomSummary>>) private val roomSummariesSource: DataSource<List<RoomSummary>>)
: VectorViewModel<RoomListViewState, RoomListAction>(initialState) { : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
interface Factory { interface Factory {
fun create(initialState: RoomListViewState): RoomListViewModel fun create(initialState: RoomListViewState): RoomListViewModel
@ -52,9 +52,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val displayMode = initialState.displayMode private val displayMode = initialState.displayMode
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode) private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
private val _viewEvents = PublishDataSource<RoomListViewEvents>()
val viewEvents: DataSource<RoomListViewEvents> = _viewEvents
init { init {
observeRoomSummaries() observeRoomSummaries()
} }
@ -197,6 +194,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
} }
private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
_viewEvents.post(RoomListViewEvents.Loading(null))
session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> { session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
_viewEvents.post(RoomListViewEvents.Failure(failure)) _viewEvents.post(RoomListViewEvents.Failure(failure))
@ -205,35 +203,54 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
} }
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries { private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
// Set up init size on directChats and groupRooms as they are the biggest ones if (displayMode == RoomListDisplayMode.SHARE) {
val invites = ArrayList<RoomSummary>() val recentRooms = ArrayList<RoomSummary>(20)
val favourites = ArrayList<RoomSummary>() val otherRooms = ArrayList<RoomSummary>(rooms.size)
val directChats = ArrayList<RoomSummary>(rooms.size)
val groupRooms = ArrayList<RoomSummary>(rooms.size)
val lowPriorities = ArrayList<RoomSummary>()
val serverNotices = ArrayList<RoomSummary>()
rooms rooms
.filter { roomListDisplayModeFilter.test(it) } .filter { roomListDisplayModeFilter.test(it) }
.forEach { room -> .forEach { room ->
val tags = room.tags.map { it.name } when (room.breadcrumbsIndex) {
when { RoomSummary.NOT_IN_BREADCRUMBS -> otherRooms.add(room)
room.membership == Membership.INVITE -> invites.add(room) else -> recentRooms.add(room)
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room) }
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
room.isDirect -> directChats.add(room)
else -> groupRooms.add(room)
} }
}
return RoomSummaries().apply { return RoomSummaries().apply {
put(RoomCategory.INVITE, invites) put(RoomCategory.RECENT_ROOMS, recentRooms)
put(RoomCategory.FAVOURITE, favourites) put(RoomCategory.OTHER_ROOMS, otherRooms)
put(RoomCategory.DIRECT, directChats) }
put(RoomCategory.GROUP, groupRooms) } else {
put(RoomCategory.LOW_PRIORITY, lowPriorities) // Set up init size on directChats and groupRooms as they are the biggest ones
put(RoomCategory.SERVER_NOTICE, serverNotices) val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>()
val directChats = ArrayList<RoomSummary>(rooms.size)
val groupRooms = ArrayList<RoomSummary>(rooms.size)
val lowPriorities = ArrayList<RoomSummary>()
val serverNotices = ArrayList<RoomSummary>()
rooms
.filter { roomListDisplayModeFilter.test(it) }
.forEach { room ->
val tags = room.tags.map { it.name }
when {
room.membership == Membership.INVITE -> invites.add(room)
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
room.isDirect -> directChats.add(room)
else -> groupRooms.add(room)
}
}
return RoomSummaries().apply {
put(RoomCategory.INVITE, invites)
put(RoomCategory.FAVOURITE, favourites)
put(RoomCategory.DIRECT, directChats)
put(RoomCategory.GROUP, groupRooms)
put(RoomCategory.LOW_PRIORITY, lowPriorities)
put(RoomCategory.SERVER_NOTICE, serverNotices)
}
} }
} }
} }

View File

@ -43,7 +43,10 @@ data class RoomListViewState(
val isDirectRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true,
val isGroupRoomsExpanded: Boolean = true, val isGroupRoomsExpanded: Boolean = true,
val isLowPriorityRoomsExpanded: Boolean = true, val isLowPriorityRoomsExpanded: Boolean = true,
val isServerNoticeRoomsExpanded: Boolean = true val isServerNoticeRoomsExpanded: Boolean = true,
// For sharing
val isRecentExpanded: Boolean = true,
val isOtherExpanded: Boolean = true
) : MvRxState { ) : MvRxState {
constructor(args: RoomListParams) : this(displayMode = args.displayMode) constructor(args: RoomListParams) : this(displayMode = args.displayMode)
@ -56,6 +59,8 @@ data class RoomListViewState(
RoomCategory.GROUP -> isGroupRoomsExpanded RoomCategory.GROUP -> isGroupRoomsExpanded
RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded
RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded
RoomCategory.RECENT_ROOMS -> isRecentExpanded
RoomCategory.OTHER_ROOMS -> isOtherExpanded
} }
} }
@ -67,6 +72,8 @@ data class RoomListViewState(
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded) RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded) RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded)
RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded) RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded)
RoomCategory.RECENT_ROOMS -> copy(isRecentExpanded = !isRecentExpanded)
RoomCategory.OTHER_ROOMS -> copy(isOtherExpanded = !isOtherExpanded)
} }
} }
@ -86,7 +93,11 @@ enum class RoomCategory(@StringRes val titleRes: Int) {
DIRECT(R.string.bottom_action_people_x), DIRECT(R.string.bottom_action_people_x),
GROUP(R.string.bottom_action_rooms), GROUP(R.string.bottom_action_rooms),
LOW_PRIORITY(R.string.low_priority_header), LOW_PRIORITY(R.string.low_priority_header),
SERVER_NOTICE(R.string.system_alerts_header) SERVER_NOTICE(R.string.system_alerts_header),
// For Sharing
RECENT_ROOMS(R.string.room_list_sharing_header_recent_rooms),
OTHER_ROOMS(R.string.room_list_sharing_header_other_rooms)
} }
fun RoomSummaries?.isNullOrEmpty(): Boolean { fun RoomSummaries?.isNullOrEmpty(): Boolean {

View File

@ -59,39 +59,9 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
override fun buildModels() { override fun buildModels() {
val nonNullViewState = viewState ?: return val nonNullViewState = viewState ?: return
when (nonNullViewState.displayMode) { when (nonNullViewState.displayMode) {
RoomListDisplayMode.FILTERED, RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState)
RoomListDisplayMode.SHARE -> { RoomListDisplayMode.SHARE -> buildShareRooms(nonNullViewState)
buildFilteredRooms(nonNullViewState) else -> buildRooms(nonNullViewState)
}
else -> {
var showHelp = false
val roomSummaries = nonNullViewState.asyncFilteredRooms()
roomSummaries?.forEach { (category, summaries) ->
if (summaries.isEmpty()) {
return@forEach
} else {
val isExpanded = nonNullViewState.isCategoryExpanded(category)
buildRoomCategory(nonNullViewState, summaries, category.titleRes, nonNullViewState.isCategoryExpanded(category)) {
listener?.onToggleRoomCategory(category)
}
if (isExpanded) {
buildRoomModels(summaries,
nonNullViewState.joiningRoomsIds,
nonNullViewState.joiningErrorRoomsIds,
nonNullViewState.rejectingRoomsIds,
nonNullViewState.rejectingErrorRoomsIds)
// Never set showHelp to true for invitation
if (category != RoomCategory.INVITE) {
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
}
}
}
}
if (showHelp) {
buildLongClickHelp()
}
}
} }
} }
@ -109,9 +79,69 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
viewState.rejectingRoomsIds, viewState.rejectingRoomsIds,
viewState.rejectingErrorRoomsIds) viewState.rejectingErrorRoomsIds)
when { addFilterFooter(viewState)
viewState.displayMode == RoomListDisplayMode.FILTERED -> addFilterFooter(viewState) }
filteredSummaries.isEmpty() -> addEmptyFooter()
private fun buildShareRooms(viewState: RoomListViewState) {
var hasResult = false
val roomSummaries = viewState.asyncFilteredRooms()
roomListNameFilter.filter = viewState.roomFilter
roomSummaries?.forEach { (category, summaries) ->
val filteredSummaries = summaries
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
if (filteredSummaries.isEmpty()) {
return@forEach
} else {
hasResult = true
val isExpanded = viewState.isCategoryExpanded(category)
buildRoomCategory(viewState, emptyList(), category.titleRes, viewState.isCategoryExpanded(category)) {
listener?.onToggleRoomCategory(category)
}
if (isExpanded) {
buildRoomModels(filteredSummaries,
emptySet(),
emptySet(),
emptySet(),
emptySet()
)
}
}
}
if (!hasResult) {
addNoResultItem()
}
}
private fun buildRooms(viewState: RoomListViewState) {
var showHelp = false
val roomSummaries = viewState.asyncFilteredRooms()
roomSummaries?.forEach { (category, summaries) ->
if (summaries.isEmpty()) {
return@forEach
} else {
val isExpanded = viewState.isCategoryExpanded(category)
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
listener?.onToggleRoomCategory(category)
}
if (isExpanded) {
buildRoomModels(summaries,
viewState.joiningRoomsIds,
viewState.joiningErrorRoomsIds,
viewState.rejectingRoomsIds,
viewState.rejectingErrorRoomsIds)
// Never set showHelp to true for invitation
if (category != RoomCategory.INVITE) {
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
}
}
}
}
if (showHelp) {
buildLongClickHelp()
} }
} }
@ -130,7 +160,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
} }
} }
private fun addEmptyFooter() { private fun addNoResultItem() {
noResultItem { noResultItem {
id("no_result") id("no_result")
text(stringProvider.getString(R.string.no_result_placeholder)) text(stringProvider.getString(R.string.no_result_placeholder))
@ -142,9 +172,6 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
@StringRes titleRes: Int, @StringRes titleRes: Int,
isExpanded: Boolean, isExpanded: Boolean,
mutateExpandedState: () -> Unit) { mutateExpandedState: () -> Unit) {
if (summaries.isEmpty()) {
return
}
// TODO should add some business logic later // TODO should add some business logic later
val unreadCount = if (summaries.isEmpty()) { val unreadCount = if (summaries.isEmpty()) {
0 0

View File

@ -24,11 +24,12 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState, class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState,
session: Session session: Session
) : VectorViewModel<RoomListQuickActionsState, EmptyAction>(initialState) { ) : VectorViewModel<RoomListQuickActionsState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -27,9 +27,9 @@ import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import io.reactivex.android.schedulers.AndroidSchedulers
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
/** /**
@ -59,25 +59,21 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java) loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java)
loginViewModel.viewEvents loginViewModel.observeViewEvents {
.observe() handleLoginViewEvents(it)
.observeOn(AndroidSchedulers.mainThread()) }
.subscribe {
handleLoginViewEvents(it)
}
.disposeOnDestroyView()
} }
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
when (loginViewEvents) { when (loginViewEvents) {
is LoginViewEvents.Error -> showError(loginViewEvents.throwable) is LoginViewEvents.Failure -> showFailure(loginViewEvents.throwable)
else -> else ->
// This is handled by the Activity // This is handled by the Activity
Unit Unit
} }.exhaustive
} }
private fun showError(throwable: Throwable) { override fun showFailure(throwable: Throwable) {
when (throwable) { when (throwable) {
is Failure.ServerError -> { is Failure.ServerError -> {
if (throwable.error.code == MatrixError.M_FORBIDDEN if (throwable.error.code == MatrixError.M_FORBIDDEN
@ -96,11 +92,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
} }
open fun onError(throwable: Throwable) { open fun onError(throwable: Throwable) {
AlertDialog.Builder(requireActivity()) super.showFailure(throwable)
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
.setPositiveButton(R.string.ok, null)
.show()
} }
override fun onBackPressed(toolbarButton: Boolean): Boolean { override fun onBackPressed(toolbarButton: Boolean): Boolean {

View File

@ -209,7 +209,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
.setMessage(R.string.login_error_outdated_homeserver_content) .setMessage(R.string.login_error_outdated_homeserver_content)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
is LoginViewEvents.Error -> is LoginViewEvents.Failure ->
// This is handled by the Fragments // This is handled by the Fragments
Unit Unit
} }

View File

@ -18,12 +18,15 @@
package im.vector.riotx.features.login package im.vector.riotx.features.login
import im.vector.matrix.android.api.auth.registration.FlowResult import im.vector.matrix.android.api.auth.registration.FlowResult
import im.vector.riotx.core.platform.VectorViewEvents
/** /**
* Transient events for Login * Transient events for Login
*/ */
sealed class LoginViewEvents { sealed class LoginViewEvents: VectorViewEvents {
data class Loading(val message: CharSequence? = null) : LoginViewEvents()
data class Failure(val throwable: Throwable) : LoginViewEvents()
data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents() data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents()
data class Error(val throwable: Throwable) : LoginViewEvents()
object OutdatedHomeserver : LoginViewEvents() object OutdatedHomeserver : LoginViewEvents()
} }

View File

@ -41,8 +41,6 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.session.SessionListener
import im.vector.riotx.features.signout.soft.SoftLogoutActivity import im.vector.riotx.features.signout.soft.SoftLogoutActivity
@ -59,7 +57,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
private val pushRuleTriggerListener: PushRuleTriggerListener, private val pushRuleTriggerListener: PushRuleTriggerListener,
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
private val sessionListener: SessionListener) private val sessionListener: SessionListener)
: VectorViewModel<LoginViewState, LoginAction>(initialState) { : VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -95,9 +93,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
private var currentTask: Cancelable? = null private var currentTask: Cancelable? = null
private val _viewEvents = PublishDataSource<LoginViewEvents>()
val viewEvents: DataSource<LoginViewEvents> = _viewEvents
override fun handle(action: LoginAction) { override fun handle(action: LoginAction) {
when (action) { when (action) {
is LoginAction.UpdateServerType -> handleUpdateServerType(action) is LoginAction.UpdateServerType -> handleUpdateServerType(action)
@ -179,7 +174,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
if (failure !is CancellationException) { if (failure !is CancellationException) {
_viewEvents.post(LoginViewEvents.Error(failure)) _viewEvents.post(LoginViewEvents.Failure(failure))
} }
setState { setState {
copy( copy(
@ -201,7 +196,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.Error(failure)) _viewEvents.post(LoginViewEvents.Failure(failure))
setState { setState {
copy( copy(
asyncRegistration = Uninitialized asyncRegistration = Uninitialized
@ -223,7 +218,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.Error(failure)) _viewEvents.post(LoginViewEvents.Failure(failure))
setState { setState {
copy( copy(
asyncRegistration = Uninitialized asyncRegistration = Uninitialized
@ -526,7 +521,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
if (homeServerConnectionConfig == null) { if (homeServerConnectionConfig == null) {
// This is invalid // This is invalid
_viewEvents.post(LoginViewEvents.Error(Throwable("Unable to create a HomeServerConnectionConfig"))) _viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
} else { } else {
currentTask?.cancel() currentTask?.cancel()
currentTask = null currentTask = null
@ -540,7 +535,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> { currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
_viewEvents.post(LoginViewEvents.Error(failure)) _viewEvents.post(LoginViewEvents.Failure(failure))
setState { setState {
copy( copy(
asyncHomeServerLoginFlowRequest = Uninitialized asyncHomeServerLoginFlowRequest = Uninitialized

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.reactions.data.EmojiDataSource import im.vector.riotx.features.reactions.data.EmojiDataSource
import im.vector.riotx.features.reactions.data.EmojiItem import im.vector.riotx.features.reactions.data.EmojiItem
@ -33,7 +34,7 @@ data class EmojiSearchResultViewState(
class EmojiSearchResultViewModel @AssistedInject constructor( class EmojiSearchResultViewModel @AssistedInject constructor(
@Assisted initialState: EmojiSearchResultViewState, @Assisted initialState: EmojiSearchResultViewState,
private val dataSource: EmojiDataSource) private val dataSource: EmojiDataSource)
: VectorViewModel<EmojiSearchResultViewState, EmojiSearchAction>(initialState) { : VectorViewModel<EmojiSearchResultViewState, EmojiSearchAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -75,6 +75,7 @@ class PublicRoomsFragment @Inject constructor(
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom) sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)
} }
// TODO remove this, replace by ViewEvents
viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable -> viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable ->
Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT) Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
.show() .show()

View File

@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
import timber.log.Timber import timber.log.Timber
@ -41,7 +42,7 @@ private const val PUBLIC_ROOMS_LIMIT = 20
class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: PublicRoomsViewState, class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: PublicRoomsViewState,
private val session: Session) private val session: Session)
: VectorViewModel<PublicRoomsViewState, RoomDirectoryAction>(initialState) { : VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -17,7 +17,12 @@
package im.vector.riotx.features.roomdirectory.createroom package im.vector.riotx.features.roomdirectory.createroom
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.airbnb.mvrx.* import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
@ -25,12 +30,13 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState, class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState,
private val session: Session private val session: Session
) : VectorViewModel<CreateRoomViewState, CreateRoomAction>(initialState) { ) : VectorViewModel<CreateRoomViewState, CreateRoomAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -16,17 +16,22 @@
package im.vector.riotx.features.roomdirectory.picker package im.vector.riotx.features.roomdirectory.picker
import com.airbnb.mvrx.* import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState, class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState,
private val session: Session) private val session: Session)
: VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction>(initialState) { : VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -26,13 +26,14 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.roomdirectory.JoinState import im.vector.riotx.features.roomdirectory.JoinState
import timber.log.Timber import timber.log.Timber
class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: RoomPreviewViewState, class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: RoomPreviewViewState,
private val session: Session) private val session: Session)
: VectorViewModel<RoomPreviewViewState, RoomPreviewAction>(initialState) { : VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -32,6 +32,7 @@ import im.vector.riotx.core.animations.AppBarStateChangeListener
import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.StateView
@ -81,17 +82,13 @@ class RoomMemberProfileFragment @Inject constructor(
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView)) matrixProfileToolbarTitleView))
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
viewModel.viewEvents viewModel.observeViewEvents {
.observe() when (it) {
.subscribe { is RoomMemberProfileViewEvents.Loading -> showLoading(it.message)
dismissLoadingDialog() is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable)
when (it) { is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit
is RoomMemberProfileViewEvents.Loading -> showLoadingDialog(it.message) }.exhaustive
is RoomMemberProfileViewEvents.Failure -> showErrorInSnackbar(it.throwable) }
}
}
.disposeOnDestroyView()
viewModel.actionResultLiveData.observeEvent(this) { async -> viewModel.actionResultLiveData.observeEvent(this) { async ->
when (async) { when (async) {
is Success -> { is Success -> {

View File

@ -16,11 +16,14 @@
package im.vector.riotx.features.roommemberprofile package im.vector.riotx.features.roommemberprofile
import im.vector.riotx.core.platform.VectorViewEvents
/** /**
* Transient events for RoomMemberProfile * Transient events for RoomMemberProfile
*/ */
sealed class RoomMemberProfileViewEvents { sealed class RoomMemberProfileViewEvents : VectorViewEvents {
data class Loading(val message: CharSequence) : RoomMemberProfileViewEvents() data class Loading(val message: CharSequence? = null) : RoomMemberProfileViewEvents()
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents() data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents()
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
} }

View File

@ -60,7 +60,7 @@ import kotlinx.coroutines.withContext
class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val session: Session) private val session: Session)
: VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) { : VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction, RoomMemberProfileViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -76,9 +76,6 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
} }
} }
private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>()
val viewEvents: DataSource<RoomMemberProfileViewEvents> = _viewEvents
private val _actionResultLiveData = MutableLiveData<LiveEvent<Async<RoomMemberProfileAction>>>() private val _actionResultLiveData = MutableLiveData<LiveEvent<Async<RoomMemberProfileAction>>>()
val actionResultLiveData: LiveData<LiveEvent<Async<RoomMemberProfileAction>>> val actionResultLiveData: LiveData<LiveEvent<Async<RoomMemberProfileAction>>>
get() = _actionResultLiveData get() = _actionResultLiveData
@ -219,7 +216,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
private fun handleIgnoreAction() = withState { state -> private fun handleIgnoreAction() = withState { state ->
val isIgnored = state.isIgnored() ?: return@withState val isIgnored = state.isIgnored() ?: return@withState
_viewEvents.post(RoomMemberProfileViewEvents.Loading(stringProvider.getString(R.string.please_wait))) _viewEvents.post(RoomMemberProfileViewEvents.Loading())
val ignoreActionCallback = object : MatrixCallback<Unit> { val ignoreActionCallback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
_viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess) _viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess)

View File

@ -26,6 +26,7 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack
import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
@ -69,7 +70,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
} }
private fun openRoomSettings() { private fun openRoomSettings() {
notImplemented("Open room settings") addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
} }
private fun openRoomMembers() { private fun openRoomMembers() {

View File

@ -31,6 +31,7 @@ import im.vector.riotx.core.animations.AppBarStateChangeListener
import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
@ -77,17 +78,13 @@ class RoomProfileFragment @Inject constructor(
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
matrixProfileToolbarTitleView)) matrixProfileToolbarTitleView))
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
roomProfileViewModel.viewEvents roomProfileViewModel.observeViewEvents {
.observe() when (it) {
.subscribe { is RoomProfileViewEvents.Loading -> showLoading(it.message)
dismissLoadingDialog() is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
when (it) { is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
is RoomProfileViewEvents.Loading -> showLoadingDialog(it.message) }.exhaustive
RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() }
is RoomProfileViewEvents.Failure -> showError(it.throwable)
}
}
.disposeOnDestroyView()
roomListQuickActionsSharedActionViewModel roomListQuickActionsSharedActionViewModel
.observe() .observe()
.subscribe { handleQuickActions(it) } .subscribe { handleQuickActions(it) }

View File

@ -15,11 +15,15 @@
*/ */
package im.vector.riotx.features.roomprofile package im.vector.riotx.features.roomprofile
import im.vector.riotx.core.platform.VectorViewEvents
/** /**
* Transient events for RoomProfile * Transient events for RoomProfile
*/ */
sealed class RoomProfileViewEvents { sealed class RoomProfileViewEvents : VectorViewEvents {
data class Loading(val message: CharSequence): RoomProfileViewEvents() data class Loading(val message: CharSequence? = null) : RoomProfileViewEvents()
object OnLeaveRoomSuccess: RoomProfileViewEvents()
data class Failure(val throwable: Throwable) : RoomProfileViewEvents() data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
object OnLeaveRoomSuccess : RoomProfileViewEvents()
} }

View File

@ -29,13 +29,11 @@ import im.vector.matrix.rx.unwrap
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState, class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val session: Session) private val session: Session)
: VectorViewModel<RoomProfileViewState, RoomProfileAction>(initialState) { : VectorViewModel<RoomProfileViewState, RoomProfileAction, RoomProfileViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -51,9 +49,6 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R
} }
} }
private val _viewEvents = PublishDataSource<RoomProfileViewEvents>()
val viewEvents: DataSource<RoomProfileViewEvents> = _viewEvents
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
init { init {

View File

@ -29,7 +29,7 @@ import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.roomprofile.RoomProfileArgs import im.vector.riotx.features.roomprofile.RoomProfileArgs
import kotlinx.android.synthetic.main.fragment_room_member_list.* import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import javax.inject.Inject import javax.inject.Inject
class RoomMemberListFragment @Inject constructor( class RoomMemberListFragment @Inject constructor(
@ -41,12 +41,12 @@ class RoomMemberListFragment @Inject constructor(
private val viewModel: RoomMemberListViewModel by fragmentViewModel() private val viewModel: RoomMemberListViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args() private val roomProfileArgs: RoomProfileArgs by args()
override fun getLayoutResId() = R.layout.fragment_room_member_list override fun getLayoutResId() = R.layout.fragment_room_setting_generic
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
roomMemberListController.callback = this roomMemberListController.callback = this
setupToolbar(roomMemberListToolbar) setupToolbar(roomSettingsToolbar)
recyclerView.configureWith(roomMemberListController, hasFixedSize = true) recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
} }
@ -66,8 +66,8 @@ class RoomMemberListFragment @Inject constructor(
private fun renderRoomSummary(state: RoomMemberListViewState) { private fun renderRoomSummary(state: RoomMemberListViewState) {
state.roomSummary()?.let { state.roomSummary()?.let {
roomMemberListToolbarTitleView.text = it.displayName roomSettingsToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomMemberListToolbarAvatarImageView) avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
} }
} }
} }

View File

@ -34,13 +34,14 @@ import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
import im.vector.matrix.rx.mapOptional import im.vector.matrix.rx.mapOptional
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
private val session: Session) private val session: Session)
: VectorViewModel<RoomMemberListViewState, RoomMemberListAction>(initialState) { : VectorViewModel<RoomMemberListViewState, RoomMemberListAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -0,0 +1,26 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.settings
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomSettingsAction : VectorViewModelAction {
data class SetRoomName(val newName: String) : RoomSettingsAction()
data class SetRoomTopic(val newTopic: String) : RoomSettingsAction()
data class SetRoomAvatar(val newAvatarUrl: String) : RoomSettingsAction()
object EnableEncryption : RoomSettingsAction()
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.settings
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.profiles.buildProfileAction
import im.vector.riotx.core.epoxy.profiles.buildProfileSection
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import javax.inject.Inject
// TODO Add other feature here (waiting for design)
class RoomSettingsController @Inject constructor(
private val stringProvider: StringProvider,
colorProvider: ColorProvider
) : TypedEpoxyController<RoomSettingsViewState>() {
interface Callback {
fun onEnableEncryptionClicked()
}
private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color)
var callback: Callback? = null
init {
setData(null)
}
override fun buildModels(data: RoomSettingsViewState?) {
val roomSummary = data?.roomSummary?.invoke() ?: return
buildProfileSection(
stringProvider.getString(R.string.settings)
)
if (roomSummary.isEncrypted) {
buildProfileAction(
id = "encryption",
title = stringProvider.getString(R.string.room_settings_addresses_e2e_enabled),
dividerColor = dividerColor,
divider = false,
editable = false
)
} else {
buildProfileAction(
id = "encryption",
title = stringProvider.getString(R.string.room_settings_enable_encryption),
subtitle = stringProvider.getString(R.string.room_settings_enable_encryption_warning),
dividerColor = dividerColor,
divider = false,
editable = true,
action = { callback?.onEnableEncryptionClicked() }
)
}
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.settings
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.roomprofile.RoomProfileArgs
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import javax.inject.Inject
class RoomSettingsFragment @Inject constructor(
val viewModelFactory: RoomSettingsViewModel.Factory,
private val controller: RoomSettingsController,
private val avatarRenderer: AvatarRenderer
) : VectorBaseFragment(), RoomSettingsController.Callback {
private val viewModel: RoomSettingsViewModel by fragmentViewModel()
private val roomProfileArgs: RoomProfileArgs by args()
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
controller.callback = this
setupToolbar(roomSettingsToolbar)
recyclerView.configureWith(controller, hasFixedSize = true)
waiting_view_status_text.setText(R.string.please_wait)
waiting_view_status_text.isVisible = true
viewModel.observeViewEvents {
when (it) {
is RoomSettingsViewEvents.Failure -> showFailure(it.throwable)
}.exhaustive
}
}
override fun onDestroyView() {
recyclerView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { viewState ->
controller.setData(viewState)
renderRoomSummary(viewState)
}
override fun onEnableEncryptionClicked() {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
.setMessage(R.string.room_settings_enable_encryption_dialog_content)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ ->
viewModel.handle(RoomSettingsAction.EnableEncryption)
}
.show()
}
private fun renderRoomSummary(state: RoomSettingsViewState) {
waiting_view.isVisible = state.isLoading
state.roomSummary()?.let {
roomSettingsToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.roomprofile.settings
import im.vector.riotx.core.platform.VectorViewEvents
/**
* Transient events for room settings screen
*/
sealed class RoomSettingsViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomSettingsViewEvents()
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.settings
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.VectorViewModel
class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState,
private val session: Session)
: VectorViewModel<RoomSettingsViewState, RoomSettingsAction, RoomSettingsViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomSettingsViewState): RoomSettingsViewModel
}
companion object : MvRxViewModelFactory<RoomSettingsViewModel, RoomSettingsViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomSettingsViewState): RoomSettingsViewModel? {
val fragment: RoomSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.viewModelFactory.create(state)
}
}
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomSummary()
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
.execute { async ->
copy(roomSummary = async)
}
}
override fun handle(action: RoomSettingsAction) {
when (action) {
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
}
}
private fun handleEnableEncryption() {
setState {
copy(isLoading = true)
}
room.enableEncryption(MXCRYPTO_ALGORITHM_MEGOLM, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
setState {
copy(isLoading = false)
}
_viewEvents.post(RoomSettingsViewEvents.Failure(failure))
}
override fun onSuccess(data: Unit) {
setState {
copy(isLoading = false)
}
}
})
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.roomprofile.settings
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotx.features.roomprofile.RoomProfileArgs
data class RoomSettingsViewState(
val roomId: String,
val roomSummary: Async<RoomSummary> = Uninitialized,
val isLoading: Boolean = false
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.settings.devices
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class DevicesAction : VectorViewModelAction {
object Retry : DevicesAction()
data class Delete(val deviceInfo: DeviceInfo) : DevicesAction()
data class Password(val password: String) : DevicesAction()
data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction()
data class PromptRename(val deviceId: String, val deviceInfo: DeviceInfo? = null) : DevicesAction()
data class VerifyMyDevice(val deviceId: String, val userId: String? = null, val transactionId: String? = null) : DevicesAction()
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.riotx.features.settings.devices
import im.vector.riotx.core.platform.VectorViewEvents
/**
* Transient events for Ignored users screen
*/
sealed class DevicesViewEvents : VectorViewEvents {
data class Loading(val message: CharSequence? = null) : DevicesViewEvents()
data class Failure(val throwable: Throwable) : DevicesViewEvents()
}

View File

@ -43,28 +43,19 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction
import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.LiveEvent
data class DevicesViewState( data class DevicesViewState(
val myDeviceId: String = "", val myDeviceId: String = "",
val devices: Async<List<DeviceInfo>> = Uninitialized, val devices: Async<List<DeviceInfo>> = Uninitialized,
val cryptoDevices: Async<List<CryptoDeviceInfo>> = Uninitialized, val cryptoDevices: Async<List<CryptoDeviceInfo>> = Uninitialized,
// TODO Replace by isLoading boolean
val request: Async<Unit> = Uninitialized val request: Async<Unit> = Uninitialized
) : MvRxState ) : MvRxState
sealed class DevicesAction : VectorViewModelAction {
object Retry : DevicesAction()
data class Delete(val deviceId: String) : DevicesAction()
data class Password(val password: String) : DevicesAction()
data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction()
data class PromptRename(val deviceId: String, val deviceInfo: DeviceInfo? = null) : DevicesAction()
data class VerifyMyDevice(val deviceId: String, val userId: String? = null, val transactionId: String? = null) : DevicesAction()
}
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState, class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
private val session: Session) private val session: Session)
: VectorViewModel<DevicesViewState, DevicesAction>(initialState), VerificationService.VerificationListener { : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -217,7 +208,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
) )
} }
_requestErrorLiveData.postLiveEvent(failure) _viewEvents.post(DevicesViewEvents.Failure(failure))
} }
}) })
} }
@ -267,7 +258,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
) )
} }
_requestErrorLiveData.postLiveEvent(failure) _viewEvents.post(DevicesViewEvents.Failure(failure))
} }
} }
@ -321,7 +312,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
) )
} }
_requestErrorLiveData.postLiveEvent(failure) _viewEvents.post(DevicesViewEvents.Failure(failure))
} }
}) })
} }

View File

@ -32,6 +32,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
@ -54,7 +55,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
override fun getLayoutResId() = R.layout.fragment_generic_recycler override fun getLayoutResId() = R.layout.fragment_generic_recycler
private val devicesViewModel: DevicesViewModel by fragmentViewModel() private val viewModel: DevicesViewModel by fragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -63,16 +64,17 @@ class VectorSettingsDevicesFragment @Inject constructor(
waiting_view_status_text.isVisible = true waiting_view_status_text.isVisible = true
devicesController.callback = this devicesController.callback = this
recyclerView.configureWith(devicesController, showDivider = true) recyclerView.configureWith(devicesController, showDivider = true)
devicesViewModel.requestErrorLiveData.observeEvent(this) { viewModel.observeViewEvents {
displayErrorDialog(it) when (it) {
// Password is maybe not good, for safety measure, reset it here is DevicesViewEvents.Loading -> showLoading(it.message)
mAccountPassword = "" is DevicesViewEvents.Failure -> showFailure(it.throwable)
}.exhaustive
} }
devicesViewModel.requestPasswordLiveData.observeEvent(this) { viewModel.requestPasswordLiveData.observeEvent(this) {
maybeShowDeleteDeviceWithPasswordDialog() maybeShowDeleteDeviceWithPasswordDialog()
} }
devicesViewModel.fragmentActionLiveData.observeEvent(this) { async -> viewModel.fragmentActionLiveData.observeEvent(this) { async ->
when (async) { when (async) {
is Success -> { is Success -> {
when (val action = async.invoke()) { when (val action = async.invoke()) {
@ -96,6 +98,13 @@ class VectorSettingsDevicesFragment @Inject constructor(
} }
} }
override fun showFailure(throwable: Throwable) {
super.showFailure(throwable)
// Password is maybe not good, for safety measure, reset it here
mAccountPassword = ""
}
override fun onDestroyView() { override fun onDestroyView() {
devicesController.callback = null devicesController.callback = null
recyclerView.cleanup() recyclerView.cleanup()
@ -108,14 +117,6 @@ class VectorSettingsDevicesFragment @Inject constructor(
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_active_sessions_manage) (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_active_sessions_manage)
} }
private fun displayErrorDialog(throwable: Throwable) {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
.setPositiveButton(R.string.ok, null)
.show()
}
override fun onDeviceClicked(deviceInfo: DeviceInfo) { override fun onDeviceClicked(deviceInfo: DeviceInfo) {
DeviceVerificationInfoBottomSheet.newInstance(deviceInfo.user_id ?: "", deviceInfo.deviceId ?: "").show( DeviceVerificationInfoBottomSheet.newInstance(deviceInfo.user_id ?: "", deviceInfo.deviceId ?: "").show(
childFragmentManager, childFragmentManager,
@ -132,7 +133,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
// } // }
override fun retry() { override fun retry() {
devicesViewModel.handle(DevicesAction.Retry) viewModel.handle(DevicesAction.Retry)
} }
/** /**
@ -153,7 +154,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
val newName = input.text.toString() val newName = input.text.toString()
devicesViewModel.handle(DevicesAction.Rename(deviceInfo, newName)) viewModel.handle(DevicesAction.Rename(deviceInfo, newName))
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
@ -164,7 +165,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
*/ */
private fun maybeShowDeleteDeviceWithPasswordDialog() { private fun maybeShowDeleteDeviceWithPasswordDialog() {
if (mAccountPassword.isNotEmpty()) { if (mAccountPassword.isNotEmpty()) {
devicesViewModel.handle(DevicesAction.Password(mAccountPassword)) viewModel.handle(DevicesAction.Password(mAccountPassword))
} else { } else {
val inflater = requireActivity().layoutInflater val inflater = requireActivity().layoutInflater
val layout = inflater.inflate(R.layout.dialog_device_delete, null) val layout = inflater.inflate(R.layout.dialog_device_delete, null)
@ -180,7 +181,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
return@OnClickListener return@OnClickListener
} }
mAccountPassword = passwordEditText.text.toString() mAccountPassword = passwordEditText.text.toString()
devicesViewModel.handle(DevicesAction.Password(mAccountPassword)) viewModel.handle(DevicesAction.Password(mAccountPassword))
}) })
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
@ -194,7 +195,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
} }
} }
override fun invalidate() = withState(devicesViewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
devicesController.update(state) devicesController.update(state)
handleRequestStatus(state.request) handleRequestStatus(state.request)

View File

@ -0,0 +1,28 @@
/*
* 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.riotx.features.settings.ignored
import im.vector.riotx.core.platform.VectorViewEvents
/**
* Transient events for Ignored users screen
*/
sealed class IgnoredUsersViewEvents : VectorViewEvents {
data class Loading(val message: CharSequence? = null) : IgnoredUsersViewEvents()
data class Failure(val throwable: Throwable) : IgnoredUsersViewEvents()
}

View File

@ -16,14 +16,21 @@
package im.vector.riotx.features.settings.ignored package im.vector.riotx.features.settings.ignored
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.postLiveEvent
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
@ -38,7 +45,7 @@ sealed class IgnoredUsersAction : VectorViewModelAction {
class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState, class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState,
private val session: Session) private val session: Session)
: VectorViewModel<IgnoredUsersViewState, IgnoredUsersAction>(initialState) { : VectorViewModel<IgnoredUsersViewState, IgnoredUsersAction, IgnoredUsersViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -89,7 +96,7 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState:
) )
} }
_requestErrorLiveData.postLiveEvent(failure) _viewEvents.post(IgnoredUsersViewEvents.Failure(failure))
} }
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {

View File

@ -27,7 +27,7 @@ import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.configureWith
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.fragment_generic_recycler.*
@ -41,7 +41,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
override fun getLayoutResId() = R.layout.fragment_generic_recycler override fun getLayoutResId() = R.layout.fragment_generic_recycler
private val ignoredUsersViewModel: IgnoredUsersViewModel by fragmentViewModel() private val viewModel: IgnoredUsersViewModel by fragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -50,8 +50,11 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
waiting_view_status_text.isVisible = true waiting_view_status_text.isVisible = true
ignoredUsersController.callback = this ignoredUsersController.callback = this
recyclerView.configureWith(ignoredUsersController) recyclerView.configureWith(ignoredUsersController)
ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) { viewModel.observeViewEvents {
displayErrorDialog(it) when (it) {
is IgnoredUsersViewEvents.Loading -> showLoading(it.message)
is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable)
}.exhaustive
} }
} }
@ -71,25 +74,17 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
AlertDialog.Builder(requireActivity()) AlertDialog.Builder(requireActivity())
.setMessage(getString(R.string.settings_unignore_user, userId)) .setMessage(getString(R.string.settings_unignore_user, userId))
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
ignoredUsersViewModel.handle(IgnoredUsersAction.UnIgnore(userId)) viewModel.handle(IgnoredUsersAction.UnIgnore(userId))
} }
.setNegativeButton(R.string.no, null) .setNegativeButton(R.string.no, null)
.show() .show()
} }
private fun displayErrorDialog(throwable: Throwable) {
AlertDialog.Builder(requireActivity())
.setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(throwable))
.setPositiveButton(R.string.ok, null)
.show()
}
// ============================================================================================================== // ==============================================================================================================
// ignored users list management // ignored users list management
// ============================================================================================================== // ==============================================================================================================
override fun invalidate() = withState(ignoredUsersViewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
ignoredUsersController.update(state) ignoredUsersController.update(state)
handleUnIgnoreRequestStatus(state.unIgnoreRequest) handleUnIgnoreRequestStatus(state.unIgnoreRequest)

View File

@ -16,13 +16,19 @@
package im.vector.riotx.features.settings.push package im.vector.riotx.features.settings.push
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.rx.RxSession import im.vector.matrix.rx.RxSession
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
data class PushGatewayViewState( data class PushGatewayViewState(
@ -31,7 +37,7 @@ data class PushGatewayViewState(
class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: PushGatewayViewState, class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: PushGatewayViewState,
private val session: Session) private val session: Session)
: VectorViewModel<PushGatewayViewState, EmptyAction>(initialState) { : VectorViewModel<PushGatewayViewState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.HasScreenInjector
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
data class PushRulesViewState( data class PushRulesViewState(
@ -28,7 +29,7 @@ data class PushRulesViewState(
) : MvRxState ) : MvRxState
class PushRulesViewModel(initialState: PushRulesViewState) class PushRulesViewModel(initialState: PushRulesViewState)
: VectorViewModel<PushRulesViewState, EmptyAction>(initialState) { : VectorViewModel<PushRulesViewState, EmptyAction, EmptyViewEvents>(initialState) {
companion object : MvRxViewModelFactory<PushRulesViewModel, PushRulesViewState> { companion object : MvRxViewModelFactory<PushRulesViewModel, PushRulesViewState> {

View File

@ -44,7 +44,9 @@ class IncomingShareActivity :
@Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var sessionHolder: ActiveSessionHolder
@Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory @Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory
private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var attachmentsHelper: AttachmentsHelper
private val incomingShareViewModel: IncomingShareViewModel by viewModel() // Do not remove, even if not used, it instantiates the view model
@Suppress("unused")
private val viewModel: IncomingShareViewModel by viewModel()
private val roomListFragment: RoomListFragment? private val roomListFragment: RoomListFragment?
get() { get() {
return supportFragmentManager.findFragmentById(R.id.shareRoomListFragmentContainer) as? RoomListFragment return supportFragmentManager.findFragmentById(R.id.shareRoomListFragmentContainer) as? RoomListFragment

View File

@ -26,7 +26,9 @@ import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.ActiveSessionDataSource import im.vector.riotx.ActiveSessionDataSource
import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyAction
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.home.room.list.BreadcrumbsRoomComparator
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -38,8 +40,9 @@ data class IncomingShareState(private val dummy: Boolean = false) : MvRxState
*/ */
class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState, class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState,
private val sessionObservableStore: ActiveSessionDataSource, private val sessionObservableStore: ActiveSessionDataSource,
private val shareRoomListObservableStore: ShareRoomListDataSource) private val shareRoomListObservableStore: ShareRoomListDataSource,
: VectorViewModel<IncomingShareState, EmptyAction>(initialState) { private val breadcrumbsRoomComparator: BreadcrumbsRoomComparator)
: VectorViewModel<IncomingShareState, EmptyAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -68,6 +71,9 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState:
?: Observable.just(emptyList()) ?: Observable.just(emptyList())
} }
.throttleLast(300, TimeUnit.MILLISECONDS) .throttleLast(300, TimeUnit.MILLISECONDS)
.map {
it.sortedWith(breadcrumbsRoomComparator)
}
.subscribe { .subscribe {
shareRoomListObservableStore.post(it) shareRoomListObservableStore.post(it)
} }

View File

@ -73,7 +73,7 @@ class SoftLogoutActivity : LoginActivity() {
private fun handleSoftLogoutViewEvents(softLogoutViewEvents: SoftLogoutViewEvents) { private fun handleSoftLogoutViewEvents(softLogoutViewEvents: SoftLogoutViewEvents) {
when (softLogoutViewEvents) { when (softLogoutViewEvents) {
is SoftLogoutViewEvents.Error -> is SoftLogoutViewEvents.Failure ->
showError(errorFormatter.toHumanReadable(softLogoutViewEvents.throwable)) showError(errorFormatter.toHumanReadable(softLogoutViewEvents.throwable))
is SoftLogoutViewEvents.ErrorNotSameUser -> { is SoftLogoutViewEvents.ErrorNotSameUser -> {
// Pop the backstack // Pop the backstack

View File

@ -17,11 +17,14 @@
package im.vector.riotx.features.signout.soft package im.vector.riotx.features.signout.soft
import im.vector.riotx.core.platform.VectorViewEvents
/** /**
* Transient events for SoftLogout * Transient events for SoftLogout
*/ */
sealed class SoftLogoutViewEvents { sealed class SoftLogoutViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : SoftLogoutViewEvents()
data class ErrorNotSameUser(val currentUserId: String, val newUserId: String) : SoftLogoutViewEvents() data class ErrorNotSameUser(val currentUserId: String, val newUserId: String) : SoftLogoutViewEvents()
data class Error(val throwable: Throwable) : SoftLogoutViewEvents()
object ClearData : SoftLogoutViewEvents() object ClearData : SoftLogoutViewEvents()
} }

View File

@ -16,7 +16,13 @@
package im.vector.riotx.features.signout.soft package im.vector.riotx.features.signout.soft
import com.airbnb.mvrx.* import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
@ -28,8 +34,6 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.extensions.hasUnsavedKeys import im.vector.riotx.core.extensions.hasUnsavedKeys
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.DataSource
import im.vector.riotx.core.utils.PublishDataSource
import im.vector.riotx.features.login.LoginMode import im.vector.riotx.features.login.LoginMode
import timber.log.Timber import timber.log.Timber
@ -41,7 +45,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
private val session: Session, private val session: Session,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val authenticationService: AuthenticationService private val authenticationService: AuthenticationService
) : VectorViewModel<SoftLogoutViewState, SoftLogoutAction>(initialState) { ) : VectorViewModel<SoftLogoutViewState, SoftLogoutAction, SoftLogoutViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -71,9 +75,6 @@ class SoftLogoutViewModel @AssistedInject constructor(
private var currentTask: Cancelable? = null private var currentTask: Cancelable? = null
private val _viewEvents = PublishDataSource<SoftLogoutViewEvents>()
val viewEvents: DataSource<SoftLogoutViewEvents> = _viewEvents
init { init {
// Get the supported login flow // Get the supported login flow
getSupportedLoginFlow() getSupportedLoginFlow()
@ -192,7 +193,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
currentTask = session.updateCredentials(action.credentials, currentTask = session.updateCredentials(action.credentials,
object : MatrixCallback<Unit> { object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
_viewEvents.post(SoftLogoutViewEvents.Error(failure)) _viewEvents.post(SoftLogoutViewEvents.Failure(failure))
setState { setState {
copy( copy(
asyncLoginAction = Uninitialized asyncLoginAction = Uninitialized

View File

@ -4,25 +4,26 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootConstraintLayout" android:id="@+id/rootConstraintLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="?riotx_header_panel_background">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/roomMemberListToolbar" android:id="@+id/roomSettingsToolbar"
style="@style/VectorToolbarStyle" style="@style/VectorToolbarStyle"
android:elevation="4dp"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/roomMemberListToolbarContentView" android:id="@+id/roomSettingsToolbarContentView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView <ImageView
android:id="@+id/roomMemberListToolbarAvatarImageView" android:id="@+id/roomSettingsToolbarAvatarImageView"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@ -33,7 +34,7 @@
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView
android:id="@+id/roomMemberListToolbarTitleView" android:id="@+id/roomSettingsToolbarTitleView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@ -42,9 +43,9 @@
android:maxLines="1" android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color" android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toEndOf="@+id/roomMemberListToolbarAvatarImageView"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/roomSettingsToolbarAvatarImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@sample/matrix.json/data/roomName" /> tools:text="@sample/matrix.json/data/roomName" />
@ -57,10 +58,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:overScrollMode="always" android:overScrollMode="always"
app:layout_constraintTop_toBottomOf="@+id/roomMemberListToolbar" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar"
tools:listitem="@layout/item_autocomplete_matrix_item" /> tools:listitem="@layout/item_profile_action" />
<include layout="@layout/merge_overlay_waiting_view" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,6 +10,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?vctr_waiting_background_color" android:background="?vctr_waiting_background_color"
android:clickable="true"
android:focusable="true"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">

View File

@ -84,12 +84,21 @@
<string name="unignore">Unignore</string> <string name="unignore">Unignore</string>
<string name="room_list_sharing_header_recent_rooms">Recent rooms</string>
<string name="room_list_sharing_header_other_rooms">Other rooms</string>
<!-- Title for category in the settings which affect what is displayed in the timeline (ex: show read receipts, etc.) --> <!-- Title for category in the settings which affect what is displayed in the timeline (ex: show read receipts, etc.) -->
<string name="settings_category_timeline">Timeline</string> <string name="settings_category_timeline">Timeline</string>
<!-- Title for category in the settings which affect the behavior of the message editor (ex: enable Markdown, send typing notification, etc.) --> <!-- Title for category in the settings which affect the behavior of the message editor (ex: enable Markdown, send typing notification, etc.) -->
<string name="settings_category_composer">Message editor</string> <string name="settings_category_composer">Message editor</string>
<string name="room_settings_enable_encryption">Enable end-to-end encryption</string>
<string name="room_settings_enable_encryption_warning">Once enabled, encryption cannot be disabled.</string>
<string name="room_settings_enable_encryption_dialog_title">Enable encryption?</string>
<string name="room_settings_enable_encryption_dialog_content">Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly.</string>
<string name="room_settings_enable_encryption_dialog_submit">Enable encryption</string>
<string name="verification_request_notice">For extra security, verify %s by checking a one-time code.</string> <string name="verification_request_notice">For extra security, verify %s by checking a one-time code.</string>