From a33b755b6d98396a0fc6113ba51ac1bfe08d1245 Mon Sep 17 00:00:00 2001 From: ohassine Date: Mon, 27 Jan 2025 13:18:42 +0100 Subject: [PATCH 1/2] feat: add is_team_member and user_type segmentations --- .../ui/userprofile/qr/SelfQRCodeScreen.kt | 91 +++++++++++-------- .../ui/userprofile/qr/SelfQRCodeState.kt | 3 +- .../ui/userprofile/qr/SelfQRCodeViewModel.kt | 9 +- .../ui/userprofile/qr/SelfQrCodeNavArgs.kt | 3 +- .../userprofile/self/SelfUserProfileScreen.kt | 9 +- .../feature/analytics/model/AnalyticsEvent.kt | 55 ++++++----- 6 files changed, 105 insertions(+), 65 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeScreen.kt index b4e763d20c8..d8d511dea8b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeScreen.kt @@ -109,7 +109,11 @@ private fun SelfQRCodeContent( val context = LocalContext.current BackHandler { - trackAnalyticsEvent(AnalyticsEvent.QrCode.Modal.Back) + trackAnalyticsEvent( + AnalyticsEvent.QrCode.Modal.Back( + isTeam = state.isTeamMember + ) + ) onBackClick() } @@ -118,7 +122,11 @@ private fun SelfQRCodeContent( WireCenterAlignedTopAppBar( title = stringResource(id = R.string.user_profile_qr_code_title), onNavigationPressed = { - trackAnalyticsEvent(AnalyticsEvent.QrCode.Modal.Back) + trackAnalyticsEvent( + AnalyticsEvent.QrCode.Modal.Back( + isTeam = state.isTeamMember + ) + ) onBackClick() }, elevation = 0.dp @@ -127,26 +135,26 @@ private fun SelfQRCodeContent( ) { internalPadding -> Column( modifier = - Modifier - .fillMaxSize() - .background(colorsScheme().background) - .padding(internalPadding), + Modifier + .fillMaxSize() + .background(colorsScheme().background) + .padding(internalPadding), horizontalAlignment = Alignment.CenterHorizontally ) { VerticalSpace.x24() Column( modifier = - Modifier - .padding(horizontal = dimensions().spacing16x) - .clip(RoundedCornerShape(dimensions().spacing8x)) - .fillMaxWidth() - .drawWithContent { - graphicsLayer.record { - this@drawWithContent.drawContent() + Modifier + .padding(horizontal = dimensions().spacing16x) + .clip(RoundedCornerShape(dimensions().spacing8x)) + .fillMaxWidth() + .drawWithContent { + graphicsLayer.record { + this@drawWithContent.drawContent() + } + drawLayer(graphicsLayer) } - drawLayer(graphicsLayer) - } - .background(Color.White), + .background(Color.White), horizontalAlignment = Alignment.CenterHorizontally ) { VerticalSpace.x16() @@ -159,14 +167,14 @@ private fun SelfQRCodeContent( Box( contentAlignment = Alignment.Center, modifier = - Modifier - .clip(CircleShape) - .border( - width = dimensions().spacing2x, - shape = CircleShape, - color = Color.White - ) - .background(colorsScheme().primary) + Modifier + .clip(CircleShape) + .border( + width = dimensions().spacing2x, + shape = CircleShape, + color = Color.White + ) + .background(colorsScheme().primary) ) { Icon( painter = painterResource(id = R.drawable.ic_launcher_foreground), @@ -203,10 +211,14 @@ private fun SelfQRCodeContent( color = colorsScheme().secondaryText ) Spacer(modifier = Modifier.weight(1f)) - ShareLinkButton(state.userAccountProfileLink, trackAnalyticsEvent) + ShareLinkButton(state.isTeamMember, state.userAccountProfileLink, trackAnalyticsEvent) VerticalSpace.x8() ShareQRCodeButton { - trackAnalyticsEvent(AnalyticsEvent.QrCode.Modal.ShareQrCode) + trackAnalyticsEvent( + AnalyticsEvent.QrCode.Modal.ShareQrCode( + isTeam = state.isTeamMember + ) + ) coroutineScope.launch { val bitmap = graphicsLayer.toImageBitmap() val qrUri = shareQRAssetClick(bitmap.asAndroidBitmap()) @@ -222,11 +234,11 @@ private fun SelfQRCodeContent( fun ShareQRCodeButton(shareQRAssetClick: () -> Unit) { WirePrimaryButton( modifier = - Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = dimensions().spacing16x) - .testTag("Share QR link"), + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = dimensions().spacing16x) + .testTag("Share QR link"), text = stringResource(R.string.user_profile_qr_code_share_image_link), onClick = shareQRAssetClick ) @@ -234,20 +246,25 @@ fun ShareQRCodeButton(shareQRAssetClick: () -> Unit) { @Composable private fun ShareLinkButton( + isTeamMember: Boolean, selfProfileUrl: String, trackAnalyticsEvent: (AnalyticsEvent.QrCode.Modal) -> Unit ) { val context = LocalContext.current WirePrimaryButton( modifier = - Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = dimensions().spacing16x) - .testTag("Share link"), + Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = dimensions().spacing16x) + .testTag("Share link"), text = stringResource(R.string.user_profile_qr_code_share_link), onClick = { - trackAnalyticsEvent(AnalyticsEvent.QrCode.Modal.ShareProfileLink) + trackAnalyticsEvent( + AnalyticsEvent.QrCode.Modal.ShareProfileLink( + isTeam = isTeamMember + ) + ) context.shareLinkToProfile(selfProfileUrl) } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeState.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeState.kt index 71f6312a0e6..7e48401a852 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeState.kt @@ -26,5 +26,6 @@ data class SelfQRCodeState( val handle: String = "", val userProfileLink: String = "", val userAccountProfileLink: String = "", - val hasError: Boolean = false + val hasError: Boolean = false, + val isTeamMember: Boolean = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModel.kt index b996715e0a9..1fcbe8409ec 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModel.kt @@ -56,13 +56,18 @@ class SelfQRCodeViewModel @Inject constructor( private val analyticsManager: AnonymousAnalyticsManager ) : ViewModel() { private val selfQrCodeNavArgs: SelfQrCodeNavArgs = savedStateHandle.navArgs() - var selfQRCodeState by mutableStateOf(SelfQRCodeState(selfUserId, handle = selfQrCodeNavArgs.handle)) + var selfQRCodeState by mutableStateOf( + SelfQRCodeState( + selfUserId, + handle = selfQrCodeNavArgs.handle, + isTeamMember = selfQrCodeNavArgs.isTeamMember + ) + ) private set private val cachePath: Path get() = kaliumFileSystem.rootCachePath init { - trackAnalyticsEvent(AnalyticsEvent.QrCode.Modal.Displayed) viewModelScope.launch { getServerLinks() } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQrCodeNavArgs.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQrCodeNavArgs.kt index 82770c884f8..fde971b7e15 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQrCodeNavArgs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/qr/SelfQrCodeNavArgs.kt @@ -22,5 +22,6 @@ import kotlinx.parcelize.Parcelize @Parcelize data class SelfQrCodeNavArgs( - val handle: String = "" + val handle: String = "", + val isTeamMember: Boolean ) : Parcelable diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt index c562b1ba5dc..276f658ac33 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt @@ -153,7 +153,14 @@ fun SelfUserProfileScreen( }, onQrCodeClick = { viewModelSelf.trackQrCodeClick() - navigator.navigate(NavigationCommand(SelfQRCodeScreenDestination(viewModelSelf.userProfileState.userName))) + navigator.navigate( + NavigationCommand( + SelfQRCodeScreenDestination( + viewModelSelf.userProfileState.userName, + !viewModelSelf.userProfileState.teamName.isNullOrBlank() + ) + ) + ) }, onCreateAccount = { viewModelSelf.sendPersonalToTeamMigrationEvent() diff --git a/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt b/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt index f05fa6d71d9..652130a177c 100644 --- a/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt +++ b/core/analytics/src/main/kotlin/com/wire/android/feature/analytics/model/AnalyticsEvent.kt @@ -30,7 +30,7 @@ import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_SIZE import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_CONVERSATION_TYPE import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_END_REASON -import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_IS_TEAM_MEMBER +import com.wire.android.feature.analytics.model.AnalyticsEventConstants.IS_TEAM_MEMBER import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_ENDED_UNIQUE_SCREEN_SHARE import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_QUALITY_REVIEW_IGNORE_REASON import com.wire.android.feature.analytics.model.AnalyticsEventConstants.CALLING_QUALITY_REVIEW_IGNORE_REASON_KEY @@ -151,7 +151,7 @@ interface AnalyticsEvent { override fun toSegmentation(): Map { return mapOf( - CALLING_ENDED_IS_TEAM_MEMBER to metadata.isTeamMember, + IS_TEAM_MEMBER to metadata.isTeamMember, CALLING_ENDED_CALL_SCREEN_SHARE to metadata.callDetails.screenShareDurationInSeconds, CALLING_ENDED_UNIQUE_SCREEN_SHARE to metadata.callDetails.callScreenShareUniques, CALLING_ENDED_CALL_DIRECTION to metadata.toCallDirection(), @@ -257,39 +257,48 @@ interface AnalyticsEvent { } sealed class QrCode : AnalyticsEvent { + fun createSegmentationMap( + isTeam: Boolean + ): Map { + val userType = if (isTeam) { + QR_CODE_SEGMENTATION_USER_TYPE_TEAM + } else { + QR_CODE_SEGMENTATION_USER_TYPE_PERSONAL + } + + return mapOf( + AnalyticsEventConstants.QR_CODE_SEGMENTATION_USER_TYPE to userType, + IS_TEAM_MEMBER to isTeam, + ) + } + data class Click( val isTeam: Boolean ) : QrCode() { override val key: String = AnalyticsEventConstants.QR_CODE_CLICK - - override fun toSegmentation(): Map { - val userType = if (isTeam) { - QR_CODE_SEGMENTATION_USER_TYPE_TEAM - } else { - QR_CODE_SEGMENTATION_USER_TYPE_PERSONAL - } - - return mapOf( - AnalyticsEventConstants.QR_CODE_SEGMENTATION_USER_TYPE to userType - ) - } + override fun toSegmentation(): Map = createSegmentationMap(isTeam) } sealed class Modal : QrCode() { - data object Displayed : Modal() { - override val key: String = AnalyticsEventConstants.QR_CODE_MODAL - } - - data object Back : Modal() { + data class Back( + val isTeam: Boolean + ) : Modal() { override val key: String = AnalyticsEventConstants.QR_CODE_MODAL_BACK + override fun toSegmentation(): Map = createSegmentationMap(isTeam) } - data object ShareProfileLink : Modal() { + data class ShareProfileLink( + val isTeam: Boolean + ) : Modal() { override val key: String = AnalyticsEventConstants.QR_CODE_SHARE_PROFILE_LINK + override fun toSegmentation(): Map = createSegmentationMap(isTeam) } - data object ShareQrCode : Modal() { + data class ShareQrCode( + val isTeam: Boolean + ) : Modal() { override val key: String = AnalyticsEventConstants.QR_CODE_SHARE_QR_CODE + override fun toSegmentation(): Map = createSegmentationMap(isTeam) } } } @@ -393,6 +402,8 @@ object AnalyticsEventConstants { const val TEAM_IS_TEAM = "team_is_team" const val APP_OPEN = "app.open" + const val IS_TEAM_MEMBER = "is_team_member" + /** * Calling */ @@ -412,7 +423,6 @@ object AnalyticsEventConstants { /** * Call ended */ - const val CALLING_ENDED_IS_TEAM_MEMBER = "is_team_member" const val CALLING_ENDED_CALL_SCREEN_SHARE = "call_screen_share_duration" const val CALLING_ENDED_UNIQUE_SCREEN_SHARE = "call_screen_share_unique" const val CALLING_ENDED_CALL_DIRECTION = "call_direction" @@ -455,7 +465,6 @@ object AnalyticsEventConstants { * Qr code */ const val QR_CODE_CLICK = "ui.QR-click" - const val QR_CODE_MODAL = "ui.share.profile" const val QR_CODE_MODAL_BACK = "user.back.share-profile" const val QR_CODE_SHARE_PROFILE_LINK = "user.share-profile" const val QR_CODE_SHARE_QR_CODE = "user.QR-code" From 65a0d10338bc2488b11a1a27acd0b4c2cb480a87 Mon Sep 17 00:00:00 2001 From: ohassine Date: Mon, 27 Jan 2025 18:00:17 +0100 Subject: [PATCH 2/2] chore: unit test --- .../wire/android/ui/userprofile/qr/SelfQRCodeViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModelTest.kt index 5c690d7398a..9c391ae86f9 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/qr/SelfQRCodeViewModelTest.kt @@ -61,7 +61,7 @@ class SelfQRCodeViewModelTest { coEvery { selfServerConfig.invoke() } returns SelfServerConfigUseCase.Result.Success( serverLinks = newServerConfig(1).copy(links = ServerConfig.STAGING) ) - every { savedStateHandle.navArgs() } returns SelfQrCodeNavArgs("handle") + every { savedStateHandle.navArgs() } returns SelfQrCodeNavArgs("handle", false) } fun arrange() = this to SelfQRCodeViewModel(