Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Conversations and Profile Views are reported individually (WPB-14942) #3792

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
42 changes: 35 additions & 7 deletions app/src/main/kotlin/com/wire/android/util/CurrentScreenManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.navigation.NavDestination
import com.ramcosta.composedestinations.spec.DestinationSpec
import com.wire.android.appLogger
import com.wire.android.feature.analytics.AnonymousAnalyticsManagerImpl
import com.wire.android.navigation.getBaseRoute
import com.wire.android.navigation.toDestination
import com.wire.android.ui.destinations.ConversationScreenDestination
import com.wire.android.ui.destinations.CreateAccountDetailsScreenDestination
Expand Down Expand Up @@ -123,17 +124,34 @@ class CurrentScreenManager @Inject constructor(
}

override fun onDestinationChanged(controller: NavController, destination: NavDestination, arguments: Bundle?) {
val currentView = currentScreenState.value.toString()
AnonymousAnalyticsManagerImpl.stopView(currentView)
val currentScreenName = currentScreenName()
currentScreenName?.let {
AnonymousAnalyticsManagerImpl.stopView(it)
}

val currentItem = destination.toDestination()
currentScreenState.value = CurrentScreen.fromDestination(
currentItem,
arguments,
isApplicationVisibleFlow.value
)

val newView = currentScreenState.value.toString()
AnonymousAnalyticsManagerImpl.recordView(newView)
val newScreenName = currentScreenName()
newScreenName?.let {
AnonymousAnalyticsManagerImpl.recordView(it)
}
}

private fun currentScreenName() = currentScreenState.value.let { currentScreen ->
when (currentScreen) {
is CurrentScreen.Home,
is CurrentScreen.Conversation,
is CurrentScreen.OtherUserProfile,
is CurrentScreen.ImportMedia,
is CurrentScreen.DeviceManager -> return@let currentScreen.toScreenName()

else -> return@let (currentScreen as? CurrentScreen.SomeOther)?.route?.getBaseRoute()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what about AuthRelated type? It will be ignored in this case because else tries to map it to SomeOther

}
}

override fun onCreate(owner: LifecycleOwner) {
Expand Down Expand Up @@ -164,23 +182,31 @@ class CurrentScreenManager @Inject constructor(
sealed class CurrentScreen {

// Home Screen is being displayed
data object Home : CurrentScreen()
data object Home : CurrentScreen() {
override fun toScreenName() = "HomeScreen"
}

// Some Conversation is opened
data class Conversation(val id: ConversationId) : CurrentScreen() {
override fun toString(): String = "Conversation(${id.toString().obfuscateId()})"
override fun toScreenName() = "ConversationScreen"
}

// Another User Profile Screen is opened
data class OtherUserProfile(val id: ConversationId) : CurrentScreen() {
override fun toString(): String = "OtherUserProfile(${id.toString().obfuscateId()})"
override fun toScreenName() = "OtherUserProfileScreen"
}

// Import media screen is opened
data object ImportMedia : CurrentScreen()
data object ImportMedia : CurrentScreen() {
override fun toScreenName() = "ImportMediaScreen"
}

// SelfDevices screen is opened
data object DeviceManager : CurrentScreen()
data object DeviceManager : CurrentScreen() {
override fun toScreenName() = "DeviceManagerScreen"
}

// Auth related screen is opened
data class AuthRelated(val route: String?) : CurrentScreen()
Expand All @@ -191,6 +217,8 @@ sealed class CurrentScreen {
// App is in background (screen is turned off, or covered by another app), non of the screens is visible
data object InBackground : CurrentScreen()

open fun toScreenName(): String = "UnknownScreen"

companion object {
@SuppressLint("RestrictedApi")
@Suppress("ComplexMethod")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ object AnonymousAnalyticsManagerImpl : AnonymousAnalyticsManager {
coroutineScope.launch {
mutex.withLock {
if (!isAnonymousUsageDataEnabled) return@withLock
anonymousAnalyticsRecorder?.recordView(screen)
anonymousAnalyticsRecorder?.recordView(screen.convertToCamelCase())
}
}
}
Expand All @@ -195,7 +195,7 @@ object AnonymousAnalyticsManagerImpl : AnonymousAnalyticsManager {
coroutineScope.launch {
mutex.withLock {
if (!isAnonymousUsageDataEnabled) return@withLock
anonymousAnalyticsRecorder?.stopView(screen)
anonymousAnalyticsRecorder?.stopView(screen.convertToCamelCase())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.feature.analytics

/**
* Converts a snake_case string to camelCase.
*/
fun String.convertToCamelCase(): String {
return this
.split('_')
.joinToString("") { it.replaceFirstChar { char -> char.uppercase() } }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.feature.analytics

import org.junit.Assert.assertEquals
import org.junit.Test

class StringExtTest {

@Test
fun `given single word string when converted then return same string`() {
assertEquals("Username", "username".convertToCamelCase())
}

@Test
fun `given string with multiple underscores when converted then return camel case string`() {
assertEquals("ThisIsATestCase", ("this_is_a_test_case").convertToCamelCase())
}

@Test
fun `given empty string when converted then return empty string`() {
assertEquals("", ("").convertToCamelCase())
}

@Test
fun `given string with leading and trailing underscores when converted then return camel case string`() {
assertEquals("LeadingAndTrailing", ("_leading_and_trailing_").convertToCamelCase())
}

@Test
fun `given string with numbers when converted then return camel case string`() {
assertEquals("User123Name", ("user_123_name").convertToCamelCase())
}
}
Loading