Init analytics modules (#350)

This commit is contained in:
Yoan Pintas
2023-06-05 15:11:34 +02:00
committed by GitHub
parent c457d74954
commit d2263372d2
125 changed files with 2018 additions and 2130 deletions

3
.gitignore vendored
View File

@@ -40,6 +40,7 @@ captures/
.idea/.name
.idea/assetWizardSettings.xml
.idea/compiler.xml
.idea/deploymentTargetDropDown.xml
.idea/gradle.xml
.idea/jarRepositories.xml
.idea/misc.xml
@@ -54,6 +55,7 @@ captures/
.idea/inspectionProfiles
# Shelved changes in the IDE
.idea/shelf
.idea/sonarlint
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
@@ -88,7 +90,6 @@ lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
/.idea/deploymentTargetDropDown.xml
/tmp
.DS_Store

View File

@@ -21,4 +21,6 @@ appId: ${APP_ID}
- inputText: ${PASSWORD}
- pressKey: Enter
- tapOn: "Continue"
- runFlow: ../assertions/assertAnalyticsDisplayed.yaml
- tapOn: "Not now"
- runFlow: ../assertions/assertHomeDisplayed.yaml

View File

@@ -0,0 +1,5 @@
appId: ${APP_ID}
---
- extendedWaitUntil:
visible: "Help improve ElementX dbg"
timeout: 10_000

View File

@@ -51,13 +51,12 @@ dependencies {
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.permissions.noop)
implementation(projects.features.verifysession.api)
implementation(projects.features.roomdetails.api)
implementation(projects.tests.uitests)
implementation(libs.coil)
implementation(projects.services.apperror.impl)
implementation(projects.services.appnavstate.api)
implementation(projects.services.analytics.api)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)

View File

@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
import coil.Coil
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe
@@ -39,6 +40,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.appnav.loggedin.LoggedInNode
import io.element.android.features.analytics.api.AnalyticsEntryPoint
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.invitelist.api.InviteListEntryPoint
import io.element.android.features.preferences.api.PreferencesEntryPoint
@@ -50,6 +52,7 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.di.AppScope
@@ -57,8 +60,13 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
@ContributesNode(AppScope::class)
@@ -68,20 +76,43 @@ class LoggedInFlowNode @AssistedInject constructor(
private val roomListEntryPoint: RoomListEntryPoint,
private val preferencesEntryPoint: PreferencesEntryPoint,
private val createRoomEntryPoint: CreateRoomEntryPoint,
private val analyticsOptInEntryPoint: AnalyticsEntryPoint,
private val appNavigationStateService: AppNavigationStateService,
private val verifySessionEntryPoint: VerifySessionEntryPoint,
private val inviteListEntryPoint: InviteListEntryPoint,
private val analyticsService: AnalyticsService,
private val coroutineScope: CoroutineScope,
snackbarDispatcher: SnackbarDispatcher,
) : BackstackNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.RoomList,
initialElement = NavTarget.SplashScreen,
savedStateMap = buildContext.savedStateMap,
),
buildContext = buildContext,
plugins = plugins
) {
private fun observeAnalyticsState() {
analyticsService.didAskUserConsent()
.distinctUntilChanged()
.onEach { isConsentAsked ->
if (isConsentAsked) {
switchToRoomList()
} else {
switchToAnalytics()
}
}
.launchIn(lifecycleScope)
}
private fun switchToRoomList() {
backstack.safeRoot(NavTarget.RoomList)
}
private fun switchToAnalytics() {
backstack.safeRoot(NavTarget.AnalyticsSettings)
}
interface Callback : Plugin {
fun onOpenBugReport() = Unit
}
@@ -105,6 +136,7 @@ class LoggedInFlowNode @AssistedInject constructor(
override fun onBuilt() {
super.onBuilt()
observeAnalyticsState()
lifecycle.subscribe(
onCreate = {
plugins<LifecycleCallback>().forEach { it.onFlowCreated(inputs.matrixClient) }
@@ -128,6 +160,9 @@ class LoggedInFlowNode @AssistedInject constructor(
}
sealed interface NavTarget : Parcelable {
@Parcelize
object SplashScreen : NavTarget
@Parcelize
object Permanent : NavTarget
@@ -150,11 +185,15 @@ class LoggedInFlowNode @AssistedInject constructor(
object VerifySession : NavTarget
@Parcelize
object InviteList: NavTarget
object InviteList : NavTarget
@Parcelize
object AnalyticsSettings : NavTarget
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) {
NavTarget.SplashScreen -> splashNode(buildContext)
NavTarget.Permanent -> {
createNode<LoggedInNode>(buildContext)
}
@@ -244,6 +283,9 @@ class LoggedInFlowNode @AssistedInject constructor(
.callback(callback)
.build()
}
NavTarget.AnalyticsSettings -> {
analyticsOptInEntryPoint.createNode(this, buildContext)
}
}
}
@@ -260,6 +302,12 @@ class LoggedInFlowNode @AssistedInject constructor(
}
}
private fun splashNode(buildContext: BuildContext) = node(buildContext) {
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
@Composable
override fun View(modifier: Modifier) {
Box(modifier = modifier) {

View File

@@ -25,8 +25,8 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.push.providers.api.Distributor
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import kotlinx.coroutines.test.runTest
import org.junit.Test

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 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.
*/
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.ksp)
}
android {
namespace = "io.element.android.features.analytics.api"
}
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.uiStrings)
ksp(libs.showkase.processor)
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.api
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
interface AnalyticsEntryPoint : SimpleFeatureEntryPoint

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.api
sealed interface AnalyticsOptInEvents {
data class EnableAnalytics(val isEnabled: Boolean) : AnalyticsOptInEvents
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.api.preferences
import io.element.android.libraries.architecture.Presenter
interface AnalyticsPreferencesPresenter : Presenter<AnalyticsPreferencesState>

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.api.preferences
import io.element.android.features.analytics.api.AnalyticsOptInEvents
data class AnalyticsPreferencesState(
val applicationName: String,
val isEnabled: Boolean,
val eventSink: (AnalyticsOptInEvents) -> Unit,
)

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.api.preferences
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class AnalyticsPreferencesStateProvider : PreviewParameterProvider<AnalyticsPreferencesState> {
override val values: Sequence<AnalyticsPreferencesState>
get() = sequenceOf(
aAnalyticsPreferencesState().copy(isEnabled = true),
)
}
fun aAnalyticsPreferencesState() = AnalyticsPreferencesState(
applicationName = "ElementX",
isEnabled = false,
eventSink = {}
)

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.api.preferences
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.libraries.designsystem.LinkColor
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun AnalyticsPreferencesView(
state: AnalyticsPreferencesState,
modifier: Modifier = Modifier,
) {
fun onEnabledChanged(isEnabled: Boolean) {
state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled))
}
PreferenceCategory(title = stringResource(id = StringR.string.screen_analytics_settings_share_data)) {
val firstPart = stringResource(id = StringR.string.screen_analytics_settings_help_us_improve, state.applicationName)
val secondPart = buildAnnotatedStringWithColoredPart(
StringR.string.screen_analytics_settings_read_terms,
StringR.string.screen_analytics_settings_read_terms_content_link
)
val title = "$firstPart\n\n$secondPart"
PreferenceSwitch(
title = title,
isChecked = state.isEnabled,
onCheckedChange = ::onEnabledChanged
)
}
}
@Composable
fun buildAnnotatedStringWithColoredPart(
@StringRes fullTextRes: Int,
@StringRes coloredTextRes: Int,
color: Color = LinkColor,
underline: Boolean = true,
) = buildAnnotatedString {
val coloredPart = stringResource(coloredTextRes)
val fullText = stringResource(fullTextRes, coloredPart)
val startIndex = fullText.indexOf(coloredPart)
append(fullText)
addStyle(
style = SpanStyle(
color = color,
textDecoration = if (underline) TextDecoration.Underline else null
), start = startIndex, end = startIndex + coloredPart.length
)
}
@Preview
@Composable
fun AnalyticsPreferencesViewLightPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun AnalyticsPreferencesViewDarkPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable
private fun ContentToPreview(state: AnalyticsPreferencesState) {
AnalyticsPreferencesView(state)
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 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.
*/
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize")
}
android {
namespace = "io.element.android.features.analytics.impl"
}
anvil {
generateDaggerFactories.set(true)
}
dependencies {
implementation(projects.anvilannotations)
anvil(projects.anvilcodegen)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.core)
implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.elementresources)
implementation(projects.libraries.uiStrings)
api(projects.features.analytics.api)
api(projects.services.analytics.api)
implementation(libs.androidx.datastore.preferences)
ksp(libs.showkase.processor)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(libs.test.mockk)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.features.analytics.test)
testImplementation(projects.features.analytics.impl)
androidTestImplementation(libs.test.junitext)
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class)
class AnalyticsOptInNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: AnalyticsOptInPresenter,
) : Node(buildContext, plugins = plugins) {
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
AnalyticsOptInView(
state = state,
modifier = modifier,
)
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
class AnalyticsOptInPresenter @Inject constructor(
private val buildMeta: BuildMeta,
private val analyticsService: AnalyticsService,
) : Presenter<AnalyticsOptInState> {
@Composable
override fun present(): AnalyticsOptInState {
val localCoroutineScope = rememberCoroutineScope()
fun handleEvents(event: AnalyticsOptInEvents) {
when (event) {
is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled)
}
localCoroutineScope.launch {
analyticsService.setDidAskUserConsent()
}
}
return AnalyticsOptInState(
applicationName = buildMeta.applicationName,
eventSink = ::handleEvents
)
}
private fun CoroutineScope.setIsEnabled(enabled: Boolean) = launch {
analyticsService.setUserConsent(enabled)
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl
import io.element.android.features.analytics.api.AnalyticsOptInEvents
data class AnalyticsOptInState(
val applicationName: String,
val eventSink: (AnalyticsOptInEvents) -> Unit
)

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.meta.BuildMeta
import javax.inject.Inject
open class AnalyticsOptInStateProvider @Inject constructor(
) : PreviewParameterProvider<AnalyticsOptInState> {
override val values: Sequence<AnalyticsOptInState>
get() = sequenceOf(
aAnalyticsOptInState(),
)
}
fun aAnalyticsOptInState() = AnalyticsOptInState(
applicationName = "ElementX",
eventSink = {}
)

View File

@@ -0,0 +1,238 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl
import android.graphics.Typeface
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import android.text.style.UnderlineSpan
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.libraries.designsystem.LinkColor
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun AnalyticsOptInView(
state: AnalyticsOptInState,
modifier: Modifier = Modifier,
) {
LogCompositions(tag = "Analytics", msg = "Root")
val eventSink = state.eventSink
Box(
modifier = modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
) {
Column(
modifier = Modifier.padding(horizontal = 16.dp),
) {
Column(modifier = Modifier.weight(1f)) {
Image(
painterResource(id = R.drawable.element_logo_stars),
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(16.dp)
)
Text(
text = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
color = MaterialTheme.colorScheme.primary,
)
Text(
text = stringResource(id = R.string.screen_analytics_prompt_help_us_improve, state.applicationName),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
textAlign = TextAlign.Center,
fontSize = 16.sp,
color = MaterialTheme.colorScheme.secondary,
)
Text(
text = buildAnnotatedStringWithColoredPart(
R.string.screen_analytics_prompt_read_terms,
R.string.screen_analytics_prompt_read_terms_content_link
),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
textAlign = TextAlign.Center,
fontSize = 16.sp,
color = MaterialTheme.colorScheme.secondary,
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Outlined.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary)
Text(
text = stringResource(id = R.string.screen_analytics_prompt_data_usage).toAnnotatedString(),
color = MaterialTheme.colorScheme.secondary,
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Outlined.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary)
Text(
text = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing).toAnnotatedString(),
color = MaterialTheme.colorScheme.secondary,
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Outlined.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary)
Text(
text = stringResource(id = R.string.screen_analytics_prompt_settings),
color = MaterialTheme.colorScheme.secondary,
)
}
}
Button(
onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) },
modifier = Modifier.fillMaxWidth(),
) {
Text(text = stringResource(id = StringR.string.action_enable))
}
Spacer(modifier = Modifier.height(16.dp))
TextButton(
onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) },
modifier = Modifier.fillMaxWidth(),
) {
Text(text = stringResource(id = StringR.string.action_not_now))
}
Spacer(Modifier.height(40.dp))
}
}
}
fun String.toAnnotatedString(): AnnotatedString = buildAnnotatedString {
append(this@toAnnotatedString)
val spannable = SpannableString(this@toAnnotatedString)
spannable.getSpans(0, spannable.length, Any::class.java).forEach { span ->
val start = spannable.getSpanStart(span)
val end = spannable.getSpanEnd(span)
when (span) {
is StyleSpan -> when (span.style) {
Typeface.BOLD -> addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end)
Typeface.ITALIC -> addStyle(SpanStyle(fontStyle = FontStyle.Italic), start, end)
Typeface.BOLD_ITALIC -> addStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic), start, end)
}
is UnderlineSpan -> addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end)
is ForegroundColorSpan -> addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end)
}
}
}
@Composable
fun buildAnnotatedStringWithColoredPart(
@StringRes fullTextRes: Int,
@StringRes coloredTextRes: Int,
color: Color = LinkColor,
underline: Boolean = true,
) = buildAnnotatedString {
val coloredPart = stringResource(coloredTextRes)
val fullText = stringResource(fullTextRes, coloredPart)
val startIndex = fullText.indexOf(coloredPart)
append(fullText)
addStyle(
style = SpanStyle(
color = color,
textDecoration = if (underline) TextDecoration.Underline else null
), start = startIndex, end = startIndex + coloredPart.length
)
}
@Preview
@Composable
fun AnalyticsOptInViewLightPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewLight {
ContentToPreview(state)
}
@Preview
@Composable
fun AnalyticsOptInViewDarkPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewDark {
ContentToPreview(state)
}
@Composable
private fun ContentToPreview(state: AnalyticsOptInState) {
AnalyticsOptInView(state = state)
}

View File

@@ -14,22 +14,19 @@
* limitations under the License.
*/
package io.element.android.services.analytics.noop
package io.element.android.features.analytics.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.services.analytics.api.AnalyticsTracker
import io.element.android.services.analytics.api.VectorAnalyticsEvent
import io.element.android.services.analytics.api.VectorAnalyticsScreen
import io.element.android.services.analytics.api.plan.UserProperties
import io.element.android.features.analytics.api.AnalyticsEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class NoopAnalyticsTracker @Inject constructor() : AnalyticsTracker {
override fun capture(event: VectorAnalyticsEvent) = Unit
override fun screen(screen: VectorAnalyticsScreen) = Unit
override fun updateUserProperties(userProperties: UserProperties) = Unit
class DefaultAnalyticsEntryPoint @Inject constructor() : AnalyticsEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
return parentNode.createNode<AnalyticsOptInNode>(buildContext)
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl.preferences
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.AppScope
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultAnalyticsPreferencesPresenter @Inject constructor(
private val analyticsService: AnalyticsService,
private val buildMeta: BuildMeta,
) : AnalyticsPreferencesPresenter {
@Composable
override fun present(): AnalyticsPreferencesState {
val localCoroutineScope = rememberCoroutineScope()
val isEnabled = analyticsService.getUserConsent()
.collectAsState(initial = false)
fun handleEvents(event: AnalyticsOptInEvents) {
when (event) {
is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled)
}
}
return AnalyticsPreferencesState(
applicationName = buildMeta.applicationName,
isEnabled = isEnabled.value,
eventSink = ::handleEvents
)
}
private fun CoroutineScope.setIsEnabled(enabled: Boolean) = launch {
analyticsService.setUserConsent(enabled)
}
}

View File

@@ -0,0 +1,57 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="94dp"
android:viewportWidth="120"
android:viewportHeight="94">
<path
android:pathData="M60.396,4.958L60.604,4.958A44.521,44.521 0,0 1,105.125 49.479L105.125,49.479A44.521,44.521 0,0 1,60.604 94L60.396,94A44.521,44.521 0,0 1,15.875 49.479L15.875,49.479A44.521,44.521 0,0 1,60.396 4.958z"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M53.228,26.676C53.228,24.958 54.623,23.566 56.344,23.566C67.82,23.566 77.123,32.847 77.123,44.296C77.123,46.014 75.727,47.406 74.006,47.406C72.285,47.406 70.889,46.014 70.889,44.296C70.889,36.282 64.377,29.785 56.344,29.785C54.623,29.785 53.228,28.393 53.228,26.676Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M67.772,72.282C67.772,73.999 66.377,75.391 64.655,75.391C53.18,75.391 43.877,66.11 43.877,54.661C43.877,52.944 45.272,51.552 46.994,51.552C48.715,51.552 50.111,52.944 50.111,54.661C50.111,62.675 56.623,69.172 64.655,69.172C66.377,69.172 67.772,70.564 67.772,72.282Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M37.644,56.734C35.922,56.734 34.527,55.342 34.527,53.625C34.527,42.176 43.83,32.895 55.305,32.895C57.027,32.895 58.422,34.287 58.422,36.004C58.422,37.722 57.027,39.114 55.305,39.114C47.272,39.114 40.76,45.611 40.76,53.625C40.76,55.342 39.365,56.734 37.644,56.734Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M83.356,42.223C85.078,42.223 86.473,43.615 86.473,45.332C86.473,56.781 77.17,66.063 65.695,66.063C63.973,66.063 62.578,64.671 62.578,62.953C62.578,61.236 63.973,59.844 65.695,59.844C73.728,59.844 80.24,53.347 80.24,45.332C80.24,43.615 81.635,42.223 83.356,42.223Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M39.571,77.305C40.181,77.305 40.683,76.856 40.769,76.227C41.7,69.687 42.587,68.79 48.918,68.076C49.56,68.001 50.041,67.478 50.041,66.87C50.041,66.251 49.57,65.75 48.929,65.664C42.63,64.843 41.817,64.043 40.769,57.502C40.662,56.873 40.181,56.435 39.571,56.435C38.972,56.435 38.47,56.873 38.374,57.513C37.454,64.053 36.566,64.95 30.235,65.664C29.594,65.739 29.123,66.251 29.123,66.87C29.123,67.478 29.583,67.99 30.235,68.076C36.534,68.951 37.315,69.697 38.374,76.238C38.491,76.867 38.983,77.305 39.571,77.305Z"
android:strokeWidth="1.5"
android:fillColor="#ffffff"
android:strokeColor="#0DBD8B"/>
<path
android:strokeWidth="1"
android:pathData="M82.194,35.392C82.697,35.392 83.111,35.019 83.182,34.494C83.949,29.044 84.682,28.297 89.905,27.701C90.434,27.639 90.831,27.203 90.831,26.697C90.831,26.181 90.443,25.763 89.913,25.692C84.717,25.007 84.046,24.34 83.182,18.89C83.093,18.365 82.697,18.001 82.194,18.001C81.7,18.001 81.285,18.365 81.205,18.899C80.447,24.349 79.714,25.096 74.491,25.692C73.962,25.754 73.574,26.181 73.574,26.697C73.574,27.203 73.953,27.63 74.491,27.701C79.688,28.43 80.332,29.053 81.205,34.503C81.302,35.028 81.708,35.392 82.194,35.392Z"
android:fillColor="#ffffff"
android:strokeColor="#0DBD8B"/>
<path
android:pathData="M113.846,18.87C114.174,18.87 114.444,18.631 114.49,18.296C114.991,14.807 115.468,14.329 118.873,13.948C119.218,13.908 119.477,13.63 119.477,13.305C119.477,12.975 119.224,12.708 118.879,12.662C115.491,12.224 115.054,11.797 114.49,8.309C114.433,7.973 114.174,7.74 113.846,7.74C113.524,7.74 113.254,7.973 113.202,8.315C112.707,11.803 112.23,12.281 108.825,12.662C108.48,12.702 108.227,12.975 108.227,13.305C108.227,13.63 108.474,13.903 108.825,13.948C112.213,14.415 112.633,14.813 113.202,18.301C113.265,18.637 113.53,18.87 113.846,18.87Z"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M107.169,9.131C107.354,9.131 107.506,8.997 107.531,8.808C107.813,6.846 108.081,6.577 109.997,6.363C110.191,6.34 110.336,6.183 110.336,6.001C110.336,5.815 110.194,5.665 110,5.639C108.094,5.393 107.849,5.153 107.531,3.191C107.499,3.002 107.354,2.871 107.169,2.871C106.988,2.871 106.836,3.002 106.807,3.194C106.529,5.156 106.26,5.425 104.345,5.639C104.151,5.662 104.008,5.815 104.008,6.001C104.008,6.183 104.147,6.337 104.345,6.363C106.25,6.625 106.486,6.849 106.807,8.811C106.842,9 106.991,9.131 107.169,9.131Z"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M108.575,24.435C108.8,24.435 108.986,24.271 109.018,24.04C109.362,21.642 109.69,21.314 112.031,21.052C112.268,21.024 112.446,20.833 112.446,20.61C112.446,20.383 112.272,20.199 112.035,20.167C109.706,19.866 109.405,19.573 109.018,17.175C108.978,16.944 108.8,16.783 108.575,16.783C108.353,16.783 108.167,16.944 108.132,17.179C107.792,19.577 107.464,19.905 105.123,20.167C104.885,20.195 104.711,20.383 104.711,20.61C104.711,20.833 104.881,21.02 105.123,21.052C107.452,21.372 107.74,21.646 108.132,24.044C108.175,24.275 108.357,24.435 108.575,24.435Z"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M6.197,15.392C6.504,15.392 6.758,15.168 6.801,14.853C7.27,11.583 7.718,11.135 10.91,10.778C11.233,10.74 11.476,10.479 11.476,10.175C11.476,9.865 11.239,9.615 10.915,9.572C7.739,9.161 7.329,8.761 6.801,5.491C6.747,5.176 6.504,4.958 6.197,4.958C5.895,4.958 5.642,5.176 5.593,5.496C5.129,8.767 4.682,9.215 1.49,9.572C1.166,9.609 0.929,9.865 0.929,10.175C0.929,10.479 1.161,10.735 1.49,10.778C4.666,11.215 5.059,11.589 5.593,14.859C5.652,15.174 5.9,15.392 6.197,15.392Z"
android:fillColor="#0DBD8B"/>
<path
android:pathData="M13.231,5.653C13.375,5.653 13.493,5.549 13.513,5.402C13.732,3.876 13.941,3.667 15.431,3.5C15.582,3.482 15.695,3.36 15.695,3.218C15.695,3.074 15.584,2.957 15.433,2.937C13.951,2.745 13.76,2.559 13.513,1.033C13.488,0.886 13.375,0.784 13.231,0.784C13.09,0.784 12.972,0.886 12.95,1.035C12.733,2.561 12.524,2.77 11.035,2.937C10.884,2.955 10.773,3.074 10.773,3.218C10.773,3.36 10.881,3.48 11.035,3.5C12.517,3.704 12.7,3.878 12.95,5.404C12.977,5.551 13.093,5.653 13.231,5.653Z"
android:strokeAlpha="0.4"
android:fillColor="#0DBD8B"
android:fillAlpha="0.4"/>
<path
android:pathData="M16.747,11.914C16.89,11.914 17.009,11.809 17.029,11.663C17.248,10.136 17.457,9.927 18.946,9.761C19.097,9.743 19.21,9.621 19.21,9.479C19.21,9.335 19.1,9.218 18.949,9.198C17.467,9.006 17.275,8.819 17.029,7.293C17.004,7.147 16.89,7.044 16.747,7.044C16.606,7.044 16.488,7.147 16.465,7.296C16.249,8.822 16.04,9.031 14.55,9.198C14.399,9.215 14.289,9.335 14.289,9.479C14.289,9.621 14.397,9.741 14.55,9.761C16.032,9.965 16.216,10.139 16.465,11.665C16.493,11.812 16.609,11.914 16.747,11.914Z"
android:strokeAlpha="0.4"
android:fillColor="#0DBD8B"
android:fillAlpha="0.4"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"Wir erfassen und analysieren "<b>"keine"</b>" Account-Daten"</string>
<string name="screen_analytics_prompt_help_us_improve">"Helfen Sie uns, Probleme zu identifizieren und %1$s zu verbessern, indem Sie anonyme Nutzungsdaten weitergeben."</string>
<string name="screen_analytics_prompt_read_terms">"Sie können alle unsere Nutzerbedingungen %1$s lesen."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
<string name="screen_analytics_prompt_settings">"Sie können die Analyse jederzeit in den Einstellungen deaktivieren"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben "<b>"keine"</b>" Informationen an Dritte weiter"</string>
<string name="screen_analytics_prompt_title">"Helfen Sie %1$s zu verbessern"</string>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage"><b>"Nu"</b>" înregistrăm sau profilăm datele contului"</string>
<string name="screen_analytics_prompt_help_us_improve">"Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime."</string>
<string name="screen_analytics_prompt_read_terms">"Puteți citi toate condițiile noastre %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"aici"</string>
<string name="screen_analytics_prompt_settings">"Puteți dezactiva această opțiune oricând din setări"</string>
<string name="screen_analytics_prompt_third_party_sharing"><b>"Nu"</b>" împărtășim informații cu terți"</string>
<string name="screen_analytics_prompt_title">"Ajutați la îmbunătățirea %1$s"</string>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"We "<b>"don\'t"</b>" record or profile any account data"</string>
<string name="screen_analytics_prompt_help_us_improve">"Help us identify issues and improve %1$s by sharing anonymous usage data."</string>
<string name="screen_analytics_prompt_read_terms">"You can read all our terms %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"here"</string>
<string name="screen_analytics_prompt_settings">"You can turn this off anytime in settings"</string>
<string name="screen_analytics_prompt_third_party_sharing">"We "<b>"don\'t"</b>" share information with third parties"</string>
<string name="screen_analytics_prompt_title">"Help improve %1$s"</string>
</resources>

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl
import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter
import io.element.android.features.analytics.test.A_BUILD_META
import io.element.android.features.analytics.test.FakeAnalyticsService
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import org.junit.Test
class AnalyticsOptInPresenterTest {
@Test
fun `present - enable`() = runTest {
val analyticsService = FakeAnalyticsService(isEnabled = false)
val presenter = AnalyticsOptInPresenter(
A_BUILD_META,
analyticsService
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(analyticsService.didAskUserConsent().first()).isFalse()
initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true))
assertThat(analyticsService.didAskUserConsent().first()).isTrue()
assertThat(analyticsService.getUserConsent().first()).isTrue()
}
}
@Test
fun `present - not now`() = runTest {
val analyticsService = FakeAnalyticsService(isEnabled = false)
val presenter = AnalyticsOptInPresenter(
A_BUILD_META,
analyticsService
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(analyticsService.didAskUserConsent().first()).isFalse()
initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false))
assertThat(analyticsService.didAskUserConsent().first()).isTrue()
assertThat(analyticsService.getUserConsent().first()).isFalse()
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.impl.preferences
import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.analytics.api.AnalyticsOptInEvents
import io.element.android.features.analytics.test.A_BUILD_META
import io.element.android.features.analytics.test.FakeAnalyticsService
import kotlinx.coroutines.test.runTest
import org.junit.Test
class AnalyticsPreferencesPresenterTest {
@Test
fun `present - initial state available`() = runTest {
val presenter = DefaultAnalyticsPreferencesPresenter(
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
A_BUILD_META
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.isEnabled).isTrue()
}
}
@Test
fun `present - initial state not available`() = runTest {
val presenter = DefaultAnalyticsPreferencesPresenter(
FakeAnalyticsService(isEnabled = false, didAskUserConsent = false),
A_BUILD_META
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.isEnabled).isFalse()
}
}
@Test
fun `present - enable and disable`() = runTest {
val presenter = DefaultAnalyticsPreferencesPresenter(
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
A_BUILD_META
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.isEnabled).isTrue()
initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false))
assertThat(awaitItem().isEnabled).isFalse()
initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true))
assertThat(awaitItem().isEnabled).isTrue()
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.features.analytics.test"
}
dependencies {
implementation(projects.services.analytics.api)
implementation(projects.libraries.core)
implementation(libs.coroutines.core)
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.test
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeAnalyticsService(
isEnabled: Boolean = false,
didAskUserConsent: Boolean = false
): AnalyticsService {
private var isEnabledFlow = MutableStateFlow(isEnabled)
private var didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
override fun getAvailableAnalyticsProviders(): List<AnalyticsProvider> = emptyList()
override fun getUserConsent(): Flow<Boolean> = isEnabledFlow
override suspend fun setUserConsent(userConsent: Boolean) {
isEnabledFlow.value = userConsent
}
override fun didAskUserConsent(): Flow<Boolean> = didAskUserConsentFlow
override suspend fun setDidAskUserConsent() {
didAskUserConsentFlow.value = true
}
override fun getAnalyticsId(): Flow<String> = MutableStateFlow("")
override suspend fun setAnalyticsId(analyticsId: String) {
}
override suspend fun onSignOut() {
}
override fun capture(event: VectorAnalyticsEvent) {
}
override fun screen(screen: VectorAnalyticsScreen) {
}
override fun updateUserProperties(userProperties: UserProperties) {
}
override fun trackError(throwable: Throwable) {
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 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 io.element.android.features.analytics.test
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType
val A_BUILD_META = BuildMeta(
isDebuggable = true,
buildType = BuildType.DEBUG,
applicationName = "Element X test",
applicationId = "",
lowPrivacyLoggingEnabled = false,
versionName = "",
gitRevision = "",
gitRevisionDate = "",
gitBranchName = "",
flavorDescription = "",
flavorShortDescription = "",
)

View File

@@ -42,6 +42,7 @@ dependencies {
implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings)
implementation(projects.features.rageshake.api)
implementation(projects.features.analytics.api)
implementation(projects.libraries.matrixui)
implementation(projects.features.logout.api)
implementation(libs.datetime)
@@ -59,6 +60,8 @@ dependencies {
testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.rageshake.impl)
testImplementation(projects.features.logout.impl)
testImplementation(projects.features.analytics.test)
testImplementation(projects.features.analytics.impl)
androidTestImplementation(libs.test.junitext)
}

View File

@@ -17,6 +17,7 @@
package io.element.android.features.preferences.impl.root
import androidx.compose.runtime.Composable
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter
import io.element.android.features.logout.api.LogoutPreferencePresenter
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
import io.element.android.libraries.architecture.Async
@@ -27,6 +28,7 @@ import javax.inject.Inject
class PreferencesRootPresenter @Inject constructor(
private val logoutPresenter: LogoutPreferencePresenter,
private val rageshakePresenter: RageshakePreferencesPresenter,
private val analyticsPresenter: AnalyticsPreferencesPresenter,
private val buildType: BuildType,
) : Presenter<PreferencesRootState> {
@@ -34,10 +36,12 @@ class PreferencesRootPresenter @Inject constructor(
override fun present(): PreferencesRootState {
val logoutState = logoutPresenter.present()
val rageshakeState = rageshakePresenter.present()
val analyticsState = analyticsPresenter.present()
val showDeveloperSettings = buildType != BuildType.RELEASE
return PreferencesRootState(
logoutState = logoutState,
rageshakeState = rageshakeState,
analyticsState = analyticsState,
myUser = Async.Uninitialized,
showDeveloperSettings = showDeveloperSettings
)

View File

@@ -16,6 +16,7 @@
package io.element.android.features.preferences.impl.root
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
import io.element.android.features.logout.api.LogoutPreferenceState
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
import io.element.android.libraries.architecture.Async
@@ -24,6 +25,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
data class PreferencesRootState(
val logoutState: LogoutPreferenceState,
val rageshakeState: RageshakePreferencesState,
val analyticsState: AnalyticsPreferencesState,
val myUser: Async<MatrixUser>,
val showDeveloperSettings: Boolean
)

View File

@@ -16,6 +16,7 @@
package io.element.android.features.preferences.impl.root
import io.element.android.features.analytics.api.preferences.aAnalyticsPreferencesState
import io.element.android.features.logout.api.aLogoutPreferenceState
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
import io.element.android.libraries.architecture.Async
@@ -23,6 +24,7 @@ import io.element.android.libraries.architecture.Async
fun aPreferencesRootState() = PreferencesRootState(
logoutState = aLogoutPreferenceState(),
rageshakeState = aRageshakePreferencesState(),
analyticsState = aAnalyticsPreferencesState(),
myUser = Async.Uninitialized,
showDeveloperSettings = true
)

View File

@@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.logout.api.LogoutPreferenceView
import io.element.android.features.preferences.impl.user.UserPreferences
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesView
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
@@ -52,6 +53,9 @@ fun PreferencesRootView(
title = stringResource(id = StringR.string.common_settings)
) {
UserPreferences(state.myUser)
AnalyticsPreferencesView(
state = state.analyticsState,
)
RageshakePreferencesView(
state = state.rageshakeState,
onOpenRageshake = onOpenRageShake,

View File

@@ -20,12 +20,14 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter
import io.element.android.features.analytics.test.A_BUILD_META
import io.element.android.features.analytics.test.FakeAnalyticsService
import io.element.android.features.logout.impl.DefaultLogoutPreferencePresenter
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.matrix.test.FakeMatrixClient
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -35,10 +37,12 @@ class PreferencesRootPresenterTest {
fun `present - initial state`() = runTest {
val logoutPresenter = DefaultLogoutPreferencePresenter(FakeMatrixClient())
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
val analyticsPresenter = DefaultAnalyticsPreferencesPresenter(FakeAnalyticsService(), A_BUILD_META)
val presenter = PreferencesRootPresenter(
logoutPresenter,
rageshakePresenter,
BuildType.DEBUG
analyticsPresenter,
A_BUILD_META.buildType
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@@ -46,6 +50,7 @@ class PreferencesRootPresenterTest {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.logoutState.logoutAction).isEqualTo(Async.Uninitialized)
assertThat(initialState.analyticsState.isEnabled).isFalse()
assertThat(initialState.rageshakeState.isEnabled).isTrue()
assertThat(initialState.rageshakeState.isSupported).isTrue()
assertThat(initialState.rageshakeState.sensitivity).isEqualTo(1.0f)

View File

@@ -41,6 +41,7 @@ showkase = "1.0.0-beta18"
jsoup = "1.16.1"
appyx = "1.2.0"
dependencycheck = "8.2.1"
dependencyanalysis = "1.20.0"
stem = "2.3.0"
sqldelight = "1.5.5"
telephoto = "0.4.0"
@@ -148,6 +149,11 @@ otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5"
vanniktech_blurhash = "com.vanniktech:blurhash:0.1.0"
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" }
# Analytics
posthog = "com.posthog.android:posthog:2.0.3"
sentry_android = "io.sentry:sentry-android:6.17.0"
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:main-SNAPSHOT"
# Di
inject = "javax.inject:javax.inject:1"
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
@@ -178,6 +184,7 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = "org.jlleitschuh.gradle.ktlint:11.3.2"
dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version.ref = "dependencygraph" }
dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" }
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyanalysis" }
paparazzi = "app.cash.paparazzi:1.2.0"
sonarqube = "org.sonarqube:4.2.0.3129"
kover = "org.jetbrains.kotlinx.kover:0.6.1"

View File

@@ -17,8 +17,8 @@
package io.element.android.libraries.push.api
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.providers.api.Distributor
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
interface PushService {
// TODO Move away

View File

@@ -22,8 +22,8 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager
import io.element.android.libraries.push.providers.api.Distributor
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import javax.inject.Inject

View File

@@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
import io.element.android.libraries.push.impl.config.PushConfig
import io.element.android.libraries.push.impl.log.pushLoggerTag
import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest
import io.element.android.libraries.push.providers.api.PusherSubscriber
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import io.element.android.services.toolbox.api.appname.AppNameProvider

View File

@@ -26,7 +26,6 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.push.impl.log.notificationLoggerTag
import io.element.android.services.analytics.api.AnalyticsTracker
import io.element.android.services.toolbox.api.systemclock.SystemClock
import timber.log.Timber
import javax.inject.Inject
@@ -41,7 +40,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
//@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var analyticsTracker: AnalyticsTracker
//@Inject lateinit var analyticsTracker: AnalyticsTracker
@Inject lateinit var clock: SystemClock
@Inject lateinit var actionIds: NotificationActionIds

View File

@@ -33,8 +33,8 @@ import io.element.android.libraries.push.impl.notifications.NotifiableEventResol
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
import io.element.android.libraries.push.impl.notifications.NotificationDrawerManager
import io.element.android.libraries.push.impl.store.DefaultPushDataStore
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.push.providers.api.PushHandler
import io.element.android.libraries.pushproviders.api.PushData
import io.element.android.libraries.pushproviders.api.PushHandler
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import kotlinx.coroutines.CoroutineScope

View File

@@ -18,7 +18,7 @@ plugins {
}
android {
namespace = "io.element.android.libraries.push.providers.api"
namespace = "io.element.android.libraries.pushproviders.api"
}
dependencies {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.api
package io.element.android.libraries.pushproviders.api
data class Distributor(
val value: String,

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.api
package io.element.android.libraries.pushproviders.api
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.api
package io.element.android.libraries.pushproviders.api
interface PushHandler {
suspend fun handle(pushData: PushData)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.api
package io.element.android.libraries.pushproviders.api
import io.element.android.libraries.matrix.api.MatrixClient

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.api
package io.element.android.libraries.pushproviders.api
import io.element.android.libraries.matrix.api.MatrixClient

View File

@@ -19,7 +19,7 @@ plugins {
}
android {
namespace = "io.element.android.libraries.push.providers.firebase"
namespace = "io.element.android.libraries.pushproviders.firebase"
}
anvil {

View File

@@ -21,7 +21,7 @@
android:name="firebase_analytics_collection_deactivated"
android:value="true" />
<service
android:name="io.element.android.libraries.push.providers.firebase.VectorFirebaseMessagingService"
android:name="io.element.android.libraries.pushproviders.firebase.VectorFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import javax.inject.Inject

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
object FirebaseConfig {
/**

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.push.providers.api.PusherSubscriber
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.api.toUserList

View File

@@ -14,9 +14,9 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.pushproviders.api.PushData
import javax.inject.Inject
class FirebasePushParser @Inject constructor() {

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.providers.api.Distributor
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.push.providers.api.PusherSubscriber
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import timber.log.Timber
import javax.inject.Inject

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import android.content.SharedPreferences
import androidx.core.content.edit

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import android.content.Context
import com.google.android.gms.common.ConnectionResult

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.pushproviders.api.PushData
/**
* In this case, the format is:

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.push.providers.api.PushHandler
import io.element.android.libraries.pushproviders.api.PushHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.libraries.di.AppScope

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.firebase
package io.element.android.libraries.pushproviders.firebase
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.pushproviders.api.PushData
import io.element.android.tests.testutils.assertThrowsInDebug
import org.junit.Test

View File

@@ -20,7 +20,7 @@ plugins {
}
android {
namespace = "io.element.android.libraries.push.providers.unifiedpush"
namespace = "io.element.android.libraries.pushproviders.unifiedpush"
}
anvil {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.BroadcastReceiver
import android.content.Context

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.pushproviders.api.PushData
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.providers.api.Distributor
import io.element.android.libraries.push.providers.api.PusherSubscriber
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import org.unifiedpush.android.connector.UnifiedPush
import javax.inject.Inject

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
object UnifiedPushConfig {
/**

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.network.RetrofitFactory
import io.element.android.libraries.push.providers.unifiedpush.network.UnifiedPushApi
import io.element.android.libraries.pushproviders.unifiedpush.network.UnifiedPushApi
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.net.URL

View File

@@ -14,11 +14,11 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.push.providers.api.PusherSubscriber
import io.element.android.libraries.pushproviders.api.PusherSubscriber
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import timber.log.Timber

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.pushproviders.api.PushData
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import javax.inject.Inject

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import com.squareup.anvil.annotations.ContributesMultibinding
@@ -22,8 +22,8 @@ import io.element.android.libraries.androidutils.system.getApplicationLabel
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.push.providers.api.Distributor
import io.element.android.libraries.push.providers.api.PushProvider
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import org.unifiedpush.android.connector.UnifiedPush
import javax.inject.Inject

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import android.content.SharedPreferences

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import io.element.android.libraries.di.ApplicationContext

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import android.content.Intent
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.push.providers.api.PushHandler
import io.element.android.libraries.pushproviders.api.PushHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.libraries.di.AppScope

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush.network
package io.element.android.libraries.pushproviders.unifiedpush.network
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush.network
package io.element.android.libraries.pushproviders.unifiedpush.network
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush.network
package io.element.android.libraries.pushproviders.unifiedpush.network
import retrofit2.http.GET

View File

@@ -14,12 +14,12 @@
* limitations under the License.
*/
package io.element.android.libraries.push.providers.unifiedpush
package io.element.android.libraries.pushproviders.unifiedpush
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.push.providers.api.PushData
import io.element.android.libraries.pushproviders.api.PushData
import io.element.android.tests.testutils.assertThrowsInDebug
import org.junit.Test

View File

@@ -134,13 +134,6 @@
<string name="room_timeline_beginning_of_room">"Dies ist der Anfang von %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Dies ist der Beginn dieser Konversation."</string>
<string name="room_timeline_read_marker_title">"Neu"</string>
<string name="screen_analytics_prompt_data_usage">"Wir erfassen und analysieren "<b>"keine"</b>" Account-Daten"</string>
<string name="screen_analytics_prompt_help_us_improve">"Helfen Sie uns, Probleme zu identifizieren und %1$s zu verbessern, indem Sie anonyme Nutzungsdaten weitergeben."</string>
<string name="screen_analytics_prompt_read_terms">"Sie können alle unsere Nutzerbedingungen %1$s lesen."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
<string name="screen_analytics_prompt_settings">"Sie können die Analyse jederzeit in den Einstellungen deaktivieren"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben "<b>"keine"</b>" Informationen an Dritte weiter"</string>
<string name="screen_analytics_prompt_title">"Helfen Sie %1$s zu verbessern"</string>
<string name="screen_analytics_settings_share_data">"Teile Analyse-Daten"</string>
<string name="screen_media_picker_error_failed_selection">"Medienauswahl fehlgeschlagen, bitte versuche es erneut."</string>
<string name="screen_report_content_block_user_hint">"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest"</string>

View File

@@ -141,13 +141,6 @@
<string name="room_timeline_beginning_of_room">"Acesta este începutul conversației %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Acesta este începutul acestei conversații."</string>
<string name="room_timeline_read_marker_title">"Nou"</string>
<string name="screen_analytics_prompt_data_usage"><b>"Nu"</b>" înregistrăm sau profilăm datele contului"</string>
<string name="screen_analytics_prompt_help_us_improve">"Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime."</string>
<string name="screen_analytics_prompt_read_terms">"Puteți citi toate condițiile noastre %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"aici"</string>
<string name="screen_analytics_prompt_settings">"Puteți dezactiva această opțiune oricând din setări"</string>
<string name="screen_analytics_prompt_third_party_sharing"><b>"Nu"</b>" împărtășim informații cu terți"</string>
<string name="screen_analytics_prompt_title">"Ajutați la îmbunătățirea %1$s"</string>
<string name="screen_analytics_settings_share_data">"Partajați datele analitice"</string>
<string name="screen_media_picker_error_failed_selection">"Selectarea fișierelor media a eșuat, încercați din nou."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Procesarea datelor media a eșuat, vă rugăm să încercați din nou."</string>

View File

@@ -145,13 +145,6 @@
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
<string name="room_timeline_read_marker_title">"New"</string>
<string name="screen_analytics_prompt_data_usage">"We "<b>"don\'t"</b>" record or profile any account data"</string>
<string name="screen_analytics_prompt_help_us_improve">"Help us identify issues and improve %1$s by sharing anonymous usage data."</string>
<string name="screen_analytics_prompt_read_terms">"You can read all our terms %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"here"</string>
<string name="screen_analytics_prompt_settings">"You can turn this off anytime in settings"</string>
<string name="screen_analytics_prompt_third_party_sharing">"We "<b>"don\'t"</b>" share information with third parties"</string>
<string name="screen_analytics_prompt_title">"Help improve %1$s"</string>
<string name="screen_analytics_settings_share_data">"Share analytics data"</string>
<string name="screen_media_picker_error_failed_selection">"Failed selecting media, please try again."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Failed processing media to upload, please try again."</string>

View File

@@ -101,7 +101,8 @@ fun DependencyHandlerScope.allLibrariesImpl() {
}
fun DependencyHandlerScope.allServicesImpl() {
implementation(project(":services:analytics:noop"))
implementation(project(":services:analytics:impl"))
implementation(project(":services:analyticsproviders:posthog"))
implementation(project(":services:apperror:impl"))
implementation(project(":services:appnavstate:impl"))
implementation(project(":services:toolbox:impl"))

View File

@@ -20,3 +20,8 @@ plugins {
android {
namespace = "io.element.android.services.analytics.api"
}
dependencies {
api(projects.services.analyticsproviders.api)
implementation(libs.coroutines.core)
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 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 io.element.android.services.analytics.api
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
import io.element.android.services.analyticsproviders.api.trackers.AnalyticsTracker
import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker
import kotlinx.coroutines.flow.Flow
interface AnalyticsService: AnalyticsTracker, ErrorTracker {
fun getAvailableAnalyticsProviders(): List<AnalyticsProvider>
/**
* Return a Flow of Boolean, true if the user has given their consent.
*/
fun getUserConsent(): Flow<Boolean>
/**
* Update the user consent value.
*/
suspend fun setUserConsent(userConsent: Boolean)
/**
* Return a Flow of Boolean, true if the user has been asked for their consent.
*/
fun didAskUserConsent(): Flow<Boolean>
/**
* Store the fact that the user has been asked for their consent.
*/
suspend fun setDidAskUserConsent()
/**
* Return a Flow of String, used for analytics Id.
*/
fun getAnalyticsId(): Flow<String>
/**
* Update analyticsId from the AccountData.
*/
suspend fun setAnalyticsId(analyticsId: String)
/**
* To be called when a session is destroyed.
*/
suspend fun onSignOut()
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when a call has ended.
*/
data class CallEnded(
/**
* The duration of the call in milliseconds.
*/
val durationMs: Int,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {
override fun getName() = "CallEnded"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("durationMs", durationMs)
put("isVideo", isVideo)
put("numParticipants", numParticipants)
put("placed", placed)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when an error occurred in a call.
*/
data class CallError(
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {
override fun getName() = "CallError"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("isVideo", isVideo)
put("numParticipants", numParticipants)
put("placed", placed)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when a call is started.
*/
data class CallStarted(
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {
override fun getName() = "CallStarted"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("isVideo", isVideo)
put("numParticipants", numParticipants)
put("placed", placed)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user sends a message via the composer.
*/
data class Composer(
/**
* Whether the user was using the composer inside of a thread.
*/
val inThread: Boolean,
/**
* Whether the user's composer interaction was editing a previously sent
* event.
*/
val isEditing: Boolean,
/**
* Whether the user's composer interaction was a reply to a previously
* sent event.
*/
val isReply: Boolean,
/**
* Whether this message begins a new thread or not.
*/
val startsThread: Boolean? = null,
) : VectorAnalyticsEvent {
override fun getName() = "Composer"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("inThread", inThread)
put("isEditing", isEditing)
put("isReply", isReply)
startsThread?.let { put("startsThread", it) }
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user creates a room.
*/
data class CreatedRoom(
/**
* Whether the room is a DM.
*/
val isDM: Boolean,
) : VectorAnalyticsEvent {
override fun getName() = "CreatedRoom"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("isDM", isDM)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when an error occurred.
*/
data class Error(
/**
* Context - client defined, can be used for debugging.
*/
val context: String? = null,
/**
* Which crypto module is the client currently using.
*/
val cryptoModule: CryptoModule? = null,
val domain: Domain,
val name: Name,
) : VectorAnalyticsEvent {
enum class Domain {
E2EE,
TO_DEVICE,
VOIP,
}
enum class Name {
OlmIndexError,
OlmKeysNotSentError,
OlmUnspecifiedError,
ToDeviceFailedToDecrypt,
UnknownError,
VoipIceFailed,
VoipIceTimeout,
VoipInviteTimeout,
VoipUserHangup,
VoipUserMediaFailed,
}
enum class CryptoModule {
/**
* Native / legacy crypto module specific to each platform.
*/
Native,
/**
* Shared / cross-platform crypto module written in Rust.
*/
Rust,
}
override fun getName() = "Error"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
context?.let { put("context", it) }
cryptoModule?.let { put("cryptoModule", it.name) }
put("domain", domain.name)
put("name", name.name)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,468 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user clicks/taps/activates a UI element.
*/
data class Interaction(
/**
* The index of the element, if its in a list of elements.
*/
val index: Int? = null,
/**
* The manner with which the user activated the UI element.
*/
val interactionType: InteractionType? = null,
/**
* The unique name of this element.
*/
val name: Name,
) : VectorAnalyticsEvent {
enum class Name {
/**
* User tapped the All filter in the All Chats filter tab.
*/
MobileAllChatsFilterAll,
/**
* User tapped the Favourites filter in the All Chats filter tab.
*/
MobileAllChatsFilterFavourites,
/**
* User tapped the People filter in the All Chats filter tab.
*/
MobileAllChatsFilterPeople,
/**
* User tapped the Unreads filter in the All Chats filter tab.
*/
MobileAllChatsFilterUnreads,
/**
* User disabled filters from the all chats layout settings.
*/
MobileAllChatsFiltersDisabled,
/**
* User enabled filters from the all chats layout settings.
*/
MobileAllChatsFiltersEnabled,
/**
* User disabled recents from the all chats layout settings.
*/
MobileAllChatsRecentsDisabled,
/**
* User enabled recents from the all chats layout settings.
*/
MobileAllChatsRecentsEnabled,
/**
* User tapped on Add to Home button on Room Details screen.
*/
MobileRoomAddHome,
/**
* User tapped on Leave Room button on Room Details screen.
*/
MobileRoomLeave,
/**
* User tapped on Threads button on Room screen.
*/
MobileRoomThreadListButton,
/**
* User tapped on a thread summary item on Room screen.
*/
MobileRoomThreadSummaryItem,
/**
* User validated the creation of a new space.
*/
MobileSpaceCreationValidated,
/**
* User tapped on the filter button on ThreadList screen.
*/
MobileThreadListFilterItem,
/**
* User selected a thread on ThreadList screen.
*/
MobileThreadListThreadItem,
/**
* User tapped the already selected space from the space list.
*/
SpacePanelSelectedSpace,
/**
* User tapped an unselected space from the space list -> space
* switching should occur.
*/
SpacePanelSwitchSpace,
/**
* User tapped an unselected sub space from the space list -> space
* switching should occur.
*/
SpacePanelSwitchSubSpace,
/**
* User clicked the create room button in the add existing room to space
* dialog in Element Web/Desktop.
*/
WebAddExistingToSpaceDialogCreateRoomButton,
/**
* User clicked the create DM button in the home page of Element
* Web/Desktop.
*/
WebHomeCreateChatButton,
/**
* User clicked the create room button in the home page of Element
* Web/Desktop.
*/
WebHomeCreateRoomButton,
/**
* User clicked the explore rooms button in the home page of Element
* Web/Desktop.
*/
WebHomeExploreRoomsButton,
/**
* User clicked on the mini avatar uploader in the home page of Element
* Web/Desktop.
*/
WebHomeMiniAvatarUploadButton,
/**
* User clicked the explore rooms button next to the search field at the
* top of the left panel in Element Web/Desktop.
*/
WebLeftPanelExploreRoomsButton,
/**
* User clicked on the avatar uploader in the profile settings of
* Element Web/Desktop.
*/
WebProfileSettingsAvatarUploadButton,
/**
* User interacted with pin to sidebar checkboxes in the quick settings
* menu of Element Web/Desktop.
*/
WebQuickSettingsPinToSidebarCheckbox,
/**
* User interacted with the theme dropdown in the quick settings menu of
* Element Web/Desktop.
*/
WebQuickSettingsThemeDropdown,
/**
* User accessed the room invite flow using the button at the top of the
* room member list in the right panel of Element Web/Desktop.
*/
WebRightPanelMemberListInviteButton,
/**
* User accessed room member list using the 'People' button in the right
* panel room summary card of Element Web/Desktop.
*/
WebRightPanelRoomInfoPeopleButton,
/**
* User accessed room settings using the 'Settings' button in the right
* panel room summary card of Element Web/Desktop.
*/
WebRightPanelRoomInfoSettingsButton,
/**
* User accessed room member list using the back button in the right
* panel user info card of Element Web/Desktop.
*/
WebRightPanelRoomUserInfoBackButton,
/**
* User invited someone to room by clicking invite on the right panel
* user info card in Element Web/Desktop.
*/
WebRightPanelRoomUserInfoInviteButton,
/**
* User clicked the threads 'show' filter dropdown in the threads panel
* in Element Web/Desktop.
*/
WebRightPanelThreadPanelFilterDropdown,
/**
* User clicked the create room button in the room directory of Element
* Web/Desktop.
*/
WebRoomDirectoryCreateRoomButton,
/**
* User clicked the Threads button in the top right of a room in Element
* Web/Desktop.
*/
WebRoomHeaderButtonsThreadsButton,
/**
* User adjusted their favourites using the context menu on the header
* of a room in Element Web/Desktop.
*/
WebRoomHeaderContextMenuFavouriteToggle,
/**
* User accessed the room invite flow using the context menu on the
* header of a room in Element Web/Desktop.
*/
WebRoomHeaderContextMenuInviteItem,
/**
* User interacted with leave action in the context menu on the header
* of a room in Element Web/Desktop.
*/
WebRoomHeaderContextMenuLeaveItem,
/**
* User accessed their room notification settings via the context menu
* on the header of a room in Element Web/Desktop.
*/
WebRoomHeaderContextMenuNotificationsItem,
/**
* User accessed room member list using the context menu on the header
* of a room in Element Web/Desktop.
*/
WebRoomHeaderContextMenuPeopleItem,
/**
* User accessed room settings using the context menu on the header of a
* room in Element Web/Desktop.
*/
WebRoomHeaderContextMenuSettingsItem,
/**
* User clicked the create DM button in the + context menu of the room
* list header in Element Web/Desktop.
*/
WebRoomListHeaderPlusMenuCreateChatItem,
/**
* User clicked the create room button in the + context menu of the room
* list header in Element Web/Desktop.
*/
WebRoomListHeaderPlusMenuCreateRoomItem,
/**
* User clicked the explore rooms button in the + context menu of the
* room list header in Element Web/Desktop.
*/
WebRoomListHeaderPlusMenuExploreRoomsItem,
/**
* User adjusted their favourites using the context menu on a room tile
* in the room list in Element Web/Desktop.
*/
WebRoomListRoomTileContextMenuFavouriteToggle,
/**
* User accessed the room invite flow using the context menu on a room
* tile in the room list in Element Web/Desktop.
*/
WebRoomListRoomTileContextMenuInviteItem,
/**
* User interacted with leave action in the context menu on a room tile
* in the room list in Element Web/Desktop.
*/
WebRoomListRoomTileContextMenuLeaveItem,
/**
* User accessed room settings using the context menu on a room tile in
* the room list in Element Web/Desktop.
*/
WebRoomListRoomTileContextMenuSettingsItem,
/**
* User accessed their room notification settings via the context menu
* on a room tile in the room list in Element Web/Desktop.
*/
WebRoomListRoomTileNotificationsMenu,
/**
* User clicked the create DM button in the + context menu of the rooms
* sublist in Element Web/Desktop.
*/
WebRoomListRoomsSublistPlusMenuCreateChatItem,
/**
* User clicked the create room button in the + context menu of the
* rooms sublist in Element Web/Desktop.
*/
WebRoomListRoomsSublistPlusMenuCreateRoomItem,
/**
* User clicked the explore rooms button in the + context menu of the
* rooms sublist in Element Web/Desktop.
*/
WebRoomListRoomsSublistPlusMenuExploreRoomsItem,
/**
* User clicked on the button to return to the user onboarding list in
* the room list in Element Web/Desktop.
*/
WebRoomListUserOnboardingButton,
/**
* User clicked on the button to close the user onboarding button in the
* room list in Element Web/Desktop.
*/
WebRoomListUserOnboardingIgnoreButton,
/**
* User interacted with leave action in the general tab of the room
* settings dialog in Element Web/Desktop.
*/
WebRoomSettingsLeaveButton,
/**
* User interacted with the prompt to create a new room when adjusting
* security settings in an existing room in Element Web/Desktop.
*/
WebRoomSettingsSecurityTabCreateNewRoomButton,
/**
* User clicked a thread summary in the timeline of a room in Element
* Web/Desktop.
*/
WebRoomTimelineThreadSummaryButton,
/**
* User interacted with the theme radio selector in the Appearance tab
* of Settings in Element Web/Desktop.
*/
WebSettingsAppearanceTabThemeSelector,
/**
* User interacted with the pre-built space checkboxes in the Sidebar
* tab of Settings in Element Web/Desktop.
*/
WebSettingsSidebarTabSpacesCheckbox,
/**
* User clicked the explore rooms button in the context menu of a space
* in Element Web/Desktop.
*/
WebSpaceContextMenuExploreRoomsItem,
/**
* User clicked the home button in the context menu of a space in
* Element Web/Desktop.
*/
WebSpaceContextMenuHomeItem,
/**
* User clicked the new room button in the context menu of a space in
* Element Web/Desktop.
*/
WebSpaceContextMenuNewRoomItem,
/**
* User clicked the new room button in the context menu on the space
* home in Element Web/Desktop.
*/
WebSpaceHomeCreateRoomButton,
/**
* User clicked the back button on a Thread view going back to the
* Threads Panel of Element Web/Desktop.
*/
WebThreadViewBackButton,
/**
* User selected a thread in the Threads panel in Element Web/Desktop.
*/
WebThreadsPanelThreadItem,
/**
* User clicked the theme toggle button in the user menu of Element
* Web/Desktop.
*/
WebUserMenuThemeToggleButton,
/**
* User clicked on the send DM CTA in the header of the new user
* onboarding page in Element Web/Desktop.
*/
WebUserOnboardingHeaderSendDm,
/**
* User clicked on the action of the download apps task on the new user
* onboarding page in Element Web/Desktop.
*/
WebUserOnboardingTaskDownloadApps,
/**
* User clicked on the action of the enable notifications task on the
* new user onboarding page in Element Web/Desktop.
*/
WebUserOnboardingTaskEnableNotifications,
/**
* User clicked on the action of the find people task on the new user
* onboarding page in Element Web/Desktop.
*/
WebUserOnboardingTaskSendDm,
/**
* User clicked on the action of the your profile task on the new user
* onboarding page in Element Web/Desktop.
*/
WebUserOnboardingTaskSetupProfile,
}
enum class InteractionType {
Keyboard,
Pointer,
Touch,
}
override fun getName() = "Interaction"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
index?.let { put("index", it) }
interactionType?.let { put("interactionType", it.name) }
put("name", name.name)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user joins a room.
*/
data class JoinedRoom(
/**
* Whether the room is a DM.
*/
val isDM: Boolean,
/**
* Whether the room is a Space.
*/
val isSpace: Boolean,
/**
* The size of the room.
*/
val roomSize: RoomSize,
/**
* The trigger for a room being joined if known.
*/
val trigger: Trigger? = null,
) : VectorAnalyticsEvent {
enum class Trigger {
/**
* Room joined via an invite.
*/
Invite,
/**
* Room joined via link.
*/
MobilePermalink,
/**
* Room joined via a push/desktop notification.
*/
Notification,
/**
* Room joined via the public rooms directory.
*/
RoomDirectory,
/**
* Room joined via its preview.
*/
RoomPreview,
/**
* Room joined via the /join slash command.
*/
SlashCommand,
/**
* Room joined via the space hierarchy view.
*/
SpaceHierarchy,
/**
* Room joined via a timeline pill or link in another room.
*/
Timeline,
}
enum class RoomSize {
ElevenToOneHundred,
MoreThanAThousand,
One,
OneHundredAndOneToAThousand,
ThreeToTen,
Two,
}
override fun getName() = "JoinedRoom"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("isDM", isDM)
put("isSpace", isSpace)
put("roomSize", roomSize.name)
trigger?.let { put("trigger", it.name) }
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,327 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsScreen
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user changed screen on Element Android/iOS.
*/
data class MobileScreen(
/**
* How long the screen was displayed for in milliseconds.
*/
val durationMs: Int? = null,
val screenName: ScreenName,
) : VectorAnalyticsScreen {
enum class ScreenName {
/**
* The screen that displays the user's breadcrumbs.
*/
Breadcrumbs,
/**
* The screen shown to create a new (non-direct) room.
*/
CreateRoom,
/**
* The screen shown to create a new space.
*/
CreateSpace,
/**
* The confirmation screen shown before deactivating an account.
*/
DeactivateAccount,
/**
* The tab on mobile that displays the dialpad.
*/
Dialpad,
/**
* The Favourites tab on mobile that lists your favourite people/rooms.
*/
Favourites,
/**
* The form for the forgot password use case.
*/
ForgotPassword,
/**
* Legacy: The screen that shows information about a specific group.
*/
Group,
/**
* The Home tab on iOS | possibly the same on Android?
*/
Home,
/**
* The screen shown to share a link to download the app.
*/
InviteFriends,
/**
* Room accessed via space bottom sheet list.
*/
Invites,
/**
* The screen that displays the login flow (when the user already has an
* account).
*/
Login,
/**
* Legacy: The screen that shows all groups/communities you have joined.
*/
MyGroups,
/**
* The People tab on mobile that lists all the DM rooms you have joined.
*/
People,
/**
* The screen that displays the registration flow (when the user wants
* to create an account).
*/
Register,
/**
* The screen that displays the messages and events received in a room.
*/
Room,
/**
* The room addresses screen shown from the Room Details screen.
*/
RoomAddresses,
/**
* The screen shown when tapping the name of a room from the Room
* screen.
*/
RoomDetailss,
/**
* The screen that lists public rooms for you to discover.
*/
RoomDirectory,
/**
* The screen that lists all the user's rooms and let them filter the
* rooms.
*/
RoomFilter,
/**
* The screen that displays the list of members that are part of a room.
*/
RoomMembers,
/**
* The notifications settings screen shown from the Room Details screen.
*/
RoomNotifications,
/**
* The roles permissions screen shown from the Room Details screen.
*/
RoomPermissions,
/**
* Screen that displays room preview if user hasn't joined yet.
*/
RoomPreview,
/**
* The screen that allows you to search for messages/files in a specific
* room.
*/
RoomSearch,
/**
* The settings screen shown from the Room Details screen.
*/
RoomSettings,
/**
* The screen that allows you to see all of the files sent in a specific
* room.
*/
RoomUploads,
/**
* The Rooms tab on mobile that lists all the (non-direct) rooms you've
* joined.
*/
Rooms,
/**
* The Files tab shown in the global search screen on Mobile.
*/
SearchFiles,
/**
* The Messages tab shown in the global search screen on Mobile.
*/
SearchMessages,
/**
* The People tab shown in the global search screen on Mobile.
*/
SearchPeople,
/**
* The Rooms tab shown in the global search screen on Mobile.
*/
SearchRooms,
/**
* The global settings screen shown in the app.
*/
Settings,
/**
* The advanced settings screen (developer mode, rageshake, push
* notification rules).
*/
SettingsAdvanced,
/**
* The settings screen to change the default notification options.
*/
SettingsDefaultNotifications,
/**
* The settings screen with general profile settings.
*/
SettingsGeneral,
/**
* The Help and About screen.
*/
SettingsHelp,
/**
* The settings screen with list of the ignored users.
*/
SettingsIgnoredUsers,
/**
* The experimental features settings screen.
*/
SettingsLabs,
/**
* The settings screen with legals information.
*/
SettingsLegals,
/**
* The settings screen to manage notification mentions and keywords.
*/
SettingsMentionsAndKeywords,
/**
* The notifications settings screen.
*/
SettingsNotifications,
/**
* The preferences screen (theme, language, editor preferences, etc.
*/
SettingsPreferences,
/**
* The global security settings screen.
*/
SettingsSecurity,
/**
* The calls settings screen.
*/
SettingsVoiceVideo,
/**
* The sidebar shown on mobile with spaces, settings etc.
*/
Sidebar,
/**
* Room accessed via space bottom sheet list.
*/
SpaceBottomSheet,
/**
* Screen that displays the list of rooms and spaces of a space.
*/
SpaceExploreRooms,
/**
* Screen that displays the list of members of a space.
*/
SpaceMembers,
/**
* The bottom sheet that list all space options.
*/
SpaceMenu,
/**
* The screen shown to create a new direct room.
*/
StartChat,
/**
* The screen shown to select which room directory you'd like to use.
*/
SwitchDirectory,
/**
* Screen that displays list of threads for a room.
*/
ThreadList,
/**
* A screen that shows information about a room member.
*/
User,
/**
* The splash screen.
*/
Welcome,
}
override fun getName() = screenName.name
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
durationMs?.let { put("durationMs", it) }
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered after timing an operation in the app.
*/
data class PerformanceTimer(
/**
* Client defined, can be used for debugging.
*/
val context: String? = null,
/**
* Client defined, an optional value to indicate how many items were
* handled during the operation.
*/
val itemCount: Int? = null,
/**
* The timer that is being reported.
*/
val name: Name,
/**
* The time reported by the timer in milliseconds.
*/
val timeMs: Int,
) : VectorAnalyticsEvent {
enum class Name {
/**
* The time spent parsing the response from an initial /sync request. In
* this case, `itemCount` should contain the number of joined rooms.
*/
InitialSyncParsing,
/**
* The time spent waiting for a response to an initial /sync request. In
* this case, `itemCount` should contain the number of joined rooms.
*/
InitialSyncRequest,
/**
* The time taken to display an event in the timeline that was opened
* from a notification.
*/
NotificationsOpenEvent,
/**
* The duration of a regular /sync request when resuming the app. In
* this case, `itemCount` should contain the number of joined rooms in
* the response.
*/
StartupIncrementalSync,
/**
* The duration of an initial /sync request during startup (if the store
* has been wiped). In this case, `itemCount` should contain the number
* of joined rooms.
*/
StartupInitialSync,
/**
* How long the app launch screen is displayed for.
*/
StartupLaunchScreen,
/**
* The time to preload data in the MXStore on iOS. In this case,
* `itemCount` should contain the number of rooms in the store.
*/
StartupStorePreload,
/**
* The time to load all data from the store (including
* StartupStorePreload time). In this case, `itemCount` should contain
* the number of rooms loaded into the session
*/
StartupStoreReady,
}
override fun getName() = "PerformanceTimer"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
context?.let { put("context", it) }
itemCount?.let { put("itemCount", it) }
put("name", name.name)
put("timeMs", timeMs)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user changes a permission status.
*/
data class PermissionChanged(
/**
* Whether the permission has been granted by the user.
*/
val granted: Boolean,
/**
* The name of the permission.
*/
val permission: Permission,
) : VectorAnalyticsEvent {
enum class Permission {
/**
* Permissions related to sending notifications have changed.
*/
Notification,
}
override fun getName() = "PermissionChanged"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("granted", granted)
put("permission", permission.name)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered once onboarding has completed, but only if the user registered a
* new account.
*/
data class Signup(
/**
* The type of authentication that was used to sign up.
*/
val authenticationType: AuthenticationType,
) : VectorAnalyticsEvent {
enum class AuthenticationType {
/**
* Social login using Apple.
*/
Apple,
/**
* Social login using Facebook.
*/
Facebook,
/**
* Social login using GitHub.
*/
GitHub,
/**
* Social login using GitLab.
*/
GitLab,
/**
* Social login using Google.
*/
Google,
/**
* Registration using some other mechanism such as fallback.
*/
Other,
/**
* Registration with a username and password.
*/
Password,
/**
* Registration using another SSO provider.
*/
SSO,
}
override fun getName() = "Signup"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("authenticationType", authenticationType.name)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user runs a slash command in their composer.
*/
data class SlashCommand(
/**
* The name of this command.
*/
val command: Command,
) : VectorAnalyticsEvent {
enum class Command {
Invite,
Part,
}
override fun getName() = "SlashCommand"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("command", command.name)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2021 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 io.element.android.services.analytics.api.plan
import io.element.android.services.analytics.api.VectorAnalyticsEvent
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* Triggered when the user becomes unauthenticated without actually clicking
* sign out(E.g. Due to expiry of an access token without a way to refresh).
*/
data class UnauthenticatedError(
/**
* The error code as defined in matrix spec. The source of this error is
* from the homeserver.
*/
val errorCode: ErrorCode,
/**
* The reason for the error. The source of this error is from the
* homeserver, the reason can vary and is subject to change so there is
* no enum of possible values.
*/
val errorReason: String,
/**
* Whether the auth mechanism is refresh-token-based.
*/
val refreshTokenAuth: Boolean,
/**
* Whether a soft logout or hard logout was triggered.
*/
val softLogout: Boolean,
) : VectorAnalyticsEvent {
enum class ErrorCode {
M_FORBIDDEN,
M_UNKNOWN,
M_UNKNOWN_TOKEN,
}
override fun getName() = "UnauthenticatedError"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("errorCode", errorCode.name)
put("errorReason", errorReason)
put("refreshTokenAuth", refreshTokenAuth)
put("softLogout", softLogout)
}.takeIf { it.isNotEmpty() }
}
}

View File

@@ -1,98 +0,0 @@
/*
* Copyright (c) 2023 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 io.element.android.services.analytics.api.plan
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
// https://github.com/matrix-org/matrix-analytics-events/
/**
* The user properties to apply when identifying. This is not an event
* definition. These properties must all be device independent.
*/
data class UserProperties(
/**
* The active filter in the All Chats screen.
*/
val allChatsActiveFilter: AllChatsActiveFilter? = null,
/**
* The selected messaging use case during the onboarding flow.
*/
val ftueUseCaseSelection: FtueUseCaseSelection? = null,
/**
* Number of joined rooms the user has favourited.
*/
val numFavouriteRooms: Int? = null,
/**
* Number of spaces (and sub-spaces) the user is joined to.
*/
val numSpaces: Int? = null,
) {
enum class FtueUseCaseSelection {
/**
* The third option, Communities.
*/
CommunityMessaging,
/**
* The first option, Friends and family.
*/
PersonalMessaging,
/**
* The footer option to skip the question.
*/
Skip,
/**
* The second option, Teams.
*/
WorkMessaging,
}
enum class AllChatsActiveFilter {
/**
* Filters are activated and All is selected.
*/
All,
/**
* Filters are activated and Favourites is selected.
*/
Favourites,
/**
* Filters are activated and People is selected.
*/
People,
/**
* Filters are activated and Unreads is selected.
*/
Unreads,
}
fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
allChatsActiveFilter?.let { put("allChatsActiveFilter", it.name) }
ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) }
numFavouriteRooms?.let { put("numFavouriteRooms", it) }
numSpaces?.let { put("numSpaces", it) }
}.takeIf { it.isNotEmpty() }
}
}

Some files were not shown because too many files have changed in this diff Show More