Merge branch 'develop' into feature/fga/room_list_filters
This commit is contained in:
@@ -76,6 +76,7 @@ dependencies {
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.features.leaveroom.test)
|
||||
testImplementation(projects.features.createroom.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||
@@ -46,6 +47,8 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
|
||||
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
|
||||
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -60,6 +63,7 @@ class RoomDetailsPresenter @Inject constructor(
|
||||
private val roomMembersDetailsPresenterFactory: RoomMemberDetailsPresenter.Factory,
|
||||
private val leaveRoomPresenter: LeaveRoomPresenter,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<RoomDetailsState> {
|
||||
@Composable
|
||||
override fun present(): RoomDetailsState {
|
||||
@@ -124,11 +128,7 @@ class RoomDetailsPresenter @Inject constructor(
|
||||
client.notificationSettingsService().unmuteRoom(room.roomId, room.isEncrypted, room.isOneToOne)
|
||||
}
|
||||
}
|
||||
is RoomDetailsEvent.SetFavorite -> {
|
||||
scope.launch {
|
||||
room.setIsFavorite(event.isFavorite)
|
||||
}
|
||||
}
|
||||
is RoomDetailsEvent.SetFavorite -> scope.setFavorite(event.isFavorite)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,4 +187,11 @@ class RoomDetailsPresenter @Inject constructor(
|
||||
room.updateRoomNotificationSettings()
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.setFavorite(isFavorite: Boolean) = launch {
|
||||
room.setIsFavorite(isFavorite)
|
||||
.onSuccess {
|
||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomFavouriteToggle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.TurbineTestContext
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
@@ -50,6 +51,8 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.FakeLifecycleOwner
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
@@ -77,6 +80,7 @@ class RoomDetailsPresenterTests {
|
||||
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
): RoomDetailsPresenter {
|
||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||
val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory {
|
||||
@@ -95,6 +99,7 @@ class RoomDetailsPresenterTests {
|
||||
roomMembersDetailsPresenterFactory = roomMemberDetailsPresenterFactory,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
dispatchers = dispatchers,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -435,13 +440,18 @@ class RoomDetailsPresenterTests {
|
||||
@Test
|
||||
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createRoomDetailsPresenter(room = room)
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val presenter = createRoomDetailsPresenter(room = room, analyticsService = analyticsService)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RoomDetailsEvent.SetFavorite(true))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
|
||||
initialState.eventSink(RoomDetailsEvent.SetFavorite(false))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Interaction(name = Interaction.Name.MobileRoomFavouriteToggle),
|
||||
Interaction(name = Interaction.Name.MobileRoomFavouriteToggle)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ dependencies {
|
||||
testImplementation(projects.libraries.permissions.noop)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.features.invitelist.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.features.leaveroom.test)
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
@@ -48,12 +49,15 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.collect
|
||||
@@ -82,6 +86,7 @@ class RoomListPresenter @Inject constructor(
|
||||
private val searchPresenter: Presenter<RoomListSearchState>,
|
||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<RoomListState> {
|
||||
private val encryptionService: EncryptionService = client.encryptionService()
|
||||
|
||||
@@ -144,27 +149,9 @@ class RoomListPresenter @Inject constructor(
|
||||
contextMenu.value = RoomListState.ContextMenu.Hidden
|
||||
}
|
||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
||||
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.use { room ->
|
||||
room.setIsFavorite(event.isFavorite)
|
||||
}
|
||||
}
|
||||
is RoomListEvents.MarkAsRead -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.use { room ->
|
||||
room.setUnreadFlag(isUnread = false)
|
||||
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
|
||||
ReceiptType.READ
|
||||
} else {
|
||||
ReceiptType.READ_PRIVATE
|
||||
}
|
||||
room.markAsRead(receiptType)
|
||||
}
|
||||
}
|
||||
is RoomListEvents.MarkAsUnread -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.use { room ->
|
||||
room.setUnreadFlag(isUnread = true)
|
||||
}
|
||||
}
|
||||
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite)
|
||||
is RoomListEvents.MarkAsRead -> coroutineScope.markAsRead(event.roomId)
|
||||
is RoomListEvents.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +210,39 @@ class RoomListPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.setRoomIsFavorite(roomId: RoomId, isFavorite: Boolean) = launch {
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
room.setIsFavorite(isFavorite)
|
||||
.onSuccess {
|
||||
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.markAsRead(roomId: RoomId) = launch {
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
room.setUnreadFlag(isUnread = false)
|
||||
val receiptType = if (sessionPreferencesStore.isSendPublicReadReceiptsEnabled().first()) {
|
||||
ReceiptType.READ
|
||||
} else {
|
||||
ReceiptType.READ_PRIVATE
|
||||
}
|
||||
room.markAsRead(receiptType)
|
||||
.onSuccess {
|
||||
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.markAsUnread(roomId: RoomId) = launch {
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
room.setUnreadFlag(isUnread = true)
|
||||
.onSuccess {
|
||||
analyticsService.captureInteraction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVisibleRange(range: IntRange) {
|
||||
if (range.isEmpty()) return
|
||||
val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2
|
||||
|
||||
@@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
@@ -72,6 +73,8 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
@@ -484,10 +487,11 @@ class RoomListPresenterTests {
|
||||
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val room = FakeMatrixRoom()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
@@ -496,6 +500,10 @@ class RoomListPresenterTests {
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true))
|
||||
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false))
|
||||
assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false))
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle),
|
||||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
@@ -536,11 +544,13 @@ class RoomListPresenterTests {
|
||||
val matrixClient = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
@@ -559,6 +569,11 @@ class RoomListPresenterTests {
|
||||
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID))
|
||||
assertThat(room.markAsReadCalls).isEqualTo(listOf(ReceiptType.READ, ReceiptType.READ_PRIVATE))
|
||||
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false, true, false))
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
|
||||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
|
||||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
@@ -582,6 +597,7 @@ class RoomListPresenterTests {
|
||||
matrixClient = client,
|
||||
migrationScreenStore = InMemoryMigrationScreenStore(),
|
||||
),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() },
|
||||
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||
) = RoomListPresenter(
|
||||
@@ -611,5 +627,6 @@ class RoomListPresenterTests {
|
||||
searchPresenter = searchPresenter,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
filtersPresenter = filtersPresenter,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0"
|
||||
# Analytics
|
||||
posthog = "com.posthog:posthog-android:3.1.8"
|
||||
sentry = "io.sentry:sentry-android:7.3.0"
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:aa14cbcdf81af2746d20a71779ec751f971e1d7f"
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.11.0"
|
||||
|
||||
# Emojibase
|
||||
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3"
|
||||
|
||||
@@ -63,5 +63,6 @@ dependencies {
|
||||
implementation(projects.features.networkmonitor.impl)
|
||||
implementation(projects.services.toolbox.impl)
|
||||
implementation(projects.libraries.featureflag.impl)
|
||||
implementation(projects.services.analytics.noop)
|
||||
implementation(libs.coroutines.core)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStore
|
||||
import io.element.android.services.analytics.noop.NoopAnalyticsService
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -126,6 +127,7 @@ class RoomListScreen(
|
||||
roomListService = matrixClient.roomListService,
|
||||
featureFlagService = featureFlagService,
|
||||
),
|
||||
analyticsService = NoopAnalyticsService(),
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.services.analyticsproviders.api.trackers
|
||||
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import im.vector.app.features.analytics.plan.UserProperties
|
||||
|
||||
interface AnalyticsTracker {
|
||||
@@ -36,3 +37,7 @@ interface AnalyticsTracker {
|
||||
*/
|
||||
fun updateUserProperties(userProperties: UserProperties)
|
||||
}
|
||||
|
||||
fun AnalyticsTracker.captureInteraction(name: Interaction.Name, type: Interaction.InteractionType? = null) {
|
||||
capture(Interaction(interactionType = type, name = name))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user