From 8c794b10591eceec02bdea0fa26b9ab81ee3d907 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 6 Dec 2021 10:52:08 +0100 Subject: [PATCH] Analytics: Opt-in screen logic + remove from SplashScreen --- vector/build.gradle | 2 + vector/src/main/AndroidManifest.xml | 1 + .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../ui/consent/AnalyticsConsentViewActions.kt | 1 - .../ui/consent/AnalyticsConsentViewModel.kt | 15 +---- .../ui/consent/AnalyticsOptInActivity.kt | 49 ++++++++++++++ .../ui/consent/AnalyticsOptInFragment.kt | 67 +++++++++++++++++++ .../ui/consent/AnalyticsOptInViewEvents.kt | 23 +++++++ .../app/features/login/LoginSplashFragment.kt | 22 ------ .../features/navigation/DefaultNavigator.kt | 5 ++ .../app/features/navigation/Navigator.kt | 2 + .../main/res/layout/fragment_login_splash.xml | 13 +--- 12 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInViewEvents.kt diff --git a/vector/build.gradle b/vector/build.gradle index 2fd562fff5..fb1199a497 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -236,6 +236,7 @@ android { // Analytics. Set to empty strings to just disable analytics buildConfigField "String", "ANALYTICS_POSTHOG_HOST", "\"https://posthog-poc.lab.element.dev\"" buildConfigField "String", "ANALYTICS_POSTHOG_API_KEY", "\"rs-pJjsYJTuAkXJfhaMmPUNBhWliDyTKLOOxike6ck8\"" + buildConfigField "String", "ANALYTICS_POLICY_URL", "\"https://element.io/cookie-policy\"" signingConfig signingConfigs.debug } @@ -250,6 +251,7 @@ android { // Analytics. Set to empty strings to just disable analytics buildConfigField "String", "ANALYTICS_POSTHOG_HOST", "\"https://posthog.hss.element.io\"" buildConfigField "String", "ANALYTICS_POSTHOG_API_KEY", "\"phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO\"" + buildConfigField "String", "ANALYTICS_POLICY_URL", "\"https://element.io/cookie-policy\"" postprocessing { removeUnusedCode true diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 5b56107ef7..7bd9f66b8e 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -310,6 +310,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index b8d00fac5a..dcf3d6d113 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -24,6 +24,7 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityComponent import dagger.multibindings.IntoMap +import im.vector.app.features.analytics.ui.consent.AnalyticsOptInFragment import im.vector.app.features.attachments.preview.AttachmentsPreviewFragment import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsFragment @@ -519,6 +520,11 @@ interface FragmentModule { @FragmentKey(BreadcrumbsFragment::class) fun bindBreadcrumbsFragment(fragment: BreadcrumbsFragment): Fragment + @Binds + @IntoMap + @FragmentKey(AnalyticsOptInFragment::class) + fun bindAnalyticsOptInFragment(fragment: AnalyticsOptInFragment): Fragment + @Binds @IntoMap @FragmentKey(EmojiChooserFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewActions.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewActions.kt index b43dc4742a..058ddcba27 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewActions.kt @@ -20,5 +20,4 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class AnalyticsConsentViewActions : VectorViewModelAction { data class SetUserConsent(val userConsent: Boolean) : AnalyticsConsentViewActions() - object OnGetStarted : AnalyticsConsentViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt index 4fdbf08c5f..2c7a8ac9bc 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsConsentViewModel.kt @@ -23,7 +23,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.VectorAnalytics import kotlinx.coroutines.launch @@ -31,7 +30,7 @@ import kotlinx.coroutines.launch class AnalyticsConsentViewModel @AssistedInject constructor( @Assisted initialState: AnalyticsConsentViewState, private val analytics: VectorAnalytics -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -56,24 +55,14 @@ class AnalyticsConsentViewModel @AssistedInject constructor( override fun handle(action: AnalyticsConsentViewActions) { when (action) { is AnalyticsConsentViewActions.SetUserConsent -> handleSetUserConsent(action) - AnalyticsConsentViewActions.OnGetStarted -> handleOnScreenLeft() }.exhaustive } private fun handleSetUserConsent(action: AnalyticsConsentViewActions.SetUserConsent) { viewModelScope.launch { analytics.setUserConsent(action.userConsent) - if (!action.userConsent) { - // User explicitly changed the default value, let's avoid reverting to the default value - analytics.setDidAskUserConsent() - } - } - } - - private fun handleOnScreenLeft() { - // Whatever the state of the box, consider the user acknowledge it - viewModelScope.launch { analytics.setDidAskUserConsent() + _viewEvents.post(AnalyticsOptInViewEvents.OnDataSaved) } } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt new file mode 100644 index 0000000000..5fca7476f2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.analytics.ui.consent + +import com.airbnb.mvrx.viewModel +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding + +/** + * Simple container for AnalyticsOptInFragment + */ +@AndroidEntryPoint +class AnalyticsOptInActivity : VectorBaseActivity() { + + private val viewModel: AnalyticsConsentViewModel by viewModel() + + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + override fun initUiAndData() { + if (isFirstCreation()) { + addFragment(R.id.simpleFragmentContainer, AnalyticsOptInFragment::class.java) + } + + viewModel.observeViewEvents { + when (it) { + AnalyticsOptInViewEvents.OnDataSaved -> finish() + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt new file mode 100644 index 0000000000..dded898961 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInFragment.kt @@ -0,0 +1,67 @@ +/* + * 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 im.vector.app.features.analytics.ui.consent + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.BuildConfig +import im.vector.app.R +import im.vector.app.core.extensions.setTextWithColoredPart +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.openUrlInChromeCustomTab +import im.vector.app.databinding.FragmentAnalyticsOptinBinding +import javax.inject.Inject + +class AnalyticsOptInFragment @Inject constructor( +) : VectorBaseFragment() { + + // Share the view model with the Activity so that the Activity + // can decide what to do when the data has been saved + private val viewModel: AnalyticsConsentViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentAnalyticsOptinBinding { + return FragmentAnalyticsOptinBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupLink() + setupListeners() + } + + private fun setupListeners() { + views.submit.debouncedClicks { + viewModel.handle(AnalyticsConsentViewActions.SetUserConsent(userConsent = true)) + } + views.later.debouncedClicks { + viewModel.handle(AnalyticsConsentViewActions.SetUserConsent(userConsent = false)) + } + } + + private fun setupLink() { + views.submit.setTextWithColoredPart( + fullTextRes = R.string.analytics_opt_in_content, + coloredTextRes = R.string.analytics_opt_in_content_link, + onClick = { + openUrlInChromeCustomTab(requireContext(), null, BuildConfig.ANALYTICS_POLICY_URL) + } + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInViewEvents.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInViewEvents.kt new file mode 100644 index 0000000000..d73f472876 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInViewEvents.kt @@ -0,0 +1,23 @@ +/* + * 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 im.vector.app.features.analytics.ui.consent + +import im.vector.app.core.platform.VectorViewEvents + +sealed interface AnalyticsOptInViewEvents : VectorViewEvents { + object OnDataSaved : AnalyticsOptInViewEvents +} diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt index e045ba11b1..527f9f99b3 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt @@ -22,15 +22,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import com.airbnb.mvrx.fragmentViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.databinding.FragmentLoginSplashBinding -import im.vector.app.features.analytics.AnalyticsConfig -import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewActions -import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel -import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewState import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.failure.Failure import java.net.UnknownHostException @@ -43,8 +38,6 @@ class LoginSplashFragment @Inject constructor( private val vectorPreferences: VectorPreferences ) : AbstractLoginFragment() { - private val analyticsConsentViewModel: AnalyticsConsentViewModel by fragmentViewModel() - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplashBinding { return FragmentLoginSplashBinding.inflate(inflater, container, false) } @@ -53,24 +46,10 @@ class LoginSplashFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) setupViews() - observeAnalyticsState() - } - - private fun observeAnalyticsState() { - analyticsConsentViewModel.onEach(AnalyticsConsentViewState::shouldCheckTheBox) { - views.loginSplashAnalyticsConsent.isChecked = it - } } private fun setupViews() { views.loginSplashSubmit.debouncedClicks { getStarted() } - // setOnCheckedChangeListener is to annoying since it does not distinguish user changes and code changes - views.loginSplashAnalyticsConsent.isVisible = AnalyticsConfig.isAnalyticsEnabled() - views.loginSplashAnalyticsConsent.setOnClickListener { - analyticsConsentViewModel.handle(AnalyticsConsentViewActions.SetUserConsent( - views.loginSplashAnalyticsConsent.isChecked - )) - } if (BuildConfig.DEBUG || vectorPreferences.developerMode()) { views.loginSplashVersion.isVisible = true @@ -82,7 +61,6 @@ class LoginSplashFragment @Inject constructor( } private fun getStarted() { - analyticsConsentViewModel.handle(AnalyticsConsentViewActions.OnGetStarted) loginViewModel.handle(LoginAction.OnGetStarted(resetLoginConfig = false)) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 89a05c88da..631c7b8395 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -36,6 +36,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast +import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity import im.vector.app.features.call.transfer.CallTransferActivity @@ -404,6 +405,10 @@ class DefaultNavigator @Inject constructor( } } + override fun openAnalyticsOptIn(context: Context) { + context.startActivity(Intent(context, AnalyticsOptInActivity::class.java)) + } + override fun openTerms(context: Context, activityResultLauncher: ActivityResultLauncher, serviceType: TermsService.ServiceType, diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 264593fe18..c828a80322 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -105,6 +105,8 @@ interface Navigator { fun openBigImageViewer(activity: Activity, sharedElement: View?, mxcUrl: String?, title: String?) + fun openAnalyticsOptIn(context: Context) + fun openPinCode(context: Context, activityResultLauncher: ActivityResultLauncher, pinMode: PinMode) diff --git a/vector/src/main/res/layout/fragment_login_splash.xml b/vector/src/main/res/layout/fragment_login_splash.xml index 89a684be01..1d69cde723 100644 --- a/vector/src/main/res/layout/fragment_login_splash.xml +++ b/vector/src/main/res/layout/fragment_login_splash.xml @@ -204,20 +204,9 @@ android:layout_height="wrap_content" android:textColor="?vctr_content_secondary" android:visibility="gone" - app:layout_constraintBottom_toTopOf="@id/loginSplashAnalyticsConsent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" tools:text="@string/settings_version" tools:visibility="visible" /> - -