mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
Merge branch 'develop' into feature/stabilization_2
This commit is contained in:
commit
ec6d78bf96
108 changed files with 1794 additions and 458 deletions
|
@ -9,6 +9,8 @@
|
|||
<w>decryptor</w>
|
||||
<w>emoji</w>
|
||||
<w>emojis</w>
|
||||
<w>fdroid</w>
|
||||
<w>gplay</w>
|
||||
<w>hmac</w>
|
||||
<w>ktlint</w>
|
||||
<w>linkified</w>
|
||||
|
|
35
AUTHORS.md
35
AUTHORS.md
|
@ -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!
|
12
CHANGES.md
12
CHANGES.md
|
@ -2,13 +2,16 @@ Changes in RiotX 0.14.0 (2020-XX-XX)
|
|||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
- Enable encryption in unencrypted rooms, from the room settings (#212)
|
||||
- Enable e2e by default when creating DM, and give the possibility to enable encryption when creating room (#837)
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
- Sharing things to RiotX: sort list by recent room first (#771)
|
||||
- Hide the algorithm when turning on e2e (#897)
|
||||
- Sort room members by display names
|
||||
|
||||
Other changes:
|
||||
-
|
||||
- Add support for /rainbow and /rainbowme commands (#879)
|
||||
|
||||
Bugfix 🐛:
|
||||
-
|
||||
|
@ -17,7 +20,8 @@ Translations 🗣:
|
|||
-
|
||||
|
||||
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)
|
||||
===================================================
|
||||
|
|
|
@ -12,12 +12,13 @@ RiotX is an Android Matrix Client currently in beta but in active development.
|
|||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||
|
||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
||||
|
||||
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
# 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
|
||||
|
|
|
@ -74,7 +74,7 @@ android {
|
|||
}
|
||||
|
||||
static def gitRevision() {
|
||||
def cmd = "git rev-parse --short HEAD"
|
||||
def cmd = "git rev-parse --short=8 HEAD"
|
||||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ data class RoomSummary(
|
|||
val readMarkerId: String? = null,
|
||||
val userDrafts: List<UserDraft> = emptyList(),
|
||||
var isEncrypted: Boolean,
|
||||
val typingRoomMemberIds: List<String> = emptyList()
|
||||
val typingRoomMemberIds: List<String> = emptyList(),
|
||||
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS
|
||||
) {
|
||||
|
||||
val isVersioned: Boolean
|
||||
|
@ -53,4 +54,8 @@ data class RoomSummary(
|
|||
|
||||
val hasNewMessages: Boolean
|
||||
get() = notificationCount != 0
|
||||
|
||||
companion object {
|
||||
const val NOT_IN_BREADCRUMBS = -1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,9 +56,15 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
|||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.database.query.whereType
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
|
@ -72,7 +78,12 @@ import im.vector.matrix.android.internal.task.configureWith
|
|||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.olm.OlmManager
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
@ -476,14 +487,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
|
||||
/**
|
||||
* Tells if a room is encrypted
|
||||
* Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM
|
||||
*
|
||||
* @param roomId the room id
|
||||
* @return true if the room is encrypted
|
||||
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
|
||||
*/
|
||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||
val encryptionEvent = monarchy.fetchCopied {
|
||||
EventEntity.whereType(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst()
|
||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
||||
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||
.findFirst()
|
||||
}
|
||||
return encryptionEvent != null
|
||||
}
|
||||
|
|
|
@ -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.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryMapper @Inject constructor(
|
||||
|
@ -74,7 +74,8 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList()
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
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.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||
internal open class RoomSummaryEntity(
|
||||
@PrimaryKey var roomId: String = "",
|
||||
var displayName: String? = "",
|
||||
var avatarUrl: String? = "",
|
||||
var topic: String? = "",
|
||||
|
@ -39,7 +41,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||
var hasUnreadMessages: Boolean = false,
|
||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
||||
var userDrafts: UserDraftsEntity? = null,
|
||||
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,
|
||||
var canonicalAlias: String? = null,
|
||||
var aliases: RealmList<String> = RealmList(),
|
||||
// this is required for querying
|
||||
|
@ -66,7 +68,5 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||
versioningStateStr = value.name
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NOT_IN_BREADCRUMBS = -1
|
||||
}
|
||||
companion object
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
|||
return RoomSummaryEntity.where(realm)
|
||||
.isNotEmpty(RoomSummaryEntityFields.DISPLAY_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)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,8 +23,12 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
|
@ -32,6 +36,8 @@ import im.vector.matrix.android.internal.database.query.getOrCreate
|
|||
import im.vector.matrix.android.internal.database.query.getOrNull
|
||||
import im.vector.matrix.android.internal.database.query.isEventRead
|
||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.database.query.whereType
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
|
||||
import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper
|
||||
|
@ -94,10 +100,15 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
}
|
||||
|
||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
||||
|
||||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
|
||||
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
||||
|
||||
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
|
||||
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||
.findFirst()
|
||||
|
||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||
// avoid this call if we are sure there are unread events
|
||||
|
|
|
@ -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.session.events.model.toModel
|
||||
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.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.getOrCreate
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
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.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.UpdateUserAccountDataTask
|
||||
import io.realm.Realm
|
||||
|
@ -177,10 +187,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(
|
|||
// Update the room summaries
|
||||
// Reset all the indexes...
|
||||
RoomSummaryEntity.where(realm)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
|
||||
.findAll()
|
||||
.forEach {
|
||||
it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS
|
||||
it.breadcrumbsIndex = RoomSummary.NOT_IN_BREADCRUMBS
|
||||
}
|
||||
|
||||
// ...and apply new indexes
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package im.vector.matrix.android.internal.session.user.accountdata
|
||||
|
||||
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.RoomSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||
|
@ -50,10 +51,10 @@ internal class DefaultSaveBreadcrumbsTask @Inject constructor(
|
|||
// Update the room summaries
|
||||
// Reset all the indexes...
|
||||
RoomSummaryEntity.where(realm)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS)
|
||||
.greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS)
|
||||
.findAll()
|
||||
.forEach {
|
||||
it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS
|
||||
it.breadcrumbsIndex = RoomSummary.NOT_IN_BREADCRUMBS
|
||||
}
|
||||
|
||||
// ...and apply new indexes
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="notice_end_to_end_ok">%1$s turned on end-to-end encryption.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm">%1$s turned on end-to-end encryption (unrecognised algorithm %2$s).</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -45,7 +45,7 @@ def getVersionCode() {
|
|||
}
|
||||
|
||||
static def gitRevision() {
|
||||
def cmd = "git rev-parse --short HEAD"
|
||||
def cmd = "git rev-parse --short=8 HEAD"
|
||||
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") {
|
||||
return ""
|
||||
} 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 ->
|
||||
buildType.javaCompileOptions.annotationProcessorOptions.arguments =
|
||||
[
|
||||
|
@ -102,8 +117,6 @@ android {
|
|||
// Other branches (master, features, etc.) will have version code based on application version.
|
||||
versionCode project.getVersionCode()
|
||||
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getVersionSuffix()}"
|
||||
|
||||
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
|
||||
resValue "string", "git_revision", "\"${gitRevision()}\""
|
||||
|
||||
|
@ -190,6 +203,8 @@ android {
|
|||
gplay {
|
||||
dimension "store"
|
||||
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
|
||||
|
||||
resValue "bool", "isGplay", "true"
|
||||
buildConfigField "boolean", "ALLOW_FCM_USE", "true"
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
|
||||
|
@ -199,6 +214,8 @@ android {
|
|||
fdroid {
|
||||
dimension "store"
|
||||
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
|
||||
|
||||
resValue "bool", "isGplay", "false"
|
||||
buildConfigField "boolean", "ALLOW_FCM_USE", "false"
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
|
||||
|
|
|
@ -22,32 +22,49 @@ import androidx.fragment.app.FragmentFactory
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationShortCodeFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationStartFragment
|
||||
import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragment
|
||||
import im.vector.riotx.features.grouplist.GroupListFragment
|
||||
import im.vector.riotx.features.home.HomeDetailFragment
|
||||
import im.vector.riotx.features.home.HomeDrawerFragment
|
||||
import im.vector.riotx.features.home.LoadingFragment
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment
|
||||
import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment
|
||||
import im.vector.riotx.features.grouplist.GroupListFragment
|
||||
import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||
import im.vector.riotx.features.login.*
|
||||
import im.vector.riotx.features.login.LoginCaptchaFragment
|
||||
import im.vector.riotx.features.login.LoginFragment
|
||||
import im.vector.riotx.features.login.LoginGenericTextInputFormFragment
|
||||
import im.vector.riotx.features.login.LoginResetPasswordFragment
|
||||
import im.vector.riotx.features.login.LoginResetPasswordMailConfirmationFragment
|
||||
import im.vector.riotx.features.login.LoginResetPasswordSuccessFragment
|
||||
import im.vector.riotx.features.login.LoginServerSelectionFragment
|
||||
import im.vector.riotx.features.login.LoginServerUrlFormFragment
|
||||
import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment
|
||||
import im.vector.riotx.features.login.LoginSplashFragment
|
||||
import im.vector.riotx.features.login.LoginWaitForEmailFragment
|
||||
import im.vector.riotx.features.login.LoginWebFragment
|
||||
import im.vector.riotx.features.login.terms.LoginTermsFragment
|
||||
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
||||
import im.vector.riotx.features.reactions.EmojiChooserFragment
|
||||
import im.vector.riotx.features.reactions.EmojiSearchResultFragment
|
||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||
import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment
|
||||
import im.vector.riotx.features.roomprofile.RoomProfileFragment
|
||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||
import im.vector.riotx.features.settings.*
|
||||
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||
|
@ -272,6 +289,11 @@ interface FragmentModule {
|
|||
@FragmentKey(RoomMemberListFragment::class)
|
||||
fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomSettingsFragment::class)
|
||||
fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(RoomMemberProfileFragment::class)
|
||||
|
|
|
@ -43,11 +43,14 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
|
|||
@EpoxyAttribute
|
||||
var destructive: Boolean = false
|
||||
@EpoxyAttribute
|
||||
lateinit var listener: View.OnClickListener
|
||||
var listener: View.OnClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.view.setOnClickListener(listener)
|
||||
if (listener == null) {
|
||||
holder.view.isClickable = false
|
||||
}
|
||||
holder.editable.isVisible = editable
|
||||
holder.title.text = title
|
||||
val tintColor = if (destructive) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.core.epoxy.profiles
|
|||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.riotx.core.epoxy.ClickListener
|
||||
import im.vector.riotx.core.epoxy.dividerItem
|
||||
|
||||
fun EpoxyController.buildProfileSection(title: String) {
|
||||
|
@ -37,7 +38,7 @@ fun EpoxyController.buildProfileAction(
|
|||
@DrawableRes icon: Int = 0,
|
||||
destructive: Boolean = false,
|
||||
divider: Boolean = true,
|
||||
action: () -> Unit
|
||||
action: ClickListener? = null
|
||||
) {
|
||||
profileActionItem {
|
||||
iconRes(icon)
|
||||
|
@ -47,7 +48,7 @@ fun EpoxyController.buildProfileAction(
|
|||
destructive(destructive)
|
||||
title(title)
|
||||
listener { _ ->
|
||||
action()
|
||||
action?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -22,10 +22,15 @@ import android.app.ProgressDialog
|
|||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
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.LayoutRes
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
|
@ -35,11 +40,13 @@ import com.airbnb.mvrx.BaseMvRxFragment
|
|||
import com.airbnb.mvrx.MvRx
|
||||
import com.bumptech.glide.util.Util.assertMainThread
|
||||
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.HasScreenInjector
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.features.navigation.Navigator
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import timber.log.Timber
|
||||
|
@ -120,6 +127,14 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
mUnBinder = ButterKnife.bind(this, view)
|
||||
}
|
||||
|
||||
open fun showLoading(message: CharSequence?) {
|
||||
showLoadingDialog(message)
|
||||
}
|
||||
|
||||
open fun showFailure(throwable: Throwable) {
|
||||
displayErrorDialog(throwable)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun 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 {
|
||||
setCancelable(cancelable)
|
||||
setMessage(message)
|
||||
setMessage(message ?: getString(R.string.please_wait))
|
||||
setProgressStyle(ProgressDialog.STYLE_SPINNER)
|
||||
show()
|
||||
}
|
||||
|
@ -220,6 +235,21 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
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
|
||||
* ========================================================================================== */
|
||||
|
@ -233,4 +263,16 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -16,20 +16,23 @@
|
|||
|
||||
package im.vector.riotx.core.platform
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.*
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.BaseMvRxViewModel
|
||||
import com.airbnb.mvrx.Fail
|
||||
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.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) {
|
||||
|
||||
// Generic handling of any request error
|
||||
protected val _requestErrorLiveData = MutableLiveData<LiveEvent<Throwable>>()
|
||||
val requestErrorLiveData: LiveData<LiveEvent<Throwable>>
|
||||
get() = _requestErrorLiveData
|
||||
// Used to post transient events to the View
|
||||
protected val _viewEvents = PublishDataSource<VE>()
|
||||
val viewEvents: DataSource<VE> = _viewEvents
|
||||
|
||||
/**
|
||||
* This method does the same thing as the execute function, but it doesn't subscribe to the stream
|
||||
|
@ -53,5 +56,5 @@ abstract class VectorViewModel<S : MvRxState, A : VectorViewModelAction>(initial
|
|||
.doOnNext { setState { stateReducer(it) } }
|
||||
}
|
||||
|
||||
abstract fun handle(action: A)
|
||||
abstract fun handle(action: VA)
|
||||
}
|
||||
|
|
|
@ -113,3 +113,39 @@ fun containsOnlyEmojis(str: String?): Boolean {
|
|||
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as split, but considering emojis
|
||||
*/
|
||||
fun CharSequence.splitEmoji(): List<CharSequence> {
|
||||
val result = mutableListOf<CharSequence>()
|
||||
|
||||
var index = 0
|
||||
|
||||
while (index < length) {
|
||||
val firstChar = get(index)
|
||||
|
||||
if (firstChar.toInt() == 0x200e) {
|
||||
// Left to right mark. What should I do with it?
|
||||
} else if (firstChar.toInt() in 0xD800..0xDBFF && index + 1 < length) {
|
||||
// We have the start of a surrogate pair
|
||||
val secondChar = get(index + 1)
|
||||
|
||||
if (secondChar.toInt() in 0xDC00..0xDFFF) {
|
||||
// We have an emoji
|
||||
result.add("$firstChar$secondChar")
|
||||
index++
|
||||
} else {
|
||||
// Not sure what we have here...
|
||||
result.add("$firstChar")
|
||||
}
|
||||
} else {
|
||||
// Regular char
|
||||
result.add("$firstChar")
|
||||
}
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||
KICK_USER("/kick", "<user-id> [reason]", R.string.command_description_kick_user),
|
||||
CHANGE_DISPLAY_NAME("/nick", "<display-name>", R.string.command_description_nick),
|
||||
MARKDOWN("/markdown", "<on|off>", R.string.command_description_markdown),
|
||||
RAINBOW("/rainbow", "<message>", R.string.command_description_rainbow),
|
||||
RAINBOW_EMOTE("/rainbowme", "<message>", R.string.command_description_rainbow_emote),
|
||||
CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token),
|
||||
SPOILER("/spoiler", "<message>", R.string.command_description_spoiler);
|
||||
|
||||
|
|
|
@ -80,6 +80,16 @@ object CommandParser {
|
|||
|
||||
ParsedCommand.SendEmote(message)
|
||||
}
|
||||
Command.RAINBOW.command -> {
|
||||
val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim()
|
||||
|
||||
ParsedCommand.SendRainbow(message)
|
||||
}
|
||||
Command.RAINBOW_EMOTE.command -> {
|
||||
val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim()
|
||||
|
||||
ParsedCommand.SendRainbowEmote(message)
|
||||
}
|
||||
Command.JOIN_ROOM.command -> {
|
||||
if (messageParts.size >= 2) {
|
||||
val roomAlias = messageParts[1]
|
||||
|
|
|
@ -34,6 +34,8 @@ sealed class ParsedCommand {
|
|||
// Valid commands:
|
||||
|
||||
class SendEmote(val message: CharSequence) : ParsedCommand()
|
||||
class SendRainbow(val message: CharSequence) : ParsedCommand()
|
||||
class SendRainbowEmote(val message: CharSequence) : ParsedCommand()
|
||||
class BanUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||
class UnbanUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||
class SetUserPowerLevel(val userId: String, val powerLevel: Int) : ParsedCommand()
|
||||
|
|
|
@ -23,7 +23,11 @@ import com.airbnb.mvrx.withState
|
|||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
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 kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.*
|
||||
import javax.inject.Inject
|
||||
|
|
|
@ -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.rx.rx
|
||||
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.utils.LiveEvent
|
||||
import io.reactivex.Single
|
||||
|
@ -51,7 +52,7 @@ data class SelectUserAction(
|
|||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: CreateDirectRoomViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction>(initialState) {
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -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.KeysBackupStateListener
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
||||
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
||||
session: Session
|
||||
) : VectorViewModel<KeysBackupSettingViewState, KeyBackupSettingsAction>(initialState),
|
||||
) : VectorViewModel<KeysBackupSettingViewState, KeyBackupSettingsAction, EmptyViewEvents>(initialState),
|
||||
KeysBackupStateListener {
|
||||
|
||||
@AssistedInject.Factory
|
||||
|
|
|
@ -44,6 +44,12 @@ abstract class FormSwitchItem : VectorEpoxyModel<FormSwitchItem.Holder>() {
|
|||
var summary: String? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.view.setOnClickListener {
|
||||
if (enabled) {
|
||||
holder.switchView.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
holder.titleView.text = title
|
||||
holder.summaryView.setTextOrHide(summary)
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.R
|
||||
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.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
|
@ -45,7 +46,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
|||
private val selectedGroupStore: SelectedGroupDataSource,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider
|
||||
) : VectorViewModel<GroupListViewState, GroupListAction>(initialState) {
|
||||
) : VectorViewModel<GroupListViewState, GroupListAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.squareup.inject.assisted.AssistedInject
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.rx.rx
|
||||
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.resources.StringProvider
|
||||
import im.vector.riotx.features.grouplist.SelectedGroupDataSource
|
||||
|
@ -40,7 +41,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
|||
private val selectedGroupStore: SelectedGroupDataSource,
|
||||
private val homeRoomListStore: HomeRoomListDataSource,
|
||||
private val stringProvider: StringProvider)
|
||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction>(initialState) {
|
||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -24,12 +24,13 @@ import com.squareup.inject.assisted.AssistedInject
|
|||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<BreadcrumbsViewState, EmptyAction>(initialState) {
|
||||
: VectorViewModel<BreadcrumbsViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -85,6 +85,7 @@ import im.vector.riotx.R
|
|||
import im.vector.riotx.core.dialogs.withColoredButton
|
||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||
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.observeEvent
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
|
@ -306,16 +307,13 @@ class RoomDetailFragment @Inject constructor(
|
|||
displayRoomDetailActionResult(it)
|
||||
}
|
||||
|
||||
roomDetailViewModel.viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
roomDetailViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable)
|
||||
is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for RoomDetail
|
||||
*/
|
||||
sealed class RoomDetailViewEvents {
|
||||
sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||
data class Failure(val throwable: Throwable) : RoomDetailViewEvents()
|
||||
data class OnNewTimelineEvents(val eventIds: List<String>) : RoomDetailViewEvents()
|
||||
}
|
||||
|
|
|
@ -60,17 +60,17 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten
|
|||
import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
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.NoOpMatrixCallback
|
||||
import im.vector.riotx.core.utils.PublishDataSource
|
||||
import im.vector.riotx.core.utils.subscribeLogError
|
||||
import im.vector.riotx.features.command.CommandParser
|
||||
import im.vector.riotx.features.command.ParsedCommand
|
||||
import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.riotx.features.home.room.typing.TypingHelper
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
|
@ -90,8 +90,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
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 eventId = initialState.eventId
|
||||
|
@ -114,9 +115,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
var timeline = room.createTimeline(eventId, timelineSettings)
|
||||
private set
|
||||
|
||||
private val _viewEvents = PublishDataSource<RoomDetailViewEvents>()
|
||||
val viewEvents: DataSource<RoomDetailViewEvents> = _viewEvents
|
||||
|
||||
// Can be used for several actions, for a one shot result
|
||||
private val _requestLiveData = MutableLiveData<LiveEvent<Async<RoomDetailAction>>>()
|
||||
val requestLiveData: LiveData<LiveEvent<Async<RoomDetailAction>>>
|
||||
|
@ -317,6 +315,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
}
|
||||
}
|
||||
|
||||
// TODO Cleanup this and use ViewEvents
|
||||
private val _nonBlockingPopAlert = MutableLiveData<LiveEvent<Pair<Int, List<Any>>>>()
|
||||
val nonBlockingPopAlert: LiveData<LiveEvent<Pair<Int, List<Any>>>>
|
||||
get() = _nonBlockingPopAlert
|
||||
|
@ -411,6 +410,20 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendRainbow -> {
|
||||
slashCommandResult.message.toString().let {
|
||||
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it))
|
||||
}
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendRainbowEmote -> {
|
||||
slashCommandResult.message.toString().let {
|
||||
room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE)
|
||||
}
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendSpoiler -> {
|
||||
room.sendFormattedTextMessage(
|
||||
"[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})",
|
||||
|
@ -427,7 +440,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
// is original event a reply?
|
||||
|
@ -485,7 +498,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||
popDraft()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.detail.composer.rainbow
|
||||
|
||||
import im.vector.riotx.core.utils.splitEmoji
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Inspired from React-Sdk
|
||||
* Ref: https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/utils/colour.js
|
||||
*/
|
||||
class RainbowGenerator @Inject constructor() {
|
||||
|
||||
fun generate(text: String): String {
|
||||
val split = text.splitEmoji()
|
||||
val frequency = 360f / split.size
|
||||
|
||||
return split
|
||||
.mapIndexed { idx, letter ->
|
||||
// Do better than React-Sdk: Avoid adding font color for spaces
|
||||
if (letter == " ") {
|
||||
"$letter"
|
||||
} else {
|
||||
val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor()
|
||||
"<font color=\"$dashColor\">$letter</font>"
|
||||
}
|
||||
}
|
||||
.joinToString(separator = "")
|
||||
}
|
||||
|
||||
private fun hueToRGB(h: Float, s: Float, l: Float): RgbColor {
|
||||
val c = s * (1 - abs(2 * l - 1))
|
||||
val x = c * (1 - abs((h / 60) % 2 - 1))
|
||||
val m = l - c / 2
|
||||
|
||||
var r = 0f
|
||||
var g = 0f
|
||||
var b = 0f
|
||||
|
||||
when {
|
||||
h < 60f -> {
|
||||
r = c
|
||||
g = x
|
||||
}
|
||||
h < 120f -> {
|
||||
r = x
|
||||
g = c
|
||||
}
|
||||
h < 180f -> {
|
||||
g = c
|
||||
b = x
|
||||
}
|
||||
h < 240f -> {
|
||||
g = x
|
||||
b = c
|
||||
}
|
||||
h < 300f -> {
|
||||
r = x
|
||||
b = c
|
||||
}
|
||||
else -> {
|
||||
r = c
|
||||
b = x
|
||||
}
|
||||
}
|
||||
|
||||
return RgbColor(
|
||||
((r + m) * 255).roundToInt(),
|
||||
((g + m) * 255).roundToInt(),
|
||||
((b + m) * 255).roundToInt()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.detail.composer.rainbow
|
||||
|
||||
data class RgbColor(
|
||||
val r: Int,
|
||||
val g: Int,
|
||||
val b: Int
|
||||
)
|
||||
|
||||
fun RgbColor.toDashColor(): String {
|
||||
return listOf(r, g, b)
|
||||
.joinToString(separator = "", prefix = "#") {
|
||||
it.toString(16).padStart(2, '0')
|
||||
}
|
||||
}
|
|
@ -46,18 +46,15 @@ class MessageActionsEpoxyController @Inject constructor(
|
|||
|
||||
override fun buildModels(state: MessageActionState) {
|
||||
// Message preview
|
||||
val body = state.messageBody
|
||||
if (body != null) {
|
||||
bottomSheetMessagePreviewItem {
|
||||
id("preview")
|
||||
avatarRenderer(avatarRenderer)
|
||||
matrixItem(state.informationData.matrixItem)
|
||||
movementMethod(createLinkMovementMethod(listener))
|
||||
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
|
||||
body(body.linkify(listener))
|
||||
body(state.messageBody.linkify(listener))
|
||||
time(state.time())
|
||||
}
|
||||
}
|
||||
|
||||
// Send state
|
||||
if (state.informationData.sendState.isSending()) {
|
||||
|
|
|
@ -15,7 +15,12 @@
|
|||
*/
|
||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
||||
|
||||
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.AssistedInject
|
||||
import dagger.Lazy
|
||||
|
@ -35,6 +40,7 @@ import im.vector.matrix.rx.rx
|
|||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.R
|
||||
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.resources.StringProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||
|
@ -44,7 +50,8 @@ import im.vector.riotx.features.html.VectorHtmlCompressor
|
|||
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Quick reactions state
|
||||
|
@ -59,7 +66,7 @@ data class MessageActionState(
|
|||
val eventId: String,
|
||||
val informationData: MessageInformationData,
|
||||
val timelineEvent: Async<TimelineEvent> = Uninitialized,
|
||||
val messageBody: CharSequence? = null,
|
||||
val messageBody: CharSequence = "",
|
||||
// For quick reactions
|
||||
val quickStates: Async<List<ToggleState>> = Uninitialized,
|
||||
// For actions
|
||||
|
@ -89,7 +96,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
private val noticeEventFormatter: NoticeEventFormatter,
|
||||
private val stringProvider: StringProvider,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : VectorViewModel<MessageActionState, MessageActionsAction>(initialState) {
|
||||
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
private val eventId = initialState.eventId
|
||||
private val informationData = initialState.informationData
|
||||
|
@ -154,13 +161,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
|
||||
private fun observeTimelineEventState() {
|
||||
asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent ->
|
||||
val computedMessage = computeMessageBody(timelineEvent)
|
||||
val actions = actionsForEvent(timelineEvent)
|
||||
setState { copy(messageBody = computedMessage, actions = actions) }
|
||||
setState {
|
||||
copy(
|
||||
messageBody = computeMessageBody(timelineEvent),
|
||||
actions = actionsForEvent(timelineEvent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? {
|
||||
private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence {
|
||||
return when (timelineEvent.root.getClearType()) {
|
||||
EventType.MESSAGE,
|
||||
EventType.STICKER -> {
|
||||
|
@ -188,7 +198,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||
noticeEventFormatter.format(timelineEvent)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} ?: ""
|
||||
}
|
||||
|
||||
private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> {
|
||||
|
|
|
@ -15,7 +15,15 @@
|
|||
*/
|
||||
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.AssistedInject
|
||||
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.riotx.core.date.VectorDateFormatter
|
||||
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.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
data class ViewEditHistoryViewState(
|
||||
val eventId: String,
|
||||
|
@ -47,7 +56,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
|||
initialState: ViewEditHistoryViewState,
|
||||
val session: Session,
|
||||
val dateFormatter: VectorDateFormatter
|
||||
) : VectorViewModel<ViewEditHistoryViewState, EmptyAction>(initialState) {
|
||||
) : VectorViewModel<ViewEditHistoryViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
private val roomId = initialState.roomId
|
||||
private val eventId = initialState.eventId
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
|
||||
package im.vector.riotx.features.home.room.detail.timeline.factory
|
||||
|
||||
import android.view.View
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_
|
||||
|
@ -28,20 +31,26 @@ import javax.inject.Inject
|
|||
|
||||
class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider,
|
||||
private val informationDataFactory: MessageInformationDataFactory) {
|
||||
|
||||
fun create(text: String,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?): DefaultItem {
|
||||
val attributes = DefaultItem.Attributes(
|
||||
avatarRenderer = avatarRenderer,
|
||||
informationData = informationData,
|
||||
text = text,
|
||||
itemLongClickListener = View.OnLongClickListener { view ->
|
||||
callback?.onEventLongClicked(informationData, null, view) ?: false
|
||||
},
|
||||
readReceiptsCallback = callback
|
||||
)
|
||||
return DefaultItem_()
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.highlighted(highlight)
|
||||
.text(text)
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.informationData(informationData)
|
||||
.baseCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.attributes(attributes)
|
||||
}
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
|
@ -49,9 +58,9 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava
|
|||
callback: TimelineEventController.Callback?,
|
||||
throwable: Throwable? = null): DefaultItem {
|
||||
val text = if (throwable == null) {
|
||||
"${event.root.getClearType()} events are not yet handled"
|
||||
stringProvider.getString(R.string.rendering_event_error_type_of_event_not_handled, event.root.getClearType())
|
||||
} else {
|
||||
"an exception occurred when rendering the event ${event.root.eventId}"
|
||||
stringProvider.getString(R.string.rendering_event_error_exception, event.root.eventId)
|
||||
}
|
||||
val informationData = informationDataFactory.create(event, null)
|
||||
return create(text, informationData, highlight, callback)
|
||||
|
|
|
@ -26,7 +26,16 @@ import android.view.View
|
|||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.session.events.model.RelationType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.*
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||
|
@ -40,8 +49,24 @@ import im.vector.riotx.core.utils.DimensionConverter
|
|||
import im.vector.riotx.core.utils.containsOnlyEmojis
|
||||
import im.vector.riotx.core.utils.isLocalFile
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.*
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.*
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||
import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
|
||||
import im.vector.riotx.features.html.CodeVisitor
|
||||
|
@ -153,7 +178,7 @@ class MessageItemFactory @Inject constructor(
|
|||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?): DefaultItem? {
|
||||
val text = "${messageContent.type} message events are not yet handled"
|
||||
val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.type)
|
||||
return defaultItemFactory.create(text, informationData, highlight, callback)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,21 @@ package im.vector.riotx.features.home.room.detail.timeline.format
|
|||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.*
|
||||
import im.vector.matrix.android.api.session.room.model.GuestAccess
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomJoinRules
|
||||
import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomNameContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
|
@ -180,7 +192,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
|||
|
||||
private fun formatRoomEncryptionEvent(event: Event, senderName: String?): CharSequence? {
|
||||
val content = event.content.toModel<EncryptionEventContent>() ?: return null
|
||||
return sp.getString(R.string.notice_end_to_end, senderName, content.algorithm)
|
||||
return if (content.algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
sp.getString(R.string.notice_end_to_end_ok, senderName)
|
||||
} else {
|
||||
sp.getString(R.string.notice_end_to_end_unknown_algorithm, senderName, content.algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String {
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
|
|||
import android.view.View
|
||||
import android.view.ViewStub
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
|
@ -42,6 +43,7 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
|
|||
@EpoxyAttribute
|
||||
lateinit var dimensionConverter: DimensionConverter
|
||||
|
||||
@CallSuper
|
||||
override fun bind(holder: H) {
|
||||
super.bind(holder)
|
||||
holder.leftGuideline.updateLayoutParams<RelativeLayout.LayoutParams> {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.riotx.features.home.room.detail.timeline.item
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
|
@ -29,42 +30,39 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
|
|||
abstract class DefaultItem : BaseEventItem<DefaultItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var informationData: MessageInformationData
|
||||
@EpoxyAttribute
|
||||
lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute
|
||||
var baseCallback: TimelineEventController.BaseCallback? = null
|
||||
|
||||
private var longClickListener = View.OnLongClickListener {
|
||||
return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true
|
||||
}
|
||||
|
||||
@EpoxyAttribute
|
||||
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||
lateinit var attributes: Attributes
|
||||
|
||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||
attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts)
|
||||
})
|
||||
|
||||
@EpoxyAttribute
|
||||
var text: CharSequence? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.messageView.text = text
|
||||
holder.view.setOnLongClickListener(longClickListener)
|
||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||
super.bind(holder)
|
||||
holder.messageTextView.text = attributes.text
|
||||
attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
|
||||
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
|
||||
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
|
||||
}
|
||||
|
||||
override fun getEventIds(): List<String> {
|
||||
return listOf(informationData.eventId)
|
||||
return listOf(attributes.informationData.eventId)
|
||||
}
|
||||
|
||||
override fun getViewType() = STUB_ID
|
||||
|
||||
class Holder : BaseHolder(STUB_ID) {
|
||||
val messageView by bind<TextView>(R.id.stateMessageView)
|
||||
val avatarImageView by bind<ImageView>(R.id.itemDefaultAvatarView)
|
||||
val messageTextView by bind<TextView>(R.id.itemDefaultTextView)
|
||||
}
|
||||
|
||||
data class Attributes(
|
||||
val avatarRenderer: AvatarRenderer,
|
||||
val informationData: MessageInformationData,
|
||||
val text: CharSequence,
|
||||
val itemLongClickListener: View.OnLongClickListener? = null,
|
||||
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val STUB_ID = R.id.messageContentDefaultStub
|
||||
}
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
|
||||
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.AssistedInject
|
||||
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.riotx.core.date.VectorDateFormatter
|
||||
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.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
|
||||
import io.reactivex.Observable
|
||||
|
@ -54,7 +60,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
|
|||
initialState: DisplayReactionsViewState,
|
||||
private val session: Session,
|
||||
private val dateFormatter: VectorDateFormatter
|
||||
) : VectorViewModel<DisplayReactionsViewState, EmptyAction>(initialState) {
|
||||
) : VectorViewModel<DisplayReactionsViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
private val roomId = initialState.roomId
|
||||
private val eventId = initialState.eventId
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,26 +22,20 @@ import javax.inject.Inject
|
|||
class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary> {
|
||||
|
||||
override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int {
|
||||
var rightTimestamp = 0L
|
||||
var leftTimestamp = 0L
|
||||
if (null != leftRoomSummary) {
|
||||
leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
|
||||
}
|
||||
if (null != rightRoomSummary) {
|
||||
rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
|
||||
}
|
||||
return if (rightRoomSummary?.latestPreviewableEvent?.root == null) {
|
||||
-1
|
||||
} else if (leftRoomSummary?.latestPreviewableEvent?.root == null) {
|
||||
1
|
||||
} else {
|
||||
return when {
|
||||
rightRoomSummary?.latestPreviewableEvent?.root == null -> -1
|
||||
leftRoomSummary?.latestPreviewableEvent?.root == null -> 1
|
||||
else -> {
|
||||
val rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
|
||||
val leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0
|
||||
|
||||
val deltaTimestamp = rightTimestamp - leftTimestamp
|
||||
if (deltaTimestamp > 0) {
|
||||
1
|
||||
} else if (deltaTimestamp < 0) {
|
||||
-1
|
||||
} else {
|
||||
0
|
||||
|
||||
when {
|
||||
deltaTimestamp > 0 -> 1
|
||||
deltaTimestamp < 0 -> -1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,11 @@ import androidx.core.view.isVisible
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.session.room.model.Membership
|
||||
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.core.epoxy.LayoutManagerStateRestorer
|
||||
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.StateView
|
||||
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.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.share.SharedData
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||
import javax.inject.Inject
|
||||
|
@ -98,16 +102,13 @@ class RoomListFragment @Inject constructor(
|
|||
setupRecyclerView()
|
||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||
roomListViewModel.subscribe { renderState(it) }
|
||||
roomListViewModel.viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
roomListViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomListViewEvents.Loading -> showLoading(it.message)
|
||||
is RoomListViewEvents.Failure -> showFailure(it.throwable)
|
||||
is RoomListViewEvents.SelectRoom -> openSelectedRoom(it)
|
||||
is RoomListViewEvents.Failure -> showErrorInSnackbar(it.throwable)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
createChatFabMenu.listener = this
|
||||
|
||||
|
@ -117,6 +118,10 @@ class RoomListFragment @Inject constructor(
|
|||
.disposeOnDestroyView()
|
||||
}
|
||||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
showErrorInSnackbar(throwable)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
roomController.removeModelBuildListener(modelBuildListener)
|
||||
modelBuildListener = null
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
|
||||
package im.vector.riotx.features.home.room.list
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* 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 SelectRoom(val roomId: String) : RoomListViewEvents()
|
||||
}
|
||||
|
|
|
@ -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.riotx.core.platform.VectorViewModel
|
||||
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 timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -34,7 +34,7 @@ import javax.inject.Inject
|
|||
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
private val session: Session,
|
||||
private val roomSummariesSource: DataSource<List<RoomSummary>>)
|
||||
: VectorViewModel<RoomListViewState, RoomListAction>(initialState) {
|
||||
: VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
||||
|
||||
interface Factory {
|
||||
fun create(initialState: RoomListViewState): RoomListViewModel
|
||||
|
@ -52,9 +52,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
private val displayMode = initialState.displayMode
|
||||
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
|
||||
|
||||
private val _viewEvents = PublishDataSource<RoomListViewEvents>()
|
||||
val viewEvents: DataSource<RoomListViewEvents> = _viewEvents
|
||||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
}
|
||||
|
@ -197,6 +194,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
}
|
||||
|
||||
private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) {
|
||||
_viewEvents.post(RoomListViewEvents.Loading(null))
|
||||
session.getRoom(action.roomId)?.leave(null, object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||
|
@ -205,6 +203,24 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
}
|
||||
|
||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||
if (displayMode == RoomListDisplayMode.SHARE) {
|
||||
val recentRooms = ArrayList<RoomSummary>(20)
|
||||
val otherRooms = ArrayList<RoomSummary>(rooms.size)
|
||||
|
||||
rooms
|
||||
.filter { roomListDisplayModeFilter.test(it) }
|
||||
.forEach { room ->
|
||||
when (room.breadcrumbsIndex) {
|
||||
RoomSummary.NOT_IN_BREADCRUMBS -> otherRooms.add(room)
|
||||
else -> recentRooms.add(room)
|
||||
}
|
||||
}
|
||||
|
||||
return RoomSummaries().apply {
|
||||
put(RoomCategory.RECENT_ROOMS, recentRooms)
|
||||
put(RoomCategory.OTHER_ROOMS, otherRooms)
|
||||
}
|
||||
} else {
|
||||
// Set up init size on directChats and groupRooms as they are the biggest ones
|
||||
val invites = ArrayList<RoomSummary>()
|
||||
val favourites = ArrayList<RoomSummary>()
|
||||
|
@ -237,3 +253,4 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,10 @@ data class RoomListViewState(
|
|||
val isDirectRoomsExpanded: Boolean = true,
|
||||
val isGroupRoomsExpanded: 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 {
|
||||
|
||||
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
||||
|
@ -56,6 +59,8 @@ data class RoomListViewState(
|
|||
RoomCategory.GROUP -> isGroupRoomsExpanded
|
||||
RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded
|
||||
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.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded)
|
||||
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),
|
||||
GROUP(R.string.bottom_action_rooms),
|
||||
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 {
|
||||
|
|
|
@ -59,39 +59,9 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
override fun buildModels() {
|
||||
val nonNullViewState = viewState ?: return
|
||||
when (nonNullViewState.displayMode) {
|
||||
RoomListDisplayMode.FILTERED,
|
||||
RoomListDisplayMode.SHARE -> {
|
||||
buildFilteredRooms(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()
|
||||
}
|
||||
}
|
||||
RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState)
|
||||
RoomListDisplayMode.SHARE -> buildShareRooms(nonNullViewState)
|
||||
else -> buildRooms(nonNullViewState)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,9 +79,69 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
viewState.rejectingRoomsIds,
|
||||
viewState.rejectingErrorRoomsIds)
|
||||
|
||||
when {
|
||||
viewState.displayMode == RoomListDisplayMode.FILTERED -> addFilterFooter(viewState)
|
||||
filteredSummaries.isEmpty() -> addEmptyFooter()
|
||||
addFilterFooter(viewState)
|
||||
}
|
||||
|
||||
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 {
|
||||
id("no_result")
|
||||
text(stringProvider.getString(R.string.no_result_placeholder))
|
||||
|
@ -142,9 +172,6 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
|
|||
@StringRes titleRes: Int,
|
||||
isExpanded: Boolean,
|
||||
mutateExpandedState: () -> Unit) {
|
||||
if (summaries.isEmpty()) {
|
||||
return
|
||||
}
|
||||
// TODO should add some business logic later
|
||||
val unreadCount = if (summaries.isEmpty()) {
|
||||
0
|
||||
|
|
|
@ -24,11 +24,12 @@ import im.vector.matrix.android.api.session.Session
|
|||
import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
||||
class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState,
|
||||
session: Session
|
||||
) : VectorViewModel<RoomListQuickActionsState, EmptyAction>(initialState) {
|
||||
) : VectorViewModel<RoomListQuickActionsState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -27,9 +27,9 @@ import com.airbnb.mvrx.withState
|
|||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
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.VectorBaseFragment
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
/**
|
||||
|
@ -59,25 +59,21 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
|||
|
||||
loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java)
|
||||
|
||||
loginViewModel.viewEvents
|
||||
.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
loginViewModel.observeViewEvents {
|
||||
handleLoginViewEvents(it)
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
|
||||
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
||||
when (loginViewEvents) {
|
||||
is LoginViewEvents.Error -> showError(loginViewEvents.throwable)
|
||||
is LoginViewEvents.Failure -> showFailure(loginViewEvents.throwable)
|
||||
else ->
|
||||
// This is handled by the Activity
|
||||
Unit
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun showError(throwable: Throwable) {
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is Failure.ServerError -> {
|
||||
if (throwable.error.code == MatrixError.M_FORBIDDEN
|
||||
|
@ -96,11 +92,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
|||
}
|
||||
|
||||
open fun onError(throwable: Throwable) {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(errorFormatter.toHumanReadable(throwable))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
super.showFailure(throwable)
|
||||
}
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||
|
|
|
@ -209,7 +209,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
.setMessage(R.string.login_error_outdated_homeserver_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
is LoginViewEvents.Error ->
|
||||
is LoginViewEvents.Failure ->
|
||||
// This is handled by the Fragments
|
||||
Unit
|
||||
}
|
||||
|
|
|
@ -18,12 +18,15 @@
|
|||
package im.vector.riotx.features.login
|
||||
|
||||
import im.vector.matrix.android.api.auth.registration.FlowResult
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* 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 Error(val throwable: Throwable) : LoginViewEvents()
|
||||
object OutdatedHomeserver : LoginViewEvents()
|
||||
}
|
||||
|
|
|
@ -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.extensions.configureAndStart
|
||||
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.session.SessionListener
|
||||
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 homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
||||
private val sessionListener: SessionListener)
|
||||
: VectorViewModel<LoginViewState, LoginAction>(initialState) {
|
||||
: VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -95,9 +93,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||
|
||||
private var currentTask: Cancelable? = null
|
||||
|
||||
private val _viewEvents = PublishDataSource<LoginViewEvents>()
|
||||
val viewEvents: DataSource<LoginViewEvents> = _viewEvents
|
||||
|
||||
override fun handle(action: LoginAction) {
|
||||
when (action) {
|
||||
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
|
||||
|
@ -179,7 +174,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (failure !is CancellationException) {
|
||||
_viewEvents.post(LoginViewEvents.Error(failure))
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
|
@ -201,7 +196,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Error(failure))
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
|
@ -223,7 +218,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Error(failure))
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
|
@ -526,7 +521,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||
|
||||
if (homeServerConnectionConfig == null) {
|
||||
// This is invalid
|
||||
_viewEvents.post(LoginViewEvents.Error(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
_viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
} else {
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
|
@ -540,7 +535,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
|||
|
||||
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Error(failure))
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
|
|
|
@ -21,6 +21,7 @@ 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.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.reactions.data.EmojiDataSource
|
||||
import im.vector.riotx.features.reactions.data.EmojiItem
|
||||
|
@ -33,7 +34,7 @@ data class EmojiSearchResultViewState(
|
|||
class EmojiSearchResultViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: EmojiSearchResultViewState,
|
||||
private val dataSource: EmojiDataSource)
|
||||
: VectorViewModel<EmojiSearchResultViewState, EmojiSearchAction>(initialState) {
|
||||
: VectorViewModel<EmojiSearchResultViewState, EmojiSearchAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -75,6 +75,7 @@ class PublicRoomsFragment @Inject constructor(
|
|||
sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)
|
||||
}
|
||||
|
||||
// TODO remove this, replace by ViewEvents
|
||||
viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable ->
|
||||
Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
|
|
|
@ -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.rx.rx
|
||||
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.utils.LiveEvent
|
||||
import timber.log.Timber
|
||||
|
@ -41,7 +42,7 @@ private const val PUBLIC_ROOMS_LIMIT = 20
|
|||
|
||||
class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: PublicRoomsViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<PublicRoomsViewState, RoomDirectoryAction>(initialState) {
|
||||
: VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -22,5 +22,6 @@ sealed class CreateRoomAction : VectorViewModelAction {
|
|||
data class SetName(val name: String) : CreateRoomAction()
|
||||
data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction()
|
||||
data class SetIsInRoomDirectory(val isInRoomDirectory: Boolean) : CreateRoomAction()
|
||||
data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction()
|
||||
object Create : CreateRoomAction()
|
||||
}
|
||||
|
|
|
@ -39,9 +39,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
|
|||
var index = 0
|
||||
|
||||
override fun buildModels(viewState: CreateRoomViewState) {
|
||||
val asyncCreateRoom = viewState.asyncCreateRoomRequest
|
||||
|
||||
when (asyncCreateRoom) {
|
||||
when (val asyncCreateRoom = viewState.asyncCreateRoomRequest) {
|
||||
is Success -> {
|
||||
// Nothing to display, the screen will be closed
|
||||
}
|
||||
|
@ -101,12 +99,24 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
|
|||
listener?.setIsInRoomDirectory(value)
|
||||
}
|
||||
}
|
||||
formSwitchItem {
|
||||
id("encryption")
|
||||
enabled(enableFormElement)
|
||||
title(stringProvider.getString(R.string.create_room_encryption_title))
|
||||
summary(stringProvider.getString(R.string.create_room_encryption_description))
|
||||
switchChecked(viewState.isEncrypted)
|
||||
|
||||
listener { value ->
|
||||
listener?.setIsEncrypted(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onNameChange(newName: String)
|
||||
fun setIsPublic(isPublic: Boolean)
|
||||
fun setIsInRoomDirectory(isInRoomDirectory: Boolean)
|
||||
fun setIsEncrypted(isEncrypted: Boolean)
|
||||
fun retry()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,6 +85,10 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C
|
|||
viewModel.handle(CreateRoomAction.SetIsInRoomDirectory(isInRoomDirectory))
|
||||
}
|
||||
|
||||
override fun setIsEncrypted(isEncrypted: Boolean) {
|
||||
viewModel.handle(CreateRoomAction.SetIsEncrypted(isEncrypted))
|
||||
}
|
||||
|
||||
override fun retry() {
|
||||
Timber.v("Retry")
|
||||
viewModel.handle(CreateRoomAction.Create)
|
||||
|
|
|
@ -17,7 +17,12 @@
|
|||
package im.vector.riotx.features.roomdirectory.createroom
|
||||
|
||||
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.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
|
@ -25,12 +30,14 @@ 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.create.CreateRoomParams
|
||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||
|
||||
class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<CreateRoomViewState, CreateRoomAction>(initialState) {
|
||||
) : VectorViewModel<CreateRoomViewState, CreateRoomAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -56,6 +63,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
is CreateRoomAction.SetName -> setName(action)
|
||||
is CreateRoomAction.SetIsPublic -> setIsPublic(action)
|
||||
is CreateRoomAction.SetIsInRoomDirectory -> setIsInRoomDirectory(action)
|
||||
is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action)
|
||||
is CreateRoomAction.Create -> doCreateRoom()
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +74,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
|
||||
private fun setIsInRoomDirectory(action: CreateRoomAction.SetIsInRoomDirectory) = setState { copy(isInRoomDirectory = action.isInRoomDirectory) }
|
||||
|
||||
private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) }
|
||||
|
||||
private fun doCreateRoom() = withState { state ->
|
||||
if (state.asyncCreateRoomRequest is Loading || state.asyncCreateRoomRequest is Success) {
|
||||
return@withState
|
||||
|
@ -83,6 +93,11 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
|
||||
// Public room
|
||||
preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT
|
||||
|
||||
// Encryption
|
||||
if (state.isEncrypted) {
|
||||
enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM)
|
||||
}
|
||||
}
|
||||
|
||||
session.createRoom(createRoomParams, object : MatrixCallback<String> {
|
||||
|
|
|
@ -24,5 +24,6 @@ data class CreateRoomViewState(
|
|||
val roomName: String = "",
|
||||
val isPublic: Boolean = false,
|
||||
val isInRoomDirectory: Boolean = false,
|
||||
val isEncrypted: Boolean = false,
|
||||
val asyncCreateRoomRequest: Async<String> = Uninitialized
|
||||
) : MvRxState
|
||||
|
|
|
@ -16,17 +16,22 @@
|
|||
|
||||
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.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
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
|
||||
|
||||
class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction>(initialState) {
|
||||
: VectorViewModel<RoomDirectoryPickerViewState, RoomDirectoryPickerAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -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.roomSummaryQueryParams
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.roomdirectory.JoinState
|
||||
import timber.log.Timber
|
||||
|
||||
class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: RoomPreviewViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<RoomPreviewViewState, RoomPreviewAction>(initialState) {
|
||||
: VectorViewModel<RoomPreviewViewState, RoomPreviewAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -20,13 +20,19 @@ package im.vector.riotx.features.roommemberprofile
|
|||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
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 com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.api.util.MatrixItem
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.animations.AppBarStateChangeListener
|
||||
import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
|
||||
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.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.platform.StateView
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
|
@ -73,17 +79,14 @@ class RoomMemberProfileFragment @Inject constructor(
|
|||
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
|
||||
matrixProfileToolbarTitleView))
|
||||
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
|
||||
viewModel.viewEvents
|
||||
.observe()
|
||||
.subscribe {
|
||||
dismissLoadingDialog()
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomMemberProfileViewEvents.Loading -> showLoadingDialog(it.message)
|
||||
is RoomMemberProfileViewEvents.Failure -> showErrorInSnackbar(it.throwable)
|
||||
is RoomMemberProfileViewEvents.Loading -> showLoading(it.message)
|
||||
is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable)
|
||||
is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener)
|
||||
|
|
|
@ -16,11 +16,14 @@
|
|||
|
||||
package im.vector.riotx.features.roommemberprofile
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for RoomMemberProfile
|
||||
*/
|
||||
sealed class RoomMemberProfileViewEvents {
|
||||
data class Loading(val message: CharSequence) : RoomMemberProfileViewEvents()
|
||||
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
|
||||
sealed class RoomMemberProfileViewEvents : VectorViewEvents {
|
||||
data class Loading(val message: CharSequence? = null) : RoomMemberProfileViewEvents()
|
||||
data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents()
|
||||
|
||||
object OnIgnoreActionSuccess : RoomMemberProfileViewEvents()
|
||||
}
|
||||
|
|
|
@ -44,8 +44,6 @@ import im.vector.matrix.rx.unwrap
|
|||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.DataSource
|
||||
import im.vector.riotx.core.utils.PublishDataSource
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -55,7 +53,7 @@ import kotlinx.coroutines.withContext
|
|||
class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState,
|
||||
private val stringProvider: StringProvider,
|
||||
private val session: Session)
|
||||
: VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction>(initialState) {
|
||||
: VectorViewModel<RoomMemberProfileViewState, RoomMemberProfileAction, RoomMemberProfileViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -71,9 +69,6 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
|||
}
|
||||
}
|
||||
|
||||
private val _viewEvents = PublishDataSource<RoomMemberProfileViewEvents>()
|
||||
val viewEvents: DataSource<RoomMemberProfileViewEvents> = _viewEvents
|
||||
|
||||
private val room = if (initialState.roomId != null) {
|
||||
session.getRoom(initialState.roomId)
|
||||
} else {
|
||||
|
@ -183,7 +178,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v
|
|||
|
||||
private fun handleIgnoreAction() = withState { state ->
|
||||
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> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
_viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess)
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack
|
|||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment
|
||||
import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment
|
||||
|
||||
class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||
|
||||
|
@ -69,7 +70,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
}
|
||||
|
||||
private fun openRoomSettings() {
|
||||
notImplemented("Open room settings")
|
||||
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
|
||||
}
|
||||
|
||||
private fun openRoomMembers() {
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.riotx.core.animations.AppBarStateChangeListener
|
|||
import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener
|
||||
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.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
|
@ -77,17 +78,13 @@ class RoomProfileFragment @Inject constructor(
|
|||
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView,
|
||||
matrixProfileToolbarTitleView))
|
||||
matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener)
|
||||
roomProfileViewModel.viewEvents
|
||||
.observe()
|
||||
.subscribe {
|
||||
dismissLoadingDialog()
|
||||
roomProfileViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is RoomProfileViewEvents.Loading -> showLoadingDialog(it.message)
|
||||
RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
||||
is RoomProfileViewEvents.Failure -> showError(it.throwable)
|
||||
is RoomProfileViewEvents.Loading -> showLoading(it.message)
|
||||
is RoomProfileViewEvents.Failure -> showFailure(it.throwable)
|
||||
is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom()
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
roomListQuickActionsSharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { handleQuickActions(it) }
|
||||
|
|
|
@ -15,11 +15,15 @@
|
|||
*/
|
||||
|
||||
package im.vector.riotx.features.roomprofile
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* Transient events for RoomProfile
|
||||
*/
|
||||
sealed class RoomProfileViewEvents {
|
||||
data class Loading(val message: CharSequence): RoomProfileViewEvents()
|
||||
object OnLeaveRoomSuccess: RoomProfileViewEvents()
|
||||
sealed class RoomProfileViewEvents : VectorViewEvents {
|
||||
data class Loading(val message: CharSequence? = null) : RoomProfileViewEvents()
|
||||
data class Failure(val throwable: Throwable) : RoomProfileViewEvents()
|
||||
|
||||
object OnLeaveRoomSuccess : RoomProfileViewEvents()
|
||||
}
|
||||
|
|
|
@ -29,13 +29,11 @@ import im.vector.matrix.rx.unwrap
|
|||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
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,
|
||||
private val stringProvider: StringProvider,
|
||||
private val session: Session)
|
||||
: VectorViewModel<RoomProfileViewState, RoomProfileAction>(initialState) {
|
||||
: VectorViewModel<RoomProfileViewState, RoomProfileAction, RoomProfileViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.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)!!
|
||||
|
||||
init {
|
||||
|
|
|
@ -29,7 +29,7 @@ import im.vector.riotx.core.extensions.configureWith
|
|||
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_member_list.*
|
||||
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomMemberListFragment @Inject constructor(
|
||||
|
@ -41,12 +41,12 @@ class RoomMemberListFragment @Inject constructor(
|
|||
private val viewModel: RoomMemberListViewModel by fragmentViewModel()
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
roomMemberListController.callback = this
|
||||
setupToolbar(roomMemberListToolbar)
|
||||
setupToolbar(roomSettingsToolbar)
|
||||
recyclerView.configureWith(roomMemberListController, hasFixedSize = true)
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,8 @@ class RoomMemberListFragment @Inject constructor(
|
|||
|
||||
private fun renderRoomSummary(state: RoomMemberListViewState) {
|
||||
state.roomSummary()?.let {
|
||||
roomMemberListToolbarTitleView.text = it.displayName
|
||||
avatarRenderer.render(it.toMatrixItem(), roomMemberListToolbarAvatarImageView)
|
||||
roomSettingsToolbarTitleView.text = it.displayName
|
||||
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,13 +34,15 @@ import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper
|
|||
import im.vector.matrix.rx.mapOptional
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.matrix.rx.unwrap
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.BiFunction
|
||||
|
||||
class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState,
|
||||
private val roomMemberSummaryComparator: RoomMemberSummaryComparator,
|
||||
private val session: Session)
|
||||
: VectorViewModel<RoomMemberListViewState, RoomMemberListAction>(initialState) {
|
||||
: VectorViewModel<RoomMemberListViewState, RoomMemberListAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -112,11 +114,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState
|
|||
}
|
||||
|
||||
return listOf(
|
||||
PowerLevelCategory.ADMIN to admins,
|
||||
PowerLevelCategory.MODERATOR to moderators,
|
||||
PowerLevelCategory.CUSTOM to customs,
|
||||
PowerLevelCategory.INVITE to invites,
|
||||
PowerLevelCategory.USER to users
|
||||
PowerLevelCategory.ADMIN to admins.sortedWith(roomMemberSummaryComparator),
|
||||
PowerLevelCategory.MODERATOR to moderators.sortedWith(roomMemberSummaryComparator),
|
||||
PowerLevelCategory.CUSTOM to customs.sortedWith(roomMemberSummaryComparator),
|
||||
PowerLevelCategory.INVITE to invites.sortedWith(roomMemberSummaryComparator),
|
||||
PowerLevelCategory.USER to users.sortedWith(roomMemberSummaryComparator)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.members
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomMemberSummaryComparator @Inject constructor() : Comparator<RoomMemberSummary> {
|
||||
|
||||
override fun compare(leftRoomMemberSummary: RoomMemberSummary?, rightRoomMemberSummary: RoomMemberSummary?): Int {
|
||||
return when (leftRoomMemberSummary) {
|
||||
null ->
|
||||
when (rightRoomMemberSummary) {
|
||||
null -> 0
|
||||
else -> 1
|
||||
}
|
||||
else ->
|
||||
when (rightRoomMemberSummary) {
|
||||
null -> -1
|
||||
else ->
|
||||
when {
|
||||
leftRoomMemberSummary.displayName.isNullOrBlank() ->
|
||||
when {
|
||||
rightRoomMemberSummary.displayName.isNullOrBlank() -> {
|
||||
// No display names, compare ids
|
||||
leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId)
|
||||
}
|
||||
else -> 1
|
||||
}
|
||||
else ->
|
||||
when {
|
||||
rightRoomMemberSummary.displayName.isNullOrBlank() -> -1
|
||||
else -> {
|
||||
when (leftRoomMemberSummary.displayName) {
|
||||
rightRoomMemberSummary.displayName ->
|
||||
// Same display name, compare id
|
||||
leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId)
|
||||
else ->
|
||||
leftRoomMemberSummary.displayName!!.compareTo(rightRoomMemberSummary.displayName!!, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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.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 ToggleDevice(val deviceInfo: DeviceInfo) : DevicesAction()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -18,7 +18,15 @@ package im.vector.riotx.features.settings.devices
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
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.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.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
|
@ -29,7 +37,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
|||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
import im.vector.riotx.core.utils.LiveEvent
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -37,20 +44,13 @@ data class DevicesViewState(
|
|||
val myDeviceId: String = "",
|
||||
val devices: Async<List<DeviceInfo>> = Uninitialized,
|
||||
val currentExpandedDeviceId: String? = null,
|
||||
// TODO Replace by isLoading boolean
|
||||
val request: Async<Unit> = Uninitialized
|
||||
) : MvRxState
|
||||
|
||||
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 ToggleDevice(val deviceInfo: DeviceInfo) : DevicesAction()
|
||||
}
|
||||
|
||||
class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<DevicesViewState, DevicesAction>(initialState) {
|
||||
: VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -153,7 +153,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
|||
)
|
||||
}
|
||||
|
||||
_requestErrorLiveData.postLiveEvent(failure)
|
||||
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
|||
)
|
||||
}
|
||||
|
||||
_requestErrorLiveData.postLiveEvent(failure)
|
||||
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic
|
|||
)
|
||||
}
|
||||
|
||||
_requestErrorLiveData.postLiveEvent(failure)
|
||||
_viewEvents.post(DevicesViewEvents.Failure(failure))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
|||
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.extensions.observeEvent
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
|
@ -52,7 +53,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -61,16 +62,24 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
waiting_view_status_text.isVisible = true
|
||||
devicesController.callback = this
|
||||
recyclerView.configureWith(devicesController, showDivider = true)
|
||||
devicesViewModel.requestErrorLiveData.observeEvent(this) {
|
||||
displayErrorDialog(it)
|
||||
// Password is maybe not good, for safety measure, reset it here
|
||||
mAccountPassword = ""
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is DevicesViewEvents.Loading -> showLoading(it.message)
|
||||
is DevicesViewEvents.Failure -> showFailure(it.throwable)
|
||||
}.exhaustive
|
||||
}
|
||||
devicesViewModel.requestPasswordLiveData.observeEvent(this) {
|
||||
viewModel.requestPasswordLiveData.observeEvent(this) {
|
||||
maybeShowDeleteDeviceWithPasswordDialog()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
super.showFailure(throwable)
|
||||
|
||||
// Password is maybe not good, for safety measure, reset it here
|
||||
mAccountPassword = ""
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
devicesController.callback = null
|
||||
recyclerView.cleanup()
|
||||
|
@ -83,20 +92,12 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_devices_list)
|
||||
}
|
||||
|
||||
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) {
|
||||
devicesViewModel.handle(DevicesAction.ToggleDevice(deviceInfo))
|
||||
viewModel.handle(DevicesAction.ToggleDevice(deviceInfo))
|
||||
}
|
||||
|
||||
override fun onDeleteDevice(deviceInfo: DeviceInfo) {
|
||||
devicesViewModel.handle(DevicesAction.Delete(deviceInfo))
|
||||
viewModel.handle(DevicesAction.Delete(deviceInfo))
|
||||
}
|
||||
|
||||
override fun onRenameDevice(deviceInfo: DeviceInfo) {
|
||||
|
@ -104,7 +105,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun retry() {
|
||||
devicesViewModel.handle(DevicesAction.Retry)
|
||||
viewModel.handle(DevicesAction.Retry)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,7 +126,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
val newName = input.text.toString()
|
||||
|
||||
devicesViewModel.handle(DevicesAction.Rename(deviceInfo, newName))
|
||||
viewModel.handle(DevicesAction.Rename(deviceInfo, newName))
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
|
@ -136,7 +137,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
*/
|
||||
private fun maybeShowDeleteDeviceWithPasswordDialog() {
|
||||
if (mAccountPassword.isNotEmpty()) {
|
||||
devicesViewModel.handle(DevicesAction.Password(mAccountPassword))
|
||||
viewModel.handle(DevicesAction.Password(mAccountPassword))
|
||||
} else {
|
||||
val inflater = requireActivity().layoutInflater
|
||||
val layout = inflater.inflate(R.layout.dialog_device_delete, null)
|
||||
|
@ -152,7 +153,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
return@OnClickListener
|
||||
}
|
||||
mAccountPassword = passwordEditText.text.toString()
|
||||
devicesViewModel.handle(DevicesAction.Password(mAccountPassword))
|
||||
viewModel.handle(DevicesAction.Password(mAccountPassword))
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event ->
|
||||
|
@ -166,7 +167,7 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(devicesViewModel) { state ->
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
devicesController.update(state)
|
||||
|
||||
handleRequestStatus(state.request)
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -16,14 +16,21 @@
|
|||
|
||||
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.AssistedInject
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
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.VectorViewModelAction
|
||||
|
||||
|
@ -38,7 +45,7 @@ sealed class IgnoredUsersAction : VectorViewModelAction {
|
|||
|
||||
class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<IgnoredUsersViewState, IgnoredUsersAction>(initialState) {
|
||||
: VectorViewModel<IgnoredUsersViewState, IgnoredUsersAction, IgnoredUsersViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.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) {
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.airbnb.mvrx.withState
|
|||
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.observeEvent
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||
|
@ -41,7 +41,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
|
|||
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -50,8 +50,11 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor(
|
|||
waiting_view_status_text.isVisible = true
|
||||
ignoredUsersController.callback = this
|
||||
recyclerView.configureWith(ignoredUsersController)
|
||||
ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) {
|
||||
displayErrorDialog(it)
|
||||
viewModel.observeViewEvents {
|
||||
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())
|
||||
.setMessage(getString(R.string.settings_unignore_user, userId))
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
ignoredUsersViewModel.handle(IgnoredUsersAction.UnIgnore(userId))
|
||||
viewModel.handle(IgnoredUsersAction.UnIgnore(userId))
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.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
|
||||
// ==============================================================================================================
|
||||
|
||||
override fun invalidate() = withState(ignoredUsersViewModel) { state ->
|
||||
override fun invalidate() = withState(viewModel) { state ->
|
||||
ignoredUsersController.update(state)
|
||||
|
||||
handleUnIgnoreRequestStatus(state.unIgnoreRequest)
|
||||
|
|
|
@ -16,13 +16,19 @@
|
|||
|
||||
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.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||
import im.vector.matrix.rx.RxSession
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
||||
data class PushGatewayViewState(
|
||||
|
@ -31,7 +37,7 @@ data class PushGatewayViewState(
|
|||
|
||||
class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: PushGatewayViewState,
|
||||
private val session: Session)
|
||||
: VectorViewModel<PushGatewayViewState, EmptyAction>(initialState) {
|
||||
: VectorViewModel<PushGatewayViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.airbnb.mvrx.ViewModelContext
|
|||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.platform.EmptyAction
|
||||
import im.vector.riotx.core.platform.EmptyViewEvents
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
|
||||
data class PushRulesViewState(
|
||||
|
@ -28,7 +29,7 @@ data class PushRulesViewState(
|
|||
) : MvRxState
|
||||
|
||||
class PushRulesViewModel(initialState: PushRulesViewState)
|
||||
: VectorViewModel<PushRulesViewState, EmptyAction>(initialState) {
|
||||
: VectorViewModel<PushRulesViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
companion object : MvRxViewModelFactory<PushRulesViewModel, PushRulesViewState> {
|
||||
|
||||
|
|
|
@ -44,7 +44,9 @@ class IncomingShareActivity :
|
|||
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
||||
@Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory
|
||||
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?
|
||||
get() {
|
||||
return supportFragmentManager.findFragmentById(R.id.shareRoomListFragmentContainer) as? RoomListFragment
|
||||
|
|
|
@ -26,7 +26,9 @@ import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
|
|||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.ActiveSessionDataSource
|
||||
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.features.home.room.list.BreadcrumbsRoomComparator
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
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,
|
||||
private val sessionObservableStore: ActiveSessionDataSource,
|
||||
private val shareRoomListObservableStore: ShareRoomListDataSource)
|
||||
: VectorViewModel<IncomingShareState, EmptyAction>(initialState) {
|
||||
private val shareRoomListObservableStore: ShareRoomListDataSource,
|
||||
private val breadcrumbsRoomComparator: BreadcrumbsRoomComparator)
|
||||
: VectorViewModel<IncomingShareState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -68,6 +71,9 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState:
|
|||
?: Observable.just(emptyList())
|
||||
}
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS)
|
||||
.map {
|
||||
it.sortedWith(breadcrumbsRoomComparator)
|
||||
}
|
||||
.subscribe {
|
||||
shareRoomListObservableStore.post(it)
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ class SoftLogoutActivity : LoginActivity() {
|
|||
|
||||
private fun handleSoftLogoutViewEvents(softLogoutViewEvents: SoftLogoutViewEvents) {
|
||||
when (softLogoutViewEvents) {
|
||||
is SoftLogoutViewEvents.Error ->
|
||||
is SoftLogoutViewEvents.Failure ->
|
||||
showError(errorFormatter.toHumanReadable(softLogoutViewEvents.throwable))
|
||||
is SoftLogoutViewEvents.ErrorNotSameUser -> {
|
||||
// Pop the backstack
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
|
||||
package im.vector.riotx.features.signout.soft
|
||||
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
/**
|
||||
* 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 Error(val throwable: Throwable) : SoftLogoutViewEvents()
|
||||
object ClearData : SoftLogoutViewEvents()
|
||||
}
|
||||
|
|
|
@ -16,7 +16,13 @@
|
|||
|
||||
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.AssistedInject
|
||||
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.extensions.hasUnsavedKeys
|
||||
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 timber.log.Timber
|
||||
|
||||
|
@ -41,7 +45,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
private val session: Session,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val authenticationService: AuthenticationService
|
||||
) : VectorViewModel<SoftLogoutViewState, SoftLogoutAction>(initialState) {
|
||||
) : VectorViewModel<SoftLogoutViewState, SoftLogoutAction, SoftLogoutViewEvents>(initialState) {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
|
@ -71,9 +75,6 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
|
||||
private var currentTask: Cancelable? = null
|
||||
|
||||
private val _viewEvents = PublishDataSource<SoftLogoutViewEvents>()
|
||||
val viewEvents: DataSource<SoftLogoutViewEvents> = _viewEvents
|
||||
|
||||
init {
|
||||
// Get the supported login flow
|
||||
getSupportedLoginFlow()
|
||||
|
@ -192,7 +193,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
|||
currentTask = session.updateCredentials(action.credentials,
|
||||
object : MatrixCallback<Unit> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(SoftLogoutViewEvents.Error(failure))
|
||||
_viewEvents.post(SoftLogoutViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized
|
||||
|
|
|
@ -4,25 +4,26 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootConstraintLayout"
|
||||
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
|
||||
android:id="@+id/roomMemberListToolbar"
|
||||
android:id="@+id/roomSettingsToolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:elevation="4dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/roomMemberListToolbarContentView"
|
||||
android:id="@+id/roomSettingsToolbarContentView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/roomMemberListToolbarAvatarImageView"
|
||||
android:id="@+id/roomSettingsToolbarAvatarImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
|
@ -33,7 +34,7 @@
|
|||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<im.vector.riotx.core.platform.EllipsizingTextView
|
||||
android:id="@+id/roomMemberListToolbarTitleView"
|
||||
android:id="@+id/roomSettingsToolbarTitleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
|
@ -42,9 +43,9 @@
|
|||
android:maxLines="1"
|
||||
android:textColor="?vctr_toolbar_primary_text_color"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintStart_toEndOf="@+id/roomMemberListToolbarAvatarImageView"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/roomSettingsToolbarAvatarImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/matrix.json/data/roomName" />
|
||||
|
||||
|
@ -57,10 +58,12 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:overScrollMode="always"
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomMemberListToolbar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:listitem="@layout/item_autocomplete_matrix_item" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar"
|
||||
tools:listitem="@layout/item_profile_action" />
|
||||
|
||||
<include layout="@layout/merge_overlay_waiting_view" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue