Merge branch 'develop' into feature/better_long_tap_menu

This commit is contained in:
Benoit Marty 2019-07-17 14:28:36 +02:00 committed by GitHub
commit 0be987ac0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 450 additions and 216 deletions

View File

@ -2,21 +2,23 @@ Changes in RiotX 0.2.1 (2019-XX-XX)
=================================================== ===================================================
Features: Features:
- Message Editing: View edit history - Message Editing: View edit history (#121)
- Rooms filtering (#304) - Rooms filtering (#304)
Improvements: Improvements:
- Handle click on redacted events: view source and create permalink - Handle click on redacted events: view source and create permalink
- Improve long tap menu: reply on top, more compact (#368) - Improve long tap menu: reply on top, more compact (#368)
- Quick reply in timeline with swipe gesture - Quick reply in timeline with swipe gesture
- Improve edit of replies
Other changes: Other changes:
- - migrate from rxbinding 2 to rxbinding 3
Bugfix: Bugfix:
- Fix regression on permalink click - Fix regression on permalink click
- Fix crash reported by the PlayStore (#341) - Fix crash reported by the PlayStore (#341)
- Fix Chat composer separator color in dark/black theme - Fix Chat composer separator color in dark/black theme
- Fix bad layout for room directory filter (#349)
Translations: Translations:
- -

View File

@ -1,3 +1,5 @@
import javax.tools.JavaCompiler
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
@ -52,6 +54,19 @@ allprojects {
} }
} }
} }
tasks.withType(JavaCompile).all {
options.compilerArgs += [
'-Adagger.gradle.incremental=enabled'
]
}
afterEvaluate {
extensions.findByName("kapt")?.arguments {
arg("dagger.gradle.incremental", "enabled")
}
}
} }
task clean(type: Delete) { task clean(type: Delete) {

View File

@ -57,6 +57,9 @@ interface Session :
*/ */
val sessionParams: SessionParams val sessionParams: SessionParams
/**
* Useful shortcut to get access to the userId
*/
val myUserId: String val myUserId: String
get() = sessionParams.credentials.userId get() = sessionParams.credentials.userId

View File

@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.events.model
/** /**
* Constants defining known event relation types from Matrix specifications. * Constants defining known event relation types from Matrix specifications
*/ */
object RelationType { object RelationType {
@ -25,7 +25,7 @@ object RelationType {
const val ANNOTATION = "m.annotation" const val ANNOTATION = "m.annotation"
/** Lets you define an event which replaces an existing event.*/ /** Lets you define an event which replaces an existing event.*/
const val REPLACE = "m.replace" const val REPLACE = "m.replace"
/** ets you define an event which references an existing event.*/ /** Lets you define an event which references an existing event.*/
const val REFERENCE = "m.reference" const val REFERENCE = "m.reference"
} }

View File

@ -25,4 +25,9 @@ interface MessageContent {
val body: String val body: String
val relatesTo: RelationDefaultContent? val relatesTo: RelationDefaultContent?
val newContent: Content? val newContent: Content?
}
fun MessageContent?.isReply(): Boolean {
return this?.relatesTo?.inReplyTo != null
} }

View File

@ -16,7 +16,10 @@
package im.vector.matrix.android.api.session.room.model.relation package im.vector.matrix.android.api.session.room.model.relation
import im.vector.matrix.android.api.session.events.model.RelationType
interface RelationContent { interface RelationContent {
/** See [RelationType] for known possible values */
val type: String? val type: String?
val eventId: String? val eventId: String?
val inReplyTo: ReplyToContent? val inReplyTo: ReplyToContent?

View File

@ -80,6 +80,22 @@ interface RelationService {
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,
compatibilityBodyText: String = "* $newBodyText"): Cancelable compatibilityBodyText: String = "* $newBodyText"): Cancelable
/**
* Edit a reply. This is a special case because replies contains fallback text as a prefix.
* This method will take the new body (stripped from fallbacks) and re-add them before sending.
* @param replyToEdit The event to edit
* @param originalSenderId the sender of the message that this reply (being edited) is relating to
* @param originalEventId the event id that this reply (being edited) is relating to
* @param newBodyText The edited body (stripped from in reply to content)
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
*/
fun editReply(replyToEdit: TimelineEvent,
originalSenderId: String?,
originalEventId : String,
newBodyText: String,
compatibilityBodyText: String = "* $newBodyText"): Cancelable
/** /**
* Get's the edit history of the given event * Get's the edit history of the given event
*/ */

View File

@ -21,7 +21,9 @@ 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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
/** /**
* This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline. * This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline.
@ -88,3 +90,15 @@ data class TimelineEvent(
*/ */
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel() fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel() ?: root.getClearContent().toModel()
fun TimelineEvent.getTextEditableContent(): String? {
val originalContent = root.getClearContent().toModel<MessageContent>() ?: return null
val isReply = originalContent.isReply()
val lastContent = getLastMessageContent()
return if (isReply) {
return extractUsefulTextFromReply(lastContent?.body ?: "")
} else {
lastContent?.body ?: ""
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.matrix.android.api.util
object ContentUtils {
fun extractUsefulTextFromReply(repliedBody: String): String {
val lines = repliedBody.lines()
var wellFormed = repliedBody.startsWith(">")
var endOfPreviousFound = false
val usefullines = ArrayList<String>()
lines.forEach {
if (it == "") {
endOfPreviousFound = true
return@forEach
}
if (!endOfPreviousFound) {
wellFormed = wellFormed && it.startsWith(">")
} else {
usefullines.add(it)
}
}
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
}
fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
if (repliedBody.startsWith("<mx-reply>")) {
val closingTagIndex = repliedBody.lastIndexOf("</mx-reply>")
if (closingTagIndex != -1)
return repliedBody.substring(closingTagIndex + "</mx-reply>".length).trim()
}
return repliedBody
}
}

View File

@ -50,16 +50,10 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
} }
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
val userId = sessionParams.credentials.userId return sessionComponents.getOrPut(sessionParams.credentials.userId) {
if (sessionComponents.containsKey(userId)) { DaggerSessionComponent
return sessionComponents[userId]!! .factory()
.create(matrixComponent, sessionParams)
} }
return DaggerSessionComponent
.factory()
.create(matrixComponent, sessionParams)
.also {
sessionComponents[sessionParams.credentials.userId] = it
}
} }
} }

View File

@ -94,11 +94,11 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
} }
override fun requireBackgroundSync() { override fun requireBackgroundSync() {
SyncWorker.requireBackgroundSync(context, sessionParams.credentials.userId) SyncWorker.requireBackgroundSync(context, myUserId)
} }
override fun startAutomaticBackgroundSync(repeatDelay: Long) { override fun startAutomaticBackgroundSync(repeatDelay: Long) {
SyncWorker.automaticallyBackgroundSync(context, sessionParams.credentials.userId, 0, repeatDelay) SyncWorker.automaticallyBackgroundSync(context, myUserId, 0, repeatDelay)
} }
override fun stopAnyBackgroundSync() { override fun stopAnyBackgroundSync() {

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.relation.RelationService import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -132,6 +133,24 @@ internal class DefaultRelationService @Inject constructor(private val context: C
} }
override fun editReply(replyToEdit: TimelineEvent,
originalSenderId: String?,
originalEventId: String,
newBodyText: String,
compatibilityBodyText: String): Cancelable {
val event = eventFactory
.createReplaceTextOfReply(roomId,
replyToEdit,
originalSenderId, originalEventId,
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
.also {
saveLocalEcho(it)
}
val workRequest = createSendEventWork(event)
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
return CancelableWork(context, workRequest.id)
}
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) { override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
val params = FetchEditHistoryTask.Params(roomId, eventId) val params = FetchEditHistoryTask.Params(roomId, eventId)
fetchEditHistoryTask.configureWith(params) fetchEditHistoryTask.configureWith(params)

View File

@ -104,6 +104,45 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
)) ))
} }
fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent,
originalSenderId: String?,
originalEventId: String,
newBodyText: String,
newBodyAutoMarkdown: Boolean,
msgType: String,
compatibilityText: String): Event {
val permalink = PermalinkFactory.createPermalink(roomId, originalEventId)
val userLink = originalSenderId?.let { PermalinkFactory.createPermalink(it) } ?: ""
val body = bodyForReply(eventReplaced.getLastMessageContent(), eventReplaced.root.getClearContent().toModel())
val replyFormatted = REPLY_PATTERN.format(
permalink,
stringProvider.getString(R.string.message_reply_to_prefix),
userLink,
originalSenderId,
body.takeFormatted(),
createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted()
)
//
// > <@alice:example.org> This is the original body
//
val replyFallback = buildReplyFallback(body, originalSenderId, newBodyText)
return createEvent(roomId,
MessageTextContent(
type = msgType,
body = compatibilityText,
relatesTo = RelationDefaultContent(RelationType.REPLACE, eventReplaced.root.eventId),
newContent = MessageTextContent(
type = msgType,
format = MessageType.FORMAT_MATRIX_HTML,
body = replyFallback,
formattedBody = replyFormatted
)
.toContent()
))
}
fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event { fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event {
return when (attachment.type) { return when (attachment.type) {
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment) ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
@ -239,16 +278,8 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
val permalink = PermalinkFactory.createPermalink(eventReplied.root) ?: return null val permalink = PermalinkFactory.createPermalink(eventReplied.root) ?: return null
val userId = eventReplied.root.senderId ?: return null val userId = eventReplied.root.senderId ?: return null
val userLink = PermalinkFactory.createPermalink(userId) ?: return null val userLink = PermalinkFactory.createPermalink(userId) ?: return null
// <mx-reply>
// <blockquote> val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.root.getClearContent().toModel())
// <a href="https://matrix.to/#/!somewhere:domain.com/$event:domain.com">In reply to</a>
// <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a>
// <br />
// <!-- This is where the related event's HTML would be. -->
// </blockquote>
// </mx-reply>
// This is where the reply goes.
val body = bodyForReply(eventReplied.getLastMessageContent())
val replyFormatted = REPLY_PATTERN.format( val replyFormatted = REPLY_PATTERN.format(
permalink, permalink,
stringProvider.getString(R.string.message_reply_to_prefix), stringProvider.getString(R.string.message_reply_to_prefix),
@ -260,8 +291,22 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
// //
// > <@alice:example.org> This is the original body // > <@alice:example.org> This is the original body
// //
val replyFallback = buildReplyFallback(body, userId, replyText)
val eventId = eventReplied.root.eventId ?: return null
val content = MessageTextContent(
type = MessageType.MSGTYPE_TEXT,
format = MessageType.FORMAT_MATRIX_HTML,
body = replyFallback,
formattedBody = replyFormatted,
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
)
return createEvent(roomId, content)
}
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
val lines = body.text.split("\n") val lines = body.text.split("\n")
val replyFallback = StringBuffer("><$userId>") val replyFallback = StringBuffer("><$originalSenderId>")
lines.forEachIndexed { index, s -> lines.forEachIndexed { index, s ->
if (index == 0) { if (index == 0) {
replyFallback.append(" $s") replyFallback.append(" $s")
@ -269,23 +314,16 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
replyFallback.append("\n>$s") replyFallback.append("\n>$s")
} }
} }
replyFallback.append("\n\n").append(replyText) replyFallback.append("\n\n").append(newBodyText)
return replyFallback.toString()
val eventId = eventReplied.root.eventId ?: return null
val content = MessageTextContent(
type = MessageType.MSGTYPE_TEXT,
format = MessageType.FORMAT_MATRIX_HTML,
body = replyFallback.toString(),
formattedBody = replyFormatted,
relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId))
)
return createEvent(roomId, content)
} }
/** /**
* Returns a TextContent used for the fallback event representation in a reply message. * Returns a TextContent used for the fallback event representation in a reply message.
* We also pass the original content, because in case of an edit of a reply the last content is not
* himself a reply, but it will contain the fallbacks, so we have to trim them.
*/ */
private fun bodyForReply(content: MessageContent?): TextContent { private fun bodyForReply(content: MessageContent?, originalContent: MessageContent?): TextContent {
when (content?.type) { when (content?.type) {
MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_TEXT, MessageType.MSGTYPE_TEXT,
@ -296,7 +334,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
formattedText = content.formattedBody formattedText = content.formattedBody
} }
} }
val isReply = content.relatesTo?.inReplyTo?.eventId != null val isReply = content.isReply() || originalContent.isReply()
return if (isReply) return if (isReply)
TextContent(content.body, formattedText).removeInReplyFallbacks() TextContent(content.body, formattedText).removeInReplyFallbacks()
else else
@ -353,7 +391,16 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
companion object { companion object {
const val LOCAL_ID_PREFIX = "local." const val LOCAL_ID_PREFIX = "local."
// No whitespace
// <mx-reply>
// <blockquote>
// <a href="https://matrix.to/#/!somewhere:domain.com/$event:domain.com">In reply to</a>
// <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a>
// <br />
// <!-- This is where the related event's HTML would be. -->
// </blockquote>
// </mx-reply>
// No whitespace because currently breaks temporary formatted text to Span
const val REPLY_PATTERN = """<mx-reply><blockquote><a href="%s">%s</a><a href="%s">%s</a><br />%s</blockquote></mx-reply>%s""" const val REPLY_PATTERN = """<mx-reply><blockquote><a href="%s">%s</a><a href="%s">%s</a><br />%s</blockquote></mx-reply>%s"""
fun isLocalEchoId(eventId: String): Boolean = eventId.startsWith(LOCAL_ID_PREFIX) fun isLocalEchoId(eventId: String): Boolean = eventId.startsWith(LOCAL_ID_PREFIX)

View File

@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.session.room.send
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent 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.MessageType
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromHtmlReply
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
/** /**
* Contains a text and eventually a formatted text * Contains a text and eventually a formatted text
@ -47,28 +49,4 @@ fun TextContent.removeInReplyFallbacks(): TextContent {
) )
} }
private fun extractUsefulTextFromReply(repliedBody: String): String {
val lines = repliedBody.lines()
var wellFormed = repliedBody.startsWith(">")
var endOfPreviousFound = false
val usefullines = ArrayList<String>()
lines.forEach {
if (it == "") {
endOfPreviousFound = true
return@forEach
}
if (!endOfPreviousFound) {
wellFormed = wellFormed && it.startsWith(">")
} else {
usefullines.add(it)
}
}
return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody
}
private fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
if (repliedBody.startsWith("<mx-reply>")) {
return repliedBody.substring(repliedBody.lastIndexOf("</mx-reply>") + "</mx-reply>".length).trim()
}
return repliedBody
}

View File

@ -187,8 +187,9 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0' implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0'
// TODO RxBindings3 exists // RXBinding
implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0' implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha2'
implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.0.0-alpha2'
implementation("com.airbnb.android:epoxy:$epoxy_version") implementation("com.airbnb.android:epoxy:$epoxy_version")
kapt "com.airbnb.android:epoxy-processor:$epoxy_version" kapt "com.airbnb.android:epoxy-processor:$epoxy_version"

View File

@ -199,7 +199,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
if (eventType == null) { if (eventType == null) {
//Just add a generic unknown event //Just add a generic unknown event
val simpleNotifiableEvent = SimpleNotifiableEvent( val simpleNotifiableEvent = SimpleNotifiableEvent(
session.sessionParams.credentials.userId, session.myUserId,
eventId, eventId,
true, //It's an issue in this case, all event will bing even if expected to be silent. true, //It's an issue in this case, all event will bing even if expected to be silent.
title = getString(R.string.notification_unknown_new_event), title = getString(R.string.notification_unknown_new_event),
@ -238,7 +238,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
} }
notifiableEvent.isPushGatewayEvent = true notifiableEvent.isPushGatewayEvent = true
notifiableEvent.matrixID = session.sessionParams.credentials.userId notifiableEvent.matrixID = session.myUserId
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
notificationDrawerManager.refreshNotificationDrawer() notificationDrawerManager.refreshNotificationDrawer()
} }

View File

@ -57,9 +57,15 @@ import im.vector.riotx.features.workers.signout.SignOutViewModel
interface ViewModelModule { interface ViewModelModule {
/**
* ViewModels with @IntoMap will be injected by this factory
*/
@Binds @Binds
fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory
/**
* Below are bindings for the androidx view models (which extend ViewModel). Will be converted to MvRx ViewModel in the future.
*/
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(SignOutViewModel::class) @ViewModelKey(SignOutViewModel::class)
@ -110,6 +116,10 @@ interface ViewModelModule {
@ViewModelKey(ConfigurationViewModel::class) @ViewModelKey(ConfigurationViewModel::class)
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
/**
* Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future.
*/
@Binds @Binds
fun bindHomeActivityViewModelFactory(factory: HomeActivityViewModel_AssistedFactory): HomeActivityViewModel.Factory fun bindHomeActivityViewModelFactory(factory: HomeActivityViewModel_AssistedFactory): HomeActivityViewModel.Factory

View File

@ -58,10 +58,10 @@ open class UserAvatarPreference : Preference {
open fun refreshAvatar() { open fun refreshAvatar() {
val session = mSession ?: return val session = mSession ?: return
val view = mAvatarView ?: return val view = mAvatarView ?: return
session.getUser(session.sessionParams.credentials.userId)?.let { session.getUser(session.myUserId)?.let {
avatarRenderer.render(it, view) avatarRenderer.render(it, view)
} ?: run { } ?: run {
avatarRenderer.render(null, session.sessionParams.credentials.userId, null, view) avatarRenderer.render(null, session.myUserId, null, view)
} }
} }

View File

@ -21,8 +21,8 @@ import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.listeners.StepProgressListener
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.platform.WaitingViewData
import im.vector.riotx.core.ui.views.KeysBackupBanner import im.vector.riotx.core.ui.views.KeysBackupBanner
@ -57,7 +57,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() {
keysBackup.restoreKeysWithRecoveryKey(keysVersionResult, keysBackup.restoreKeysWithRecoveryKey(keysVersionResult,
recoveryKey, recoveryKey,
null, null,
session.sessionParams.credentials.userId, session.myUserId,
object : StepProgressListener { object : StepProgressListener {
override fun onStepProgress(step: StepProgressListener.Step) { override fun onStepProgress(step: StepProgressListener.Step) {
when (step) { when (step) {

View File

@ -21,8 +21,8 @@ import androidx.lifecycle.ViewModel
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.listeners.StepProgressListener
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.platform.WaitingViewData
import im.vector.riotx.core.ui.views.KeysBackupBanner import im.vector.riotx.core.ui.views.KeysBackupBanner
@ -58,7 +58,7 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel
keysBackup.restoreKeyBackupWithPassword(keysVersionResult, keysBackup.restoreKeyBackupWithPassword(keysVersionResult,
passphrase.value!!, passphrase.value!!,
null, null,
sharedViewModel.session.sessionParams.credentials.userId, sharedViewModel.session.myUserId,
object : StepProgressListener { object : StepProgressListener {
override fun onStepProgress(step: StepProgressListener.Step) { override fun onStepProgress(step: StepProgressListener.Step) {
when (step) { when (step) {

View File

@ -52,7 +52,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer) replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer)
} }
session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user -> session.observeUser(session.myUserId).observeK(this) { user ->
if (user != null) { if (user != null) {
avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
homeDrawerUsernameView.text = user.displayName homeDrawerUsernameView.text = user.displayName

View File

@ -93,7 +93,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
.rx() .rx()
.liveGroupSummaries() .liveGroupSummaries()
.map { .map {
val myUser = session.getUser(session.sessionParams.credentials.userId) val myUser = session.getUser(session.myUserId)
val allCommunityGroup = GroupSummary( val allCommunityGroup = GroupSummary(
groupId = ALL_COMMUNITIES_GROUP_ID, groupId = ALL_COMMUNITIES_GROUP_ID,
displayName = stringProvider.getString(R.string.group_all_communities), displayName = stringProvider.getString(R.string.group_all_communities),

View File

@ -62,6 +62,7 @@ import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent 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.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
@ -261,7 +262,7 @@ class RoomDetailFragment :
composerLayout.composerRelatedMessageContent.text = formattedBody composerLayout.composerRelatedMessageContent.text = formattedBody
?: nonFormattedBody ?: nonFormattedBody
composerLayout.composerEditText.setText(if (useText) nonFormattedBody else "") composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "")
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
avatarRenderer.render(event.senderAvatar, event.root.senderId avatarRenderer.render(event.senderAvatar, event.root.senderId
@ -515,7 +516,7 @@ class RoomDetailFragment :
timelineEventController.setTimeline(state.timeline, state.eventId) timelineEventController.setTimeline(state.timeline, state.eventId)
inviteView.visibility = View.GONE inviteView.visibility = View.GONE
val uid = session.sessionParams.credentials.userId val uid = session.myUserId
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid) val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView) avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)
@ -809,7 +810,7 @@ class RoomDetailFragment :
if (null != text) { if (null != text) {
// var vibrate = false // var vibrate = false
val myDisplayName = session.getUser(session.sessionParams.credentials.userId)?.displayName val myDisplayName = session.getUser(session.myUserId)?.displayName
if (TextUtils.equals(myDisplayName, text)) { if (TextUtils.equals(myDisplayName, text)) {
// current user // current user
if (TextUtils.isEmpty(composerLayout.composerEditText.text)) { if (TextUtils.isEmpty(composerLayout.composerEditText.text)) {

View File

@ -39,7 +39,6 @@ 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.TimelineEvent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotx.R
import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.intent.getFilenameFromUri
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.resources.UserPreferencesProvider
@ -52,8 +51,6 @@ import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -229,16 +226,24 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
} }
is SendMode.EDIT -> { is SendMode.EDIT -> {
val messageContent: MessageContent? =
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
val nonFormattedBody = messageContent?.body ?: ""
if (nonFormattedBody != action.text) { //is original event a reply?
room.editTextMessage(state.sendMode.timelineEvent.root.eventId val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
?: "", messageContent?.type ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) if (inReplyTo != null) {
//TODO check if same content?
room.editReply(state.sendMode.timelineEvent, room.getTimeLineEvent(inReplyTo)?.root?.senderId, inReplyTo, action.text)
} else { } else {
Timber.w("Same message content, do not send edition") val messageContent: MessageContent? =
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
val existingBody = messageContent?.body ?: ""
if (existingBody != action.text) {
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
?: "", messageContent?.type
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
} else {
Timber.w("Same message content, do not send edition")
}
} }
setState { setState {
copy( copy(
@ -347,7 +352,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun handleUndoReact(action: RoomDetailActions.UndoReaction) { private fun handleUndoReact(action: RoomDetailActions.UndoReaction) {
room.undoReaction(action.key, action.targetEventId, session.sessionParams.credentials.userId) room.undoReaction(action.key, action.targetEventId, session.myUserId)
} }
@ -355,7 +360,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
if (action.add) { if (action.add) {
room.sendReaction(action.selectedReaction, action.targetEventId) room.sendReaction(action.selectedReaction, action.targetEventId)
} else { } else {
room.undoReaction(action.selectedReaction, action.targetEventId, session.sessionParams.credentials.userId) room.undoReaction(action.selectedReaction, action.targetEventId, session.myUserId)
} }
} }

View File

@ -132,11 +132,11 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId)) this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId))
} }
if (canEdit(event, session.sessionParams.credentials.userId)) { if (canEdit(event, session.myUserId)) {
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId)) this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId))
} }
if (canRedact(event, session.sessionParams.credentials.userId)) { if (canRedact(event, session.myUserId)) {
this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId)) this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId))
} }
@ -185,7 +185,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
} }
this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId)) this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
if (session.sessionParams.credentials.userId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) {
//not sent by me //not sent by me
this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId)) this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId))
} }

View File

@ -26,6 +26,7 @@ import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.ui.list.genericFooterItem import im.vector.riotx.core.ui.list.genericFooterItem
@ -60,13 +61,13 @@ class ViewEditHistoryEpoxyController(private val context: Context,
} }
} }
is Success -> { is Success -> {
state.editList()?.let { renderEvents(it) } state.editList()?.let { renderEvents(it, state.isOriginalAReply) }
} }
} }
} }
private fun renderEvents(sourceEvents: List<Event>) { private fun renderEvents(sourceEvents: List<Event>, isOriginalReply: Boolean) {
if (sourceEvents.isEmpty()) { if (sourceEvents.isEmpty()) {
genericItem { genericItem {
id("footer") id("footer")
@ -92,7 +93,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
} }
} }
lastDate = evDate lastDate = evDate
val cContent = getCorrectContent(timelineEvent) val cContent = getCorrectContent(timelineEvent, isOriginalReply)
val body = cContent.second?.let { eventHtmlRenderer.render(it) } val body = cContent.second?.let { eventHtmlRenderer.render(it) }
?: cContent.first ?: cContent.first
@ -101,7 +102,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
var spannedDiff: Spannable? = null var spannedDiff: Spannable? = null
if (nextEvent != null && cContent.second == null /*No diff for html*/) { if (nextEvent != null && cContent.second == null /*No diff for html*/) {
//compares the body //compares the body
val nContent = getCorrectContent(nextEvent) val nContent = getCorrectContent(nextEvent, isOriginalReply)
val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) } val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) }
?: nContent.first ?: nContent.first
val dmp = diff_match_patch() val dmp = diff_match_patch()
@ -144,11 +145,14 @@ class ViewEditHistoryEpoxyController(private val context: Context,
} }
} }
private fun getCorrectContent(event: Event): Pair<String, String?> { private fun getCorrectContent(event: Event, isOriginalReply: Boolean): Pair<String, String?> {
val clearContent = event.getClearContent().toModel<MessageTextContent>() val clearContent = event.getClearContent().toModel<MessageTextContent>()
val newContent = clearContent val newContent = clearContent
?.newContent ?.newContent
?.toModel<MessageTextContent>() ?.toModel<MessageTextContent>()
if (isOriginalReply) {
return extractUsefulTextFromReply(newContent?.body ?: clearContent?.body ?: "") to null
}
return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody
?: clearContent?.formattedBody) ?: clearContent?.formattedBody)
} }

View File

@ -21,6 +21,9 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
@ -28,6 +31,7 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFor
data class ViewEditHistoryViewState( data class ViewEditHistoryViewState(
val eventId: String, val eventId: String,
val roomId: String, val roomId: String,
val isOriginalAReply: Boolean = false,
val editList: Async<List<Event>> = Uninitialized) val editList: Async<List<Event>> = Uninitialized)
: MvRxState { : MvRxState {
@ -77,11 +81,16 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
override fun onSuccess(data: List<Event>) { override fun onSuccess(data: List<Event>) {
//TODO until supported by API Add original event manually //TODO until supported by API Add original event manually
val withOriginal = data.toMutableList() val withOriginal = data.toMutableList()
var originalIsReply = false
room.getTimeLineEvent(eventId)?.let { room.getTimeLineEvent(eventId)?.let {
withOriginal.add(it.root) withOriginal.add(it.root)
originalIsReply = it.root.getClearContent().toModel<MessageContent>().isReply()
} }
setState { setState {
copy(editList = Success(withOriginal)) copy(
editList = Success(withOriginal),
isOriginalAReply = originalIsReply
)
} }
} }
}) })

View File

@ -30,10 +30,13 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.
@EpoxyAttribute @EpoxyAttribute
var listener: FilteredRoomFooterItemListener? = null var listener: FilteredRoomFooterItemListener? = null
@EpoxyAttribute
var currentFilter: String = ""
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.createRoomButton.setOnClickListener { listener?.createRoom() } holder.createRoomButton.setOnClickListener { listener?.createRoom(currentFilter) }
holder.createDirectChat.setOnClickListener { listener?.createDirectChat() } holder.createDirectChat.setOnClickListener { listener?.createDirectChat() }
holder.openRoomDirectory.setOnClickListener { listener?.openRoomDirectory() } holder.openRoomDirectory.setOnClickListener { listener?.openRoomDirectory(currentFilter) }
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
@ -43,7 +46,7 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel<FilteredRoomFooterItem.
} }
interface FilteredRoomFooterItemListener : FabMenuView.Listener { interface FilteredRoomFooterItemListener : FabMenuView.Listener {
fun createRoom() fun createRoom(initialName: String)
} }
} }

View File

@ -20,17 +20,15 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.extensions.replaceFragment
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListFragment
import im.vector.riotx.features.home.room.list.RoomListParams import im.vector.riotx.features.home.room.list.RoomListParams
import kotlinx.android.synthetic.main.activity_filtered_rooms.* import kotlinx.android.synthetic.main.activity_filtered_rooms.*
class FilteredRoomsActivity : VectorBaseActivity(), ToolbarConfigurable { class FilteredRoomsActivity : VectorBaseActivity() {
private lateinit var roomListFragment: RoomListFragment private lateinit var roomListFragment: RoomListFragment
@ -44,6 +42,9 @@ class FilteredRoomsActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
configureToolbar(filteredRoomsToolbar)
if (isFirstCreation()) { if (isFirstCreation()) {
roomListFragment = RoomListFragment.newInstance(RoomListParams(RoomListFragment.DisplayMode.FILTERED)) roomListFragment = RoomListFragment.newInstance(RoomListParams(RoomListFragment.DisplayMode.FILTERED))
replaceFragment(roomListFragment, R.id.filteredRoomsFragmentContainer, FRAGMENT_TAG) replaceFragment(roomListFragment, R.id.filteredRoomsFragmentContainer, FRAGMENT_TAG)
@ -67,10 +68,6 @@ class FilteredRoomsActivity : VectorBaseActivity(), ToolbarConfigurable {
filteredRoomsSearchView.requestFocus() filteredRoomsSearchView.requestFocus()
} }
override fun configure(toolbar: Toolbar) {
configureToolbar(toolbar)
}
companion object { companion object {
private const val FRAGMENT_TAG = "RoomListFragment" private const val FRAGMENT_TAG = "RoomListFragment"

View File

@ -112,7 +112,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
} }
// Hide FAB when list is scrolling // Hide FAB when list is scrolling
epoxyRecyclerView.addOnScrollListener( roomListEpoxyRecyclerView.addOnScrollListener(
object : RecyclerView.OnScrollListener() { object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
createChatFabMenu.removeCallbacks(showFabRunnable) createChatFabMenu.removeCallbacks(showFabRunnable)
@ -136,11 +136,14 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
} }
fun filterRoomsWith(filter: String) { fun filterRoomsWith(filter: String) {
// Scroll the list to top
roomListEpoxyRecyclerView.scrollToPosition(0)
roomListViewModel.accept(RoomListActions.FilterWith(filter)) roomListViewModel.accept(RoomListActions.FilterWith(filter))
} }
override fun openRoomDirectory() { override fun openRoomDirectory(initialFilter: String) {
navigator.openRoomDirectory(requireActivity()) navigator.openRoomDirectory(requireActivity(), initialFilter)
} }
override fun createDirectChat() { override fun createDirectChat() {
@ -150,12 +153,12 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
private fun setupRecyclerView() { private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
epoxyRecyclerView.layoutManager = layoutManager roomListEpoxyRecyclerView.layoutManager = layoutManager
epoxyRecyclerView.itemAnimator = RoomListAnimator() roomListEpoxyRecyclerView.itemAnimator = RoomListAnimator()
roomController.listener = this roomController.listener = this
roomController.addModelBuildListener { it.dispatchTo(stateRestorer) } roomController.addModelBuildListener { it.dispatchTo(stateRestorer) }
stateView.contentView = epoxyRecyclerView stateView.contentView = roomListEpoxyRecyclerView
epoxyRecyclerView.setController(roomController) roomListEpoxyRecyclerView.setController(roomController)
} }
private val showFabRunnable = Runnable { private val showFabRunnable = Runnable {
@ -266,9 +269,8 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory)) roomListViewModel.accept(RoomListActions.ToggleCategory(roomCategory))
} }
// TODO Pass title override fun createRoom(initialName: String) {
override fun createRoom() { navigator.openCreateRoom(requireActivity(), initialName)
navigator.openCreateRoom(requireActivity())
} }
} }

View File

@ -70,13 +70,14 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
viewState.rejectingRoomsIds, viewState.rejectingRoomsIds,
viewState.rejectingErrorRoomsIds) viewState.rejectingErrorRoomsIds)
addFilterFooter() addFilterFooter(viewState)
} }
private fun addFilterFooter() { private fun addFilterFooter(viewState: RoomListViewState) {
filteredRoomFooterItem { filteredRoomFooterItem {
id("filter_footer") id("filter_footer")
listener(listener) listener(listener)
currentFilter(viewState.roomFilter)
} }
} }

View File

@ -92,7 +92,7 @@ class FabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSe
interface Listener { interface Listener {
fun createDirectChat() fun createDirectChat()
fun openRoomDirectory() fun openRoomDirectory(initialFilter: String = "")
} }
} }

View File

@ -22,7 +22,7 @@ import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isVisible import androidx.core.view.isVisible
import arrow.core.Try import arrow.core.Try
import com.jakewharton.rxbinding2.widget.RxTextView import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
@ -127,9 +127,9 @@ class LoginActivity : VectorBaseActivity() {
private fun setupAuthButton() { private fun setupAuthButton() {
Observable Observable
.combineLatest( .combineLatest(
RxTextView.textChanges(loginField).map { it.trim().isNotEmpty() }, loginField.textChanges().map { it.trim().isNotEmpty() },
RxTextView.textChanges(passwordField).map { it.trim().isNotEmpty() }, passwordField.textChanges().map { it.trim().isNotEmpty() },
RxTextView.textChanges(homeServerField).map { it.trim().isNotEmpty() }, homeServerField.textChanges().map { it.trim().isNotEmpty() },
Function3<Boolean, Boolean, Boolean, Boolean> { isLoginNotEmpty, isPasswordNotEmpty, isHomeServerNotEmpty -> Function3<Boolean, Boolean, Boolean, Boolean> { isLoginNotEmpty, isPasswordNotEmpty, isHomeServerNotEmpty ->
isLoginNotEmpty && isPasswordNotEmpty && isHomeServerNotEmpty isLoginNotEmpty && isPasswordNotEmpty && isHomeServerNotEmpty
} }

View File

@ -58,13 +58,13 @@ class DefaultNavigator @Inject constructor() : Navigator {
context.startActivity(intent) context.startActivity(intent)
} }
override fun openRoomDirectory(context: Context) { override fun openRoomDirectory(context: Context, initialFilter: String) {
val intent = Intent(context, RoomDirectoryActivity::class.java) val intent = RoomDirectoryActivity.getIntent(context, initialFilter)
context.startActivity(intent) context.startActivity(intent)
} }
override fun openCreateRoom(context: Context) { override fun openCreateRoom(context: Context, initialName: String) {
val intent = CreateRoomActivity.getIntent(context) val intent = CreateRoomActivity.getIntent(context, initialName)
context.startActivity(intent) context.startActivity(intent)
} }

View File

@ -27,9 +27,9 @@ interface Navigator {
fun openRoomPreview(publicRoom: PublicRoom, context: Context) fun openRoomPreview(publicRoom: PublicRoom, context: Context)
fun openCreateRoom(context: Context) fun openCreateRoom(context: Context, initialName: String = "")
fun openRoomDirectory(context: Context) fun openRoomDirectory(context: Context, initialFilter: String = "")
fun openRoomsFiltering(context: Context) fun openRoomsFiltering(context: Context)

View File

@ -70,7 +70,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
val bodyPreview = event.type val bodyPreview = event.type
return SimpleNotifiableEvent( return SimpleNotifiableEvent(
session.sessionParams.credentials.userId, session.myUserId,
eventId = event.eventId!!, eventId = event.eventId!!,
noisy = false,//will be updated noisy = false,//will be updated
timestamp = event.originServerTs ?: System.currentTimeMillis(), timestamp = event.originServerTs ?: System.currentTimeMillis(),
@ -109,7 +109,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
roomId = event.root.roomId!!, roomId = event.root.roomId!!,
roomName = roomName) roomName = roomName)
notifiableEvent.matrixID = session.sessionParams.credentials.userId notifiableEvent.matrixID = session.myUserId
return notifiableEvent return notifiableEvent
} else { } else {
if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) { if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) {
@ -145,7 +145,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
roomName = roomName, roomName = roomName,
roomIsDirect = room.roomSummary()?.isDirect ?: false) roomIsDirect = room.roomSummary()?.isDirect ?: false)
notifiableEvent.matrixID = session.sessionParams.credentials.userId notifiableEvent.matrixID = session.myUserId
notifiableEvent.soundName = null notifiableEvent.soundName = null
// Get the avatars URL // Get the avatars URL
@ -175,7 +175,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
val body = noticeEventFormatter.format(event, dName) val body = noticeEventFormatter.format(event, dName)
?: stringProvider.getString(R.string.notification_new_invitation) ?: stringProvider.getString(R.string.notification_new_invitation)
return InviteNotifiableEvent( return InviteNotifiableEvent(
session.sessionParams.credentials.userId, session.myUserId,
eventId = event.eventId!!, eventId = event.eventId!!,
roomId = roomId, roomId = roomId,
timestamp = event.originServerTs ?: 0, timestamp = event.originServerTs ?: 0,

View File

@ -121,9 +121,9 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
false, false,
System.currentTimeMillis(), System.currentTimeMillis(),
session.getUser(session.sessionParams.credentials.userId)?.displayName session.getUser(session.myUserId)?.displayName
?: context?.getString(R.string.notification_sender_me), ?: context?.getString(R.string.notification_sender_me),
session.sessionParams.credentials.userId, session.myUserId,
message, message,
room.roomId, room.roomId,
room.roomSummary()?.displayName ?: room.roomId, room.roomSummary()?.displayName ?: room.roomId,

View File

@ -181,8 +181,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
val user = session.getUser(session.sessionParams.credentials.userId) val user = session.getUser(session.myUserId)
val myUserDisplayName = user?.displayName ?: session.sessionParams.credentials.userId // myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash
val myUserDisplayName = user?.displayName?.takeIf { it.isNotBlank() } ?: session.myUserId
val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE) val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE)
synchronized(eventList) { synchronized(eventList) {
@ -343,7 +344,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
for (event in simpleEvents) { for (event in simpleEvents) {
//We build a simple event //We build a simple event
if (firstTime || !event.hasBeenDisplayed) { if (firstTime || !event.hasBeenDisplayed) {
NotificationUtils.buildSimpleEventNotification(context, event, null, myUserDisplayName)?.let { NotificationUtils.buildSimpleEventNotification(context, event, null, session.myUserId)?.let {
notifications.add(it) notifications.add(it)
NotificationUtils.showNotificationMessage(context, event.eventId, ROOM_EVENT_NOTIFICATION_ID, it) NotificationUtils.showNotificationMessage(context, event.eventId, ROOM_EVENT_NOTIFICATION_ID, it)
event.hasBeenDisplayed = true //we can consider it as displayed event.hasBeenDisplayed = true //we can consider it as displayed

View File

@ -204,7 +204,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
var olmVersion = "undefined" var olmVersion = "undefined"
activeSessionHolder.getSafeActiveSession()?.let { session -> activeSessionHolder.getSafeActiveSession()?.let { session ->
userId = session.sessionParams.credentials.userId userId = session.myUserId
deviceId = session.sessionParams.credentials.deviceId ?: "undefined" deviceId = session.sessionParams.credentials.deviceId ?: "undefined"
olmVersion = session.getCryptoVersion(context, true) olmVersion = session.getCryptoVersion(context, true)
} }

View File

@ -25,14 +25,13 @@ import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding2.widget.RxTextView import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.themes.ThemeUtils
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import kotlinx.android.synthetic.main.fragment_public_rooms.* import kotlinx.android.synthetic.main.fragment_public_rooms.*
import timber.log.Timber import timber.log.Timber
@ -70,9 +69,7 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback
it.setDisplayHomeAsUpEnabled(true) it.setDisplayHomeAsUpEnabled(true)
} }
publicRoomsFilter.setBackgroundResource(ThemeUtils.getResourceId(requireContext(), R.drawable.bg_search_edit_text_light)) publicRoomsFilter.queryTextChanges()
RxTextView.textChanges(publicRoomsFilter)
.debounce(500, TimeUnit.MILLISECONDS) .debounce(500, TimeUnit.MILLISECONDS)
.subscribeBy { .subscribeBy {
viewModel.filterWith(it.toString()) viewModel.filterWith(it.toString())
@ -147,6 +144,11 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback
} }
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
if (publicRoomsFilter.text.toString() != state.currentFilter) {
// For initial filter
publicRoomsFilter.setText(state.currentFilter)
}
// Populate list with Epoxy // Populate list with Epoxy
publicRoomsController.setData(state) publicRoomsController.setData(state)
} }

View File

@ -22,6 +22,8 @@ import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
data class PublicRoomsViewState( data class PublicRoomsViewState(
// The current filter
val currentFilter: String = "",
// Store cumul of pagination result // Store cumul of pagination result
val publicRooms: List<PublicRoom> = emptyList(), val publicRooms: List<PublicRoom> = emptyList(),
// Current pagination request // Current pagination request

View File

@ -16,8 +16,11 @@
package im.vector.riotx.features.roomdirectory package im.vector.riotx.features.roomdirectory
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.viewModel
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragment
@ -25,6 +28,7 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
import javax.inject.Inject import javax.inject.Inject
@ -39,7 +43,10 @@ class RoomDirectoryActivity : VectorBaseActivity() {
} }
@Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory
@Inject lateinit var roomDirectoryViewModelFactory: RoomDirectoryViewModel.Factory @Inject lateinit var roomDirectoryViewModelFactory: RoomDirectoryViewModel.Factory
private val roomDirectoryViewModel: RoomDirectoryViewModel by viewModel()
private val createRoomViewModel: CreateRoomViewModel by viewModel()
private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel
override fun getLayoutRes() = R.layout.activity_simple override fun getLayoutRes() = R.layout.activity_simple
@ -51,6 +58,11 @@ class RoomDirectoryActivity : VectorBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java) navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java)
if (isFirstCreation()) {
roomDirectoryViewModel.filterWith(intent?.getStringExtra(INITIAL_FILTER) ?: "")
}
navigationViewModel.navigateTo.observeEvent(this) { navigation -> navigationViewModel.navigateTo.observeEvent(this) { navigation ->
when (navigation) { when (navigation) {
is Navigation.Back -> onBackPressed() is Navigation.Back -> onBackPressed()
@ -59,6 +71,11 @@ class RoomDirectoryActivity : VectorBaseActivity() {
is Navigation.Close -> finish() is Navigation.Close -> finish()
} }
} }
roomDirectoryViewModel.selectSubscribe(this, PublicRoomsViewState::currentFilter) { currentFilter ->
// Transmit the filter to the createRoomViewModel
createRoomViewModel.setName(currentFilter)
}
} }
override fun initUiAndData() { override fun initUiAndData() {
@ -67,4 +84,13 @@ class RoomDirectoryActivity : VectorBaseActivity() {
} }
} }
companion object {
private const val INITIAL_FILTER = "INITIAL_FILTER"
fun getIntent(context: Context, initialFilter: String = ""): Intent {
val intent = Intent(context, RoomDirectoryActivity::class.java)
intent.putExtra(INITIAL_FILTER, initialFilter)
return intent
}
}
} }

View File

@ -59,9 +59,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
get() = _joinRoomErrorLiveData get() = _joinRoomErrorLiveData
// TODO Store in ViewState?
private var currentFilter: String = ""
private var since: String? = null private var since: String? = null
private var currentTask: Cancelable? = null private var currentTask: Cancelable? = null
@ -70,9 +67,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
private var roomDirectoryData = RoomDirectoryData() private var roomDirectoryData = RoomDirectoryData()
init { init {
// Load with empty filter
load()
setState { setState {
copy( copy(
roomDirectoryDisplayName = roomDirectoryData.displayName roomDirectoryDisplayName = roomDirectoryData.displayName
@ -115,24 +109,20 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
this.roomDirectoryData = roomDirectoryData this.roomDirectoryData = roomDirectoryData
reset() reset("")
load() load("")
} }
fun filterWith(filter: String) { fun filterWith(filter: String) = withState { state ->
if (currentFilter == filter) { if (state.currentFilter != filter) {
return currentTask?.cancel()
reset(filter)
load(filter)
} }
currentTask?.cancel()
currentFilter = filter
reset()
load()
} }
private fun reset() { private fun reset(newFilter: String) {
// Reset since token // Reset since token
since = null since = null
@ -141,12 +131,13 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
publicRooms = emptyList(), publicRooms = emptyList(),
asyncPublicRoomsRequest = Loading(), asyncPublicRoomsRequest = Loading(),
hasMore = false, hasMore = false,
roomDirectoryDisplayName = roomDirectoryData.displayName roomDirectoryDisplayName = roomDirectoryData.displayName,
currentFilter = newFilter
) )
} }
} }
fun loadMore() { fun loadMore() = withState { state ->
if (currentTask == null) { if (currentTask == null) {
setState { setState {
copy( copy(
@ -154,15 +145,15 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
) )
} }
load() load(state.currentFilter)
} }
} }
private fun load() { private fun load(filter: String) {
currentTask = session.getPublicRooms(roomDirectoryData.homeServer, currentTask = session.getPublicRooms(roomDirectoryData.homeServer,
PublicRoomsParams( PublicRoomsParams(
limit = PUBLIC_ROOMS_LIMIT, limit = PUBLIC_ROOMS_LIMIT,
filter = PublicRoomsFilter(searchTerm = currentFilter), filter = PublicRoomsFilter(searchTerm = filter),
includeAllNetworks = roomDirectoryData.includeAllNetworks, includeAllNetworks = roomDirectoryData.includeAllNetworks,
since = since, since = since,
thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId

View File

@ -21,6 +21,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.viewModel
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragment
@ -29,12 +30,16 @@ import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel
import javax.inject.Inject
/** /**
* Simple container for [CreateRoomFragment] * Simple container for [CreateRoomFragment]
*/ */
class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable {
@Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory
private val createRoomViewModel: CreateRoomViewModel by viewModel()
private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel
override fun getLayoutRes() = R.layout.activity_simple override fun getLayoutRes() = R.layout.activity_simple
@ -46,6 +51,8 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun initUiAndData() { override fun initUiAndData() {
if (isFirstCreation()) { if (isFirstCreation()) {
addFragment(CreateRoomFragment(), R.id.simpleFragmentContainer) addFragment(CreateRoomFragment(), R.id.simpleFragmentContainer)
createRoomViewModel.setName(intent?.getStringExtra(INITIAL_NAME) ?: "")
} }
} }
@ -58,14 +65,19 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable {
navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java) navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java)
navigationViewModel.navigateTo.observeEvent(this) { navigation -> navigationViewModel.navigateTo.observeEvent(this) { navigation ->
when (navigation) { when (navigation) {
is RoomDirectoryActivity.Navigation.Back -> finish() is RoomDirectoryActivity.Navigation.Back,
is RoomDirectoryActivity.Navigation.Close -> finish()
} }
} }
} }
companion object { companion object {
fun getIntent(context: Context): Intent { private const val INITIAL_NAME = "INITIAL_NAME"
return Intent(context, CreateRoomActivity::class.java)
fun getIntent(context: Context, initialName: String = ""): Intent {
return Intent(context, CreateRoomActivity::class.java).apply {
putExtra(INITIAL_NAME, initialName)
}
} }
} }

View File

@ -21,7 +21,7 @@ import android.view.MenuItem
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
@ -35,9 +35,8 @@ import javax.inject.Inject
class CreateRoomFragment : VectorBaseFragment(), CreateRoomController.Listener { class CreateRoomFragment : VectorBaseFragment(), CreateRoomController.Listener {
private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel
private val viewModel: CreateRoomViewModel by fragmentViewModel() private val viewModel: CreateRoomViewModel by activityViewModel()
@Inject lateinit var createRoomController: CreateRoomController @Inject lateinit var createRoomController: CreateRoomController
@Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory
override fun getLayoutResId() = R.layout.fragment_create_room override fun getLayoutResId() = R.layout.fragment_create_room

View File

@ -16,6 +16,7 @@
package im.vector.riotx.features.roomdirectory.createroom package im.vector.riotx.features.roomdirectory.createroom
import androidx.fragment.app.FragmentActivity
import com.airbnb.mvrx.* import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
@ -25,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState, class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState,
private val session: Session private val session: Session
@ -39,8 +41,13 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: CreateRoomViewState): CreateRoomViewModel? { override fun create(viewModelContext: ViewModelContext, state: CreateRoomViewState): CreateRoomViewModel? {
val fragment: CreateRoomFragment = (viewModelContext as FragmentViewModelContext).fragment() val activity: FragmentActivity = (viewModelContext as ActivityViewModelContext).activity()
return fragment.createRoomViewModelFactory.create(state)
return when (activity) {
is CreateRoomActivity -> activity.createRoomViewModelFactory.create(state)
is RoomDirectoryActivity -> activity.createRoomViewModelFactory.create(state)
else -> throw IllegalStateException("Wrong activity")
}
} }
} }

View File

@ -79,9 +79,9 @@ class VectorSettingsActivity : VectorBaseActivity(),
var oFragment: Fragment? = null var oFragment: Fragment? = null
if (VectorPreferences.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) { if (VectorPreferences.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId) oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.myUserId)
} else if (VectorPreferences.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) { } else if (VectorPreferences.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) {
oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.sessionParams.credentials.userId) oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.myUserId)
} else { } else {
try { try {
pref?.fragment?.let { pref?.fragment?.let {

View File

@ -95,7 +95,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
// Display name // Display name
mDisplayNamePreference.let { mDisplayNamePreference.let {
it.summary = session.getUser(session.sessionParams.credentials.userId)?.displayName ?: "" it.summary = session.getUser(session.myUserId)?.displayName ?: ""
it.text = it.summary.toString() it.text = it.summary.toString()
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
onDisplayNameClick(newValue?.let { (it as String).trim() }) onDisplayNameClick(newValue?.let { (it as String).trim() })
@ -148,7 +148,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
// user account // user account
findPreference(VectorPreferences.SETTINGS_LOGGED_IN_PREFERENCE_KEY) findPreference(VectorPreferences.SETTINGS_LOGGED_IN_PREFERENCE_KEY)
.summary = session.sessionParams.credentials.userId .summary = session.myUserId
// home server // home server
findPreference(VectorPreferences.SETTINGS_HOME_SERVER_PREFERENCE_KEY) findPreference(VectorPreferences.SETTINGS_HOME_SERVER_PREFERENCE_KEY)

View File

@ -367,7 +367,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
* @param aMyDeviceInfo the device info * @param aMyDeviceInfo the device info
*/ */
private fun refreshCryptographyPreference(aMyDeviceInfo: DeviceInfo?) { private fun refreshCryptographyPreference(aMyDeviceInfo: DeviceInfo?) {
val userId = session.sessionParams.credentials.userId val userId = session.myUserId
val deviceId = session.sessionParams.credentials.deviceId val deviceId = session.sessionParams.credentials.deviceId
// device name // device name

View File

@ -35,7 +35,7 @@ class SignOutUiWorker(private val activity: FragmentActivity) {
activeSessionHolder = context.vectorComponent().activeSessionHolder() activeSessionHolder = context.vectorComponent().activeSessionHolder()
val session = activeSessionHolder.getActiveSession() val session = activeSessionHolder.getActiveSession()
if (SignOutViewModel.doYouNeedToBeDisplayed(session)) { if (SignOutViewModel.doYouNeedToBeDisplayed(session)) {
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance(session.sessionParams.credentials.userId) val signOutDialog = SignOutBottomSheetDialogFragment.newInstance(session.myUserId)
signOutDialog.onSignOut = Runnable { signOutDialog.onSignOut = Runnable {
doSignOut() doSignOut()
} }

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M9,9m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#03B381"
android:strokeLineCap="round"/>
<path
android:pathData="M19,19l-4.35,-4.35"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:fillType="evenOdd"
android:strokeColor="#03B381"
android:strokeLineCap="round"/>
</vector>

View File

@ -14,17 +14,17 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:elevation="4dp" android:elevation="4dp"
app:contentInsetStart="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.SearchView <androidx.appcompat.widget.SearchView
android:id="@+id/filteredRoomsSearchView" android:id="@+id/filteredRoomsSearchView"
style="@style/VectorSearchView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:closeIcon="@drawable/ic_x_green" app:queryHint="@string/room_filtering_filter_hint"
app:iconifiedByDefault="false"
app:queryHint="@string/home_filter_placeholder_home"
app:searchIcon="@drawable/ic_filter" /> app:searchIcon="@drawable/ic_filter" />
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>

View File

@ -11,10 +11,10 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/createRoomToolbar" android:id="@+id/createRoomToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
android:elevation="4dp" android:elevation="4dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">

View File

@ -8,10 +8,10 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/groupToolbar" android:id="@+id/groupToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:elevation="4dp" android:elevation="4dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">

View File

@ -23,36 +23,22 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/publicRoomsToolbar" android:id="@+id/publicRoomsToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:elevation="4dp" android:elevation="4dp"
android:minHeight="0dp" android:minHeight="0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"> app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways">
<!-- Note: Background is modified in the code for other themes --> <androidx.appcompat.widget.SearchView
<EditText
android:id="@+id/publicRoomsFilter" android:id="@+id/publicRoomsFilter"
style="@style/VectorSearchView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="wrap_content"
android:layout_marginStart="8dp" app:queryHint="@string/room_directory_search_hint" />
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:layout_marginRight="@dimen/layout_horizontal_margin"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_search_edit_text_light"
android:drawableStart="@drawable/ic_search_white"
android:drawableLeft="@drawable/ic_search_white"
android:drawablePadding="8dp"
android:drawableTint="?riotx_text_secondary"
android:hint="@string/home_filter_placeholder_rooms"
android:lines="1"
android:paddingLeft="8dp"
android:paddingRight="8dp" />
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>

View File

@ -8,10 +8,10 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/roomToolbar" android:id="@+id/roomToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
android:elevation="4dp" android:elevation="4dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">

View File

@ -8,7 +8,7 @@
android:background="?riotx_header_panel_background"> android:background="?riotx_header_panel_background">
<com.airbnb.epoxy.EpoxyRecyclerView <com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/epoxyRecyclerView" android:id="@+id/roomListEpoxyRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View File

@ -13,10 +13,10 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/roomPreviewNoPreviewToolbar" android:id="@+id/roomPreviewNoPreviewToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
android:elevation="4dp" android:elevation="4dp">
app:contentInsetStartWithNavigation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -26,11 +26,14 @@
<string name="no_message_edits_found">No edits found</string> <string name="no_message_edits_found">No edits found</string>
<!-- Room filtering --> <!-- Room filtering -->
<string name="room_filtering_filter_hint">Filter conversations…</string>
<string name="room_filtering_footer_title">Cant find what youre looking for?</string> <string name="room_filtering_footer_title">Cant find what youre looking for?</string>
<string name="room_filtering_footer_create_new_room">Create a new room</string> <string name="room_filtering_footer_create_new_room">Create a new room</string>
<string name="room_filtering_footer_create_new_direct_message">Send a new direct message</string> <string name="room_filtering_footer_create_new_direct_message">Send a new direct message</string>
<string name="room_filtering_footer_open_room_directory">View the room directory</string> <string name="room_filtering_footer_open_room_directory">View the room directory</string>
<string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string> <string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
</resources> </resources>

View File

@ -9,6 +9,7 @@
<item name="titleTextAppearance">@style/Vector.Toolbar.Title</item> <item name="titleTextAppearance">@style/Vector.Toolbar.Title</item>
<item name="subtitleTextAppearance">@style/Vector.Toolbar.SubTitle</item> <item name="subtitleTextAppearance">@style/Vector.Toolbar.SubTitle</item>
<item name="android:background">?riotx_background</item> <item name="android:background">?riotx_background</item>
<item name="contentInsetStartWithNavigation">0dp</item>
</style> </style>
<style name="VectorToolbarStyle.Group"> <style name="VectorToolbarStyle.Group">
@ -160,6 +161,12 @@
<item name="colorControlHighlight">@android:color/white</item> <item name="colorControlHighlight">@android:color/white</item>
</style> </style>
<style name="VectorSearchView" parent="Widget.AppCompat.SearchView">
<item name="searchIcon">@drawable/ic_search</item>
<item name="closeIcon">@drawable/ic_x_green</item>
<item name="iconifiedByDefault">false</item>
</style>
<style name="VectorSearches.EditText" parent="Widget.AppCompat.EditText"> <style name="VectorSearches.EditText" parent="Widget.AppCompat.EditText">
<item name="android:textCursorDrawable">@drawable/searches_cursor_background</item> <item name="android:textCursorDrawable">@drawable/searches_cursor_background</item>
<item name="android:background">@android:color/transparent</item> <item name="android:background">@android:color/transparent</item>