Merge branch 'develop' into feature/aris/thread_labs_notice_users

# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
#	vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
This commit is contained in:
ariskotsomitopoulos 2022-03-18 11:28:21 +01:00
commit a0e6dd5f6c
26 changed files with 159 additions and 50 deletions

1
changelog.d/5378.misc Normal file
View File

@ -0,0 +1 @@
Add analytics support for threads

1
changelog.d/5540.bugfix Normal file
View File

@ -0,0 +1 @@
Fixes crash when tapping the timeline verification surround box instead of the buttons

1
changelog.d/5547.bugfix Normal file
View File

@ -0,0 +1 @@
[Notification mode] Wrong mode is displayed when the mention only is selected on the web client

1
changelog.d/5563.misc Normal file
View File

@ -0,0 +1 @@
Add a presence sync enabling build config

View File

@ -61,6 +61,10 @@ data class MatrixConfiguration(
* RoomDisplayNameFallbackProvider to provide default room display name. * RoomDisplayNameFallbackProvider to provide default room display name.
*/ */
val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider, val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider,
/**
* True to enable presence information sync (if available). False to disable regardless of server setting.
*/
val presenceSyncEnabled: Boolean = true,
/** /**
* Thread messages default enable/disabled value * Thread messages default enable/disabled value
*/ */

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room.timeline package org.matrix.android.sdk.api.session.room.timeline
import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.RelationType
@ -159,6 +160,13 @@ fun TimelineEvent.isSticker(): Boolean {
return root.isSticker() return root.isSticker()
} }
/**
* Returns whether or not the event is a root thread event
*/
fun TimelineEvent.isRootThread(): Boolean {
return root.threadDetails?.isRootThread.orFalse()
}
/** /**
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary * Get the latest message body, after a possible edition, stripping the reply prefix if necessary
*/ */

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.sync.handler package org.matrix.android.sdk.internal.session.sync.handler
import io.realm.Realm import io.realm.Realm
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.getPresenceContent import org.matrix.android.sdk.api.session.events.model.getPresenceContent
import org.matrix.android.sdk.api.session.sync.model.PresenceSyncResponse import org.matrix.android.sdk.api.session.sync.model.PresenceSyncResponse
@ -27,27 +28,29 @@ import org.matrix.android.sdk.internal.database.query.updateDirectUserPresence
import org.matrix.android.sdk.internal.database.query.updateUserPresence import org.matrix.android.sdk.internal.database.query.updateUserPresence
import javax.inject.Inject import javax.inject.Inject
internal class PresenceSyncHandler @Inject constructor() { internal class PresenceSyncHandler @Inject constructor(private val matrixConfiguration: MatrixConfiguration) {
fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) { fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) {
presenceSyncResponse?.events if (matrixConfiguration.presenceSyncEnabled) {
?.filter { event -> event.type == EventType.PRESENCE } presenceSyncResponse?.events
?.forEach { event -> ?.filter { event -> event.type == EventType.PRESENCE }
val content = event.getPresenceContent() ?: return@forEach ?.forEach { event ->
val userId = event.senderId ?: return@forEach val content = event.getPresenceContent() ?: return@forEach
val userPresenceEntity = UserPresenceEntity( val userId = event.senderId ?: return@forEach
userId = userId, val userPresenceEntity = UserPresenceEntity(
lastActiveAgo = content.lastActiveAgo, userId = userId,
statusMessage = content.statusMessage, lastActiveAgo = content.lastActiveAgo,
isCurrentlyActive = content.isCurrentlyActive, statusMessage = content.statusMessage,
avatarUrl = content.avatarUrl, isCurrentlyActive = content.isCurrentlyActive,
displayName = content.displayName avatarUrl = content.avatarUrl,
).also { displayName = content.displayName
it.presence = content.presence ).also {
} it.presence = content.presence
}
storePresenceToDB(realm, userPresenceEntity) storePresenceToDB(realm, userPresenceEntity)
} }
}
} }
/** /**

View File

@ -151,6 +151,7 @@ android {
buildConfigField "Boolean", "enableLocationSharing", "true" buildConfigField "Boolean", "enableLocationSharing", "true"
buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\"" buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\""
buildConfigField "Boolean", "PRESENCE_SYNC_ENABLED", "true"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@ -120,7 +120,8 @@ object VectorStaticModule {
return MatrixConfiguration( return MatrixConfiguration(
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION, applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider, roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled() threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
presenceSyncEnabled = BuildConfig.PRESENCE_SYNC_ENABLED
) )
} }

View File

@ -25,10 +25,11 @@ import org.matrix.android.sdk.api.session.presence.model.UserPresence
@EpoxyModelClass(layout = R.layout.item_profile_matrix_item) @EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
abstract class ProfileMatrixItemWithPowerLevelWithPresence : ProfileMatrixItemWithPowerLevel() { abstract class ProfileMatrixItemWithPowerLevelWithPresence : ProfileMatrixItemWithPowerLevel() {
@EpoxyAttribute var showPresence: Boolean = true
@EpoxyAttribute var userPresence: UserPresence? = null @EpoxyAttribute var userPresence: UserPresence? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.presenceImageView.render(userPresence = userPresence) holder.presenceImageView.render(showPresence, userPresence)
} }
} }

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.analytics.extensions
import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.home.room.detail.composer.MessageComposerViewState
import im.vector.app.features.home.room.detail.composer.SendMode
fun MessageComposerViewState.toAnalyticsComposer(): Composer =
Composer(
inThread = isInThreadTimeline(),
isEditing = sendMode is SendMode.Edit,
isReply = sendMode is SendMode.Reply,
startsThread = startsThread
)

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.analytics.extensions
import im.vector.app.features.analytics.plan.Interaction
fun Interaction.Name.toAnalyticsInteraction(interactionType: Interaction.InteractionType = Interaction.InteractionType.Touch) =
Interaction(
name = this,
interactionType = interactionType
)

View File

@ -118,7 +118,8 @@ import im.vector.app.core.utils.startInstallFromSourceIntent
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.DialogReportContentBinding import im.vector.app.databinding.DialogReportContentBinding
import im.vector.app.databinding.FragmentTimelineBinding import im.vector.app.databinding.FragmentTimelineBinding
import im.vector.app.features.analytics.plan.Composer import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.attachments.AttachmentTypeSelectorView import im.vector.app.features.attachments.AttachmentTypeSelectorView
import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.AttachmentsHelper
@ -205,6 +206,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
@ -259,7 +261,8 @@ class TimelineFragment @Inject constructor(
private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
private val callManager: WebRtcCallManager, private val callManager: WebRtcCallManager,
private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker, private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
private val clock: Clock private val clock: Clock,
private val matrixConfiguration: MatrixConfiguration
) : ) :
VectorBaseFragment<FragmentTimelineBinding>(), VectorBaseFragment<FragmentTimelineBinding>(),
TimelineEventController.Callback, TimelineEventController.Callback,
@ -1491,9 +1494,6 @@ class TimelineFragment @Inject constructor(
return return
} }
if (text.isNotBlank()) { if (text.isNotBlank()) {
withState(messageComposerViewModel) { state ->
analyticsTracker.capture(Composer(isThreadTimeLine(), isEditing = state.sendMode is SendMode.Edit, isReply = state.sendMode is SendMode.Reply))
}
// We collapse ASAP, if not there will be a slight annoying delay // We collapse ASAP, if not there will be a slight annoying delay
views.composerLayout.collapse(true) views.composerLayout.collapse(true)
lockSendButton = true lockSendButton = true
@ -1613,7 +1613,10 @@ class TimelineFragment @Inject constructor(
views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName
avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView) avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView)
views.includeRoomToolbar.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel) views.includeRoomToolbar.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel)
views.includeRoomToolbar.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence) views.includeRoomToolbar.roomToolbarPresenceImageView.render(
roomSummary.isDirect && matrixConfiguration.presenceSyncEnabled,
roomSummary.directUserPresence
)
views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect
} }
} else { } else {
@ -1773,13 +1776,11 @@ class TimelineFragment @Inject constructor(
} }
is RoomDetailAction.ResumeVerification -> { is RoomDetailAction.ResumeVerification -> {
val otherUserId = data.otherUserId ?: return val otherUserId = data.otherUserId ?: return
VerificationBottomSheet().apply { VerificationBottomSheet.withArgs(
setArguments(VerificationBottomSheet.VerificationArgs( roomId = timelineArgs.roomId,
otherUserId = otherUserId, otherUserId = otherUserId,
verificationId = data.transactionId, transactionId = data.transactionId,
roomId = timelineArgs.roomId ).show(parentFragmentManager, "REQ")
))
}.show(parentFragmentManager, "REQ")
} }
} }
} }
@ -2174,7 +2175,7 @@ class TimelineFragment @Inject constructor(
} }
is EventSharedAction.ReplyInThread -> { is EventSharedAction.ReplyInThread -> {
if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
navigateToThreadTimeline(action.eventId) navigateToThreadTimeline(action.eventId, action.startsThread)
} else { } else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
} }
@ -2333,9 +2334,11 @@ class TimelineFragment @Inject constructor(
* using the ThreadsActivity * using the ThreadsActivity
*/ */
private fun navigateToThreadTimeline(rootThreadEventId: String) { private fun navigateToThreadTimeline(rootThreadEventId: String, startsThread: Boolean = false) {
analyticsTracker.capture(Interaction.Name.MobileRoomThreadSummaryItem.toAnalyticsInteraction())
context?.let { context?.let {
val roomThreadDetailArgs = ThreadTimelineArgs( val roomThreadDetailArgs = ThreadTimelineArgs(
startsThread = startsThread,
roomId = timelineArgs.roomId, roomId = timelineArgs.roomId,
displayName = timelineViewModel.getRoomSummary()?.displayName, displayName = timelineViewModel.getRoomSummary()?.displayName,
avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl, avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl,
@ -2351,6 +2354,7 @@ class TimelineFragment @Inject constructor(
*/ */
private fun navigateToThreadList() { private fun navigateToThreadList() {
analyticsTracker.capture(Interaction.Name.MobileRoomThreadListButton.toAnalyticsInteraction())
context?.let { context?.let {
val roomThreadDetailArgs = ThreadTimelineArgs( val roomThreadDetailArgs = ThreadTimelineArgs(
roomId = timelineArgs.roomId, roomId = timelineArgs.roomId,

View File

@ -27,6 +27,7 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsComposer
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.command.CommandParser import im.vector.app.features.command.CommandParser
@ -188,6 +189,9 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleSendMessage(action: MessageComposerAction.SendMessage) { private fun handleSendMessage(action: MessageComposerAction.SendMessage) {
withState { state -> withState { state ->
analyticsTracker.capture(state.toAnalyticsComposer()).also {
setState { copy(startsThread = false) }
}
when (state.sendMode) { when (state.sendMode) {
is SendMode.Regular -> { is SendMode.Regular -> {
when (val slashCommandResult = commandParser.parseSlashCommand( when (val slashCommandResult = commandParser.parseSlashCommand(

View File

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
/** /**
@ -62,6 +63,7 @@ data class MessageComposerViewState(
val canSendMessage: CanSendStatus = CanSendStatus.Allowed, val canSendMessage: CanSendStatus = CanSendStatus.Allowed,
val isSendButtonVisible: Boolean = false, val isSendButtonVisible: Boolean = false,
val rootThreadEventId: String? = null, val rootThreadEventId: String? = null,
val startsThread: Boolean = false,
val sendMode: SendMode = SendMode.Regular("", false), val sendMode: SendMode = SendMode.Regular("", false),
val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle
) : MavericksState { ) : MavericksState {
@ -80,6 +82,7 @@ data class MessageComposerViewState(
constructor(args: TimelineArgs) : this( constructor(args: TimelineArgs) : this(
roomId = args.roomId, roomId = args.roomId,
startsThread = args.threadTimelineArgs?.startsThread.orFalse(),
rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId) rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId)
fun isInThreadTimeline(): Boolean = rootThreadEventId != null fun isInThreadTimeline(): Boolean = rootThreadEventId != null

View File

@ -48,7 +48,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
data class Reply(val eventId: String) : data class Reply(val eventId: String) :
EventSharedAction(R.string.reply, R.drawable.ic_reply) EventSharedAction(R.string.reply, R.drawable.ic_reply)
data class ReplyInThread(val eventId: String) : data class ReplyInThread(val eventId: String, val startsThread: Boolean) :
EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread) EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread)
object ViewInRoom : object ViewInRoom :

View File

@ -61,6 +61,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
import org.matrix.android.sdk.api.session.room.timeline.isPoll import org.matrix.android.sdk.api.session.room.timeline.isPoll
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
import org.matrix.android.sdk.api.session.room.timeline.isSticker import org.matrix.android.sdk.api.session.room.timeline.isSticker
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.flow.unwrap
@ -329,7 +330,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
} }
if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) { if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) {
add(EventSharedAction.ReplyInThread(eventId)) add(EventSharedAction.ReplyInThread(eventId, !timelineEvent.isRootThread()))
} }
if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) { if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) {

View File

@ -155,7 +155,7 @@ class MessageItemFactory @Inject constructor(
if (event.root.isRedacted()) { if (event.root.isRedacted()) {
// message is redacted // message is redacted
val attributes = messageItemAttributesFactory.create(null, informationData, callback, params.reactionsSummaryEvents) val attributes = messageItemAttributesFactory.create(null, informationData, callback, params.reactionsSummaryEvents, threadDetails)
return buildRedactedItem(attributes, highlight) return buildRedactedItem(attributes, highlight)
} }

View File

@ -29,6 +29,7 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -41,7 +42,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val typingHelper: TypingHelper, private val typingHelper: TypingHelper,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val errorFormatter: ErrorFormatter) { private val errorFormatter: ErrorFormatter,
private val matrixConfiguration: MatrixConfiguration) {
fun create(roomSummary: RoomSummary, fun create(roomSummary: RoomSummary,
roomChangeMembershipStates: Map<String, ChangeMembershipState>, roomChangeMembershipStates: Map<String, ChangeMembershipState>,
@ -125,7 +127,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
// We do not display shield in the room list anymore // We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.izPublic(roomSummary.isPublic) .izPublic(roomSummary.isPublic)
.showPresence(roomSummary.isDirect) .showPresence(roomSummary.isDirect && matrixConfiguration.presenceSyncEnabled)
.userPresence(roomSummary.directUserPresence) .userPresence(roomSummary.directUserPresence)
.matrixItem(roomSummary.toMatrixItem()) .matrixItem(roomSummary.toMatrixItem())
.lastEventTime(latestEventTime) .lastEventTime(latestEventTime)

View File

@ -26,6 +26,8 @@ import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityThreadsBinding import im.vector.app.databinding.ActivityThreadsBinding
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.TimelineFragment import im.vector.app.features.home.room.detail.TimelineFragment
import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.arguments.TimelineArgs
@ -92,6 +94,7 @@ class ThreadsActivity : VectorBaseActivity<ActivityThreadsBinding>() {
* One usage of that is from the Threads Activity * One usage of that is from the Threads Activity
*/ */
fun navigateToThreadTimeline(threadTimelineArgs: ThreadTimelineArgs) { fun navigateToThreadTimeline(threadTimelineArgs: ThreadTimelineArgs) {
analyticsTracker.capture(Interaction.Name.MobileThreadListThreadItem.toAnalyticsInteraction())
val commonOption: (FragmentTransaction) -> Unit = { val commonOption: (FragmentTransaction) -> Unit = {
it.setCustomAnimations( it.setCustomAnimations(
R.anim.animation_slide_in_right, R.anim.animation_slide_in_right,

View File

@ -26,5 +26,6 @@ data class ThreadTimelineArgs(
val displayName: String?, val displayName: String?,
val avatarUrl: String?, val avatarUrl: String?,
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?, val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?,
val rootThreadEventId: String? = null val rootThreadEventId: String? = null,
val startsThread: Boolean = false
) : Parcelable ) : Parcelable

View File

@ -25,6 +25,9 @@ import dagger.assisted.AssistedInject
import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -34,6 +37,7 @@ import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState: ThreadListViewState, class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState: ThreadListViewState,
private val analyticsTracker: AnalyticsTracker,
private val session: Session) : private val session: Session) :
VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) { VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) {
@ -113,9 +117,10 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
} }
} }
fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading fun canHomeserverUseThreading() = session.getHomeServerCapabilities().canUseThreading
fun applyFiltering(shouldFilterThreads: Boolean) { fun applyFiltering(shouldFilterThreads: Boolean) {
analyticsTracker.capture(Interaction.Name.MobileThreadListFilterItem.toAnalyticsInteraction())
setState { setState {
copy(shouldFilterThreads = shouldFilterThreads) copy(shouldFilterThreads = shouldFilterThreads)
} }

View File

@ -30,6 +30,7 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentThreadListBinding import im.vector.app.databinding.FragmentThreadListBinding
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator
import im.vector.app.features.home.room.threads.ThreadsActivity import im.vector.app.features.home.room.threads.ThreadsActivity
@ -62,6 +63,7 @@ class ThreadListFragment @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
analyticsScreenName = MobileScreen.ScreenName.ThreadList
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@ -54,6 +54,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber import timber.log.Timber
@ -67,7 +68,8 @@ data class RoomProfileArgs(
class RoomProfileFragment @Inject constructor( class RoomProfileFragment @Inject constructor(
private val roomProfileController: RoomProfileController, private val roomProfileController: RoomProfileController,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val roomDetailPendingActionStore: RoomDetailPendingActionStore private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
private val matrixConfiguration: MatrixConfiguration
) : ) :
VectorBaseFragment<FragmentMatrixProfileBinding>(), VectorBaseFragment<FragmentMatrixProfileBinding>(),
RoomProfileController.Callback { RoomProfileController.Callback {
@ -222,7 +224,7 @@ class RoomProfileFragment @Inject constructor(
avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView) avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView)
headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel) headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel)
views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
headerViews.roomProfilePresenceImageView.render(it.isDirect, it.directUserPresence) headerViews.roomProfilePresenceImageView.render(it.isDirect && matrixConfiguration.presenceSyncEnabled, it.directUserPresence)
headerViews.roomProfilePublicImageView.isVisible = it.isPublic && !it.isDirect headerViews.roomProfilePublicImageView.isVisible = it.isPublic && !it.isDirect
} }
} }

View File

@ -27,6 +27,7 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import me.gujun.android.span.span import me.gujun.android.span.span
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
@ -39,7 +40,8 @@ class RoomMemberListController @Inject constructor(
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val roomMemberSummaryFilter: RoomMemberSummaryFilter private val roomMemberSummaryFilter: RoomMemberSummaryFilter,
private val matrixConfiguration: MatrixConfiguration
) : TypedEpoxyController<RoomMemberListViewState>() { ) : TypedEpoxyController<RoomMemberListViewState>() {
interface Callback { interface Callback {
@ -122,6 +124,7 @@ class RoomMemberListController @Inject constructor(
host: RoomMemberListController, host: RoomMemberListController,
data: RoomMemberListViewState) { data: RoomMemberListViewState) {
val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) val powerLabel = stringProvider.getString(powerLevelCategory.titleRes)
val presenceSyncEnabled = matrixConfiguration.presenceSyncEnabled
profileMatrixItemWithPowerLevelWithPresence { profileMatrixItemWithPowerLevelWithPresence {
id(roomMember.userId) id(roomMember.userId)
@ -131,6 +134,7 @@ class RoomMemberListController @Inject constructor(
clickListener { clickListener {
host.callback?.onRoomMemberClicked(roomMember) host.callback?.onRoomMemberClicked(roomMember)
} }
showPresence(presenceSyncEnabled)
userPresence(roomMember.userPresence) userPresence(roomMember.userPresence)
powerLevelLabel( powerLevelLabel(
span { span {

View File

@ -40,14 +40,16 @@ data class RoomNotificationSettingsViewState(
*/ */
val RoomNotificationSettingsViewState.notificationStateMapped: Async<RoomNotificationState> val RoomNotificationSettingsViewState.notificationStateMapped: Async<RoomNotificationState>
get() { get() {
if ((roomSummary()?.isEncrypted == true && notificationState() == RoomNotificationState.MENTIONS_ONLY) || return when {
notificationState() == RoomNotificationState.ALL_MESSAGES) { /**
/** if in an encrypted room, mentions notifications are not supported so show "All Messages" as selected. * if in an encrypted room, mentions notifications are not supported so show "None" as selected.
* Also in the new settings there is no notion of notifications without sound so it maps to noisy also * Also in the new settings there is no notion of notifications without sound so it maps to noisy also
*/ */
return Success(RoomNotificationState.ALL_MESSAGES_NOISY) (roomSummary()?.isEncrypted == true && notificationState() == RoomNotificationState.MENTIONS_ONLY)
-> Success(RoomNotificationState.MUTE)
notificationState() == RoomNotificationState.ALL_MESSAGES -> Success(RoomNotificationState.ALL_MESSAGES_NOISY)
else -> notificationState
} }
return notificationState
} }
/** /**