diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ca52ca3..044b8faa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2 ## [Unreleased] +### Added +- The new Crash Reporting Opt In/Out screen has been added + ## [1.5.2 (929)] - 2025-04-09 ### Changed diff --git a/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt b/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt index 650a380e0..c12eff50f 100644 --- a/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt +++ b/app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt @@ -90,6 +90,7 @@ class ZcashApplication : CoroutineApplication() { if (GlobalCrashReporter.register(this, getAvailableCrashReporters())) { applicationScope.launch { StandardPreferenceKeys.IS_ANALYTICS_ENABLED.observe(standardPreferenceProvider()).collect { + Twig.debug { "Is crashlytics enabled: $it" } if (it) { GlobalCrashReporter.enable() } else { diff --git a/crash-android-lib/src/zcashmainnetStoreDebug/kotlin/co/electriccoin/zcash/crash/android/internal/firebase/FirebaseCrashReporter.kt b/crash-android-lib/src/zcashmainnetStoreDebug/kotlin/co/electriccoin/zcash/crash/android/internal/firebase/FirebaseCrashReporter.kt index 99e13543e..c44ef2c86 100644 --- a/crash-android-lib/src/zcashmainnetStoreDebug/kotlin/co/electriccoin/zcash/crash/android/internal/firebase/FirebaseCrashReporter.kt +++ b/crash-android-lib/src/zcashmainnetStoreDebug/kotlin/co/electriccoin/zcash/crash/android/internal/firebase/FirebaseCrashReporter.kt @@ -78,11 +78,11 @@ private class FirebaseCrashReporterImpl( } override fun enable() { - firebaseCrashlytics.setCrashlyticsCollectionEnabled(true) + firebaseCrashlytics.isCrashlyticsCollectionEnabled = true } override fun disableAndDelete() { - firebaseCrashlytics.setCrashlyticsCollectionEnabled(false) + firebaseCrashlytics.isCrashlyticsCollectionEnabled = false firebaseCrashlytics.deleteUnsentReports() firebaseInstallations.delete() } diff --git a/docs/whatsNew/WHATS_NEW_EN.md b/docs/whatsNew/WHATS_NEW_EN.md index 560c3d0b7..14c730030 100644 --- a/docs/whatsNew/WHATS_NEW_EN.md +++ b/docs/whatsNew/WHATS_NEW_EN.md @@ -12,6 +12,9 @@ directly impact users rather than highlighting other key architectural updates.* ## [Unreleased] +### Added +- The new Crash Reporting Opt In/Out screen has been added + ## [1.5.2 (929)] - 2025-04-09 ### Fixed diff --git a/docs/whatsNew/WHATS_NEW_ES.md b/docs/whatsNew/WHATS_NEW_ES.md index e34e92ec0..b37e0321c 100644 --- a/docs/whatsNew/WHATS_NEW_ES.md +++ b/docs/whatsNew/WHATS_NEW_ES.md @@ -12,6 +12,9 @@ directly impact users rather than highlighting other key architectural updates.* ## [Unreleased] +### Añadido +- The new Crash Reporting Opt In/Out screen has been added + ## [1.5.2 (929)] - 2025-04-09 ### Corregido diff --git a/ui-lib/build.gradle.kts b/ui-lib/build.gradle.kts index 292a88974..1300b54e8 100644 --- a/ui-lib/build.gradle.kts +++ b/ui-lib/build.gradle.kts @@ -47,6 +47,7 @@ android { "src/main/res/ui/common", "src/main/res/ui/contact", "src/main/res/ui/connect_keystone", + "src/main/res/ui/crash_reporting_opt_in", "src/main/res/ui/delete_wallet", "src/main/res/ui/export_data", "src/main/res/ui/home", diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt b/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt index faefaa045..49b1066af 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/di/ViewModelModule.kt @@ -11,6 +11,7 @@ import co.electriccoin.zcash.ui.screen.advancedsettings.viewmodel.AdvancedSettin import co.electriccoin.zcash.ui.screen.chooseserver.ChooseServerViewModel import co.electriccoin.zcash.ui.screen.contact.viewmodel.AddContactViewModel import co.electriccoin.zcash.ui.screen.contact.viewmodel.UpdateContactViewModel +import co.electriccoin.zcash.ui.screen.crashreporting.viewmodel.CrashReportingViewModel import co.electriccoin.zcash.ui.screen.feedback.viewmodel.FeedbackViewModel import co.electriccoin.zcash.ui.screen.integrations.viewmodel.IntegrationsViewModel import co.electriccoin.zcash.ui.screen.onboarding.viewmodel.OnboardingViewModel @@ -138,4 +139,5 @@ val viewModelModule = ) } viewModelOf(::TaxExportViewModel) + viewModelOf(::CrashReportingViewModel) } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt index cdca320dc..fb80f75cd 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/Navigation.kt @@ -28,6 +28,7 @@ import co.electriccoin.zcash.ui.NavigationArguments.SEND_SCAN_ZIP_321_URI import co.electriccoin.zcash.ui.NavigationTargets.ABOUT import co.electriccoin.zcash.ui.NavigationTargets.ADVANCED_SETTINGS import co.electriccoin.zcash.ui.NavigationTargets.CHOOSE_SERVER +import co.electriccoin.zcash.ui.NavigationTargets.CRASH_REPORTING_OPT_IN import co.electriccoin.zcash.ui.NavigationTargets.DELETE_WALLET import co.electriccoin.zcash.ui.NavigationTargets.EXCHANGE_RATE_OPT_IN import co.electriccoin.zcash.ui.NavigationTargets.EXPORT_PRIVATE_DATA @@ -66,6 +67,7 @@ import co.electriccoin.zcash.ui.screen.contact.AddContactArgs import co.electriccoin.zcash.ui.screen.contact.UpdateContactArgs import co.electriccoin.zcash.ui.screen.contact.WrapAddContact import co.electriccoin.zcash.ui.screen.contact.WrapUpdateContact +import co.electriccoin.zcash.ui.screen.crashreporting.AndroidCrashReportingOptIn import co.electriccoin.zcash.ui.screen.deletewallet.WrapDeleteWallet import co.electriccoin.zcash.ui.screen.disconnected.WrapDisconnected import co.electriccoin.zcash.ui.screen.exchangerate.optin.AndroidExchangeRateOptIn @@ -296,6 +298,9 @@ internal fun MainActivity.Navigation() { composable(SETTINGS_EXCHANGE_RATE_OPT_IN) { AndroidSettingsExchangeRateOptIn() } + composable(CRASH_REPORTING_OPT_IN) { + AndroidCrashReportingOptIn() + } composable { WrapScanKeystoneSignInRequest() } @@ -617,6 +622,7 @@ object NavigationTargets { const val SEED_RECOVERY = "seed_recovery" const val SETTINGS = "settings" const val SETTINGS_EXCHANGE_RATE_OPT_IN = "settings_exchange_rate_opt_in" + const val CRASH_REPORTING_OPT_IN = "crash_reporting_opt_in" const val SUPPORT = "support" const val WHATS_NEW = "whats_new" } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/view/AdvancedSettingsView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/view/AdvancedSettingsView.kt index af5c45e4f..528b1c470 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/view/AdvancedSettingsView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/view/AdvancedSettingsView.kt @@ -164,6 +164,11 @@ private fun AdvancedSettingsPreview() = title = stringRes(R.string.advanced_settings_currency_conversion), icon = R.drawable.ic_advanced_settings_currency_conversion, onClick = {} + ), + ZashiListItemState( + title = stringRes(R.string.advanced_settings_crash_reporting), + icon = R.drawable.ic_advanced_settings_crash_reporting, + onClick = {} ) ), deleteButton = diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/viewmodel/AdvancedSettingsViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/viewmodel/AdvancedSettingsViewModel.kt index 44a014ba2..a7c640116 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/viewmodel/AdvancedSettingsViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/advancedsettings/viewmodel/AdvancedSettingsViewModel.kt @@ -1,12 +1,15 @@ package co.electriccoin.zcash.ui.screen.advancedsettings.viewmodel +import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT import co.electriccoin.zcash.ui.NavigationRouter import co.electriccoin.zcash.ui.NavigationTargets import co.electriccoin.zcash.ui.R +import co.electriccoin.zcash.ui.common.model.DistributionDimension import co.electriccoin.zcash.ui.common.model.WalletRestoringState +import co.electriccoin.zcash.ui.common.provider.GetVersionInfoProvider import co.electriccoin.zcash.ui.common.usecase.GetWalletRestoringStateUseCase import co.electriccoin.zcash.ui.common.usecase.NavigateToTaxExportUseCase import co.electriccoin.zcash.ui.design.component.ButtonState @@ -25,7 +28,10 @@ class AdvancedSettingsViewModel( getWalletRestoringState: GetWalletRestoringStateUseCase, private val navigationRouter: NavigationRouter, private val navigateToTaxExport: NavigateToTaxExportUseCase, + private val getVersionInfo: GetVersionInfoProvider, ) : ViewModel() { + private val versionInfo by lazy { getVersionInfo() } + val state: StateFlow = getWalletRestoringState .observe() @@ -41,7 +47,7 @@ class AdvancedSettingsViewModel( AdvancedSettingsState( onBack = ::onBack, items = - listOfNotNull( + mutableStateListOf( ZashiListItemState( title = stringRes(R.string.advanced_settings_recovery), icon = R.drawable.ic_advanced_settings_recovery, @@ -74,8 +80,19 @@ class AdvancedSettingsViewModel( icon = R.drawable.ic_advanced_settings_currency_conversion, onClick = ::onCurrencyConversionClick - ) - ).toImmutableList(), + ), + ).apply { + if (versionInfo.distributionDimension == DistributionDimension.STORE) { + add( + ZashiListItemState( + title = stringRes(R.string.advanced_settings_crash_reporting), + icon = + R.drawable.ic_advanced_settings_crash_reporting, + onClick = ::onCrashReportingClick + ) + ) + } + }.toImmutableList(), deleteButton = ButtonState( text = stringRes(R.string.advanced_settings_delete_button), @@ -89,6 +106,8 @@ class AdvancedSettingsViewModel( private fun onCurrencyConversionClick() = navigationRouter.forward(NavigationTargets.SETTINGS_EXCHANGE_RATE_OPT_IN) + private fun onCrashReportingClick() = navigationRouter.forward(NavigationTargets.CRASH_REPORTING_OPT_IN) + private fun onTaxExportClick() = viewModelScope.launch { navigateToTaxExport() diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/AndroidCrashReportingOptIn.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/AndroidCrashReportingOptIn.kt new file mode 100644 index 000000000..d7e4ea695 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/AndroidCrashReportingOptIn.kt @@ -0,0 +1,20 @@ +package co.electriccoin.zcash.ui.screen.crashreporting + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.electriccoin.zcash.ui.screen.crashreporting.view.CrashReportingOptIn +import co.electriccoin.zcash.ui.screen.crashreporting.viewmodel.CrashReportingViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun AndroidCrashReportingOptIn() { + val crashReportingViewModel = koinViewModel() + val state = crashReportingViewModel.state.collectAsStateWithLifecycle().value + + BackHandler { + state.onBack() + } + + CrashReportingOptIn(state = state) +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/model/CrashReportingOptInState.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/model/CrashReportingOptInState.kt new file mode 100644 index 000000000..6ee8fa544 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/model/CrashReportingOptInState.kt @@ -0,0 +1,19 @@ +package co.electriccoin.zcash.ui.screen.crashreporting.model + +data class CrashReportingOptInState( + val isOptedIn: Boolean, + val onBack: () -> Unit, + val onSaveClicked: (enabled: Boolean) -> Unit, +) { + companion object { + fun new( + isOptedIn: Boolean = true, + onBack: () -> Unit = {}, + onSaveClicked: (enabled: Boolean) -> Unit = {} + ) = CrashReportingOptInState( + isOptedIn = isOptedIn, + onBack = onBack, + onSaveClicked = onSaveClicked + ) + } +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/view/CrashReportingOptInView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/view/CrashReportingOptInView.kt new file mode 100644 index 000000000..1d05f24b6 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/view/CrashReportingOptInView.kt @@ -0,0 +1,295 @@ +package co.electriccoin.zcash.ui.screen.crashreporting.view + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import co.electriccoin.zcash.ui.R +import co.electriccoin.zcash.ui.design.component.BlankSurface +import co.electriccoin.zcash.ui.design.component.ZashiButton +import co.electriccoin.zcash.ui.design.component.ZashiButtonDefaults +import co.electriccoin.zcash.ui.design.newcomponent.PreviewScreens +import co.electriccoin.zcash.ui.design.theme.ZcashTheme +import co.electriccoin.zcash.ui.design.theme.colors.ZashiColors +import co.electriccoin.zcash.ui.design.theme.typography.ZashiTypography +import co.electriccoin.zcash.ui.design.util.scaffoldPadding +import co.electriccoin.zcash.ui.screen.crashreporting.model.CrashReportingOptInState +import co.electriccoin.zcash.ui.screen.exchangerate.SecondaryCard + +@Composable +fun CrashReportingOptIn(state: CrashReportingOptInState) { + Scaffold { paddingValues -> + Column( + modifier = + Modifier + .fillMaxSize() + .scaffoldPadding(paddingValues) + ) { + Button( + contentPadding = PaddingValues(0.dp), + modifier = Modifier.size(40.dp), + onClick = state.onBack, + shape = RoundedCornerShape(12.dp), + colors = + ButtonDefaults.buttonColors( + containerColor = ZashiColors.Btns.Tertiary.btnTertiaryBg + ) + ) { + Image( + painter = painterResource(id = R.drawable.ic_crash_reporting_opt_in_close), + contentDescription = stringResource(R.string.close_navigation_content_description), + colorFilter = ColorFilter.tint(ZashiColors.Btns.Tertiary.btnTertiaryFg) + ) + } + + Spacer(modifier = Modifier.height(28.dp)) + + CrashReportingOptInContent(state) + } + } +} + +@Composable +fun CrashReportingOptInContent(state: CrashReportingOptInState) { + var isOptInSelected by remember(state.isOptedIn) { mutableStateOf(state.isOptedIn) } + + val isButtonDisabled by remember(state.isOptedIn) { + derivedStateOf { + (state.isOptedIn && isOptInSelected) || (!state.isOptedIn && !isOptInSelected) + } + } + + Column( + modifier = + Modifier + .fillMaxHeight() + .verticalScroll(rememberScrollState()) + ) { + Image(painter = painterResource(R.drawable.crash_reporting_opt_in_all_icons), contentDescription = null) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = + stringResource( + id = R.string.crash_reporting_opt_in_title, + stringResource(R.string.app_name) + ), + color = ZashiColors.Text.textPrimary, + style = ZashiTypography.header6, + fontWeight = FontWeight.SemiBold + ) + + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.crash_reporting_opt_in_subtitle), + style = ZashiTypography.textSm, + color = ZashiColors.Text.textTertiary, + ) + + CrashReportingOptInOptions( + isOptInSelected = isOptInSelected, + setOptInSelected = { isOptInSelected = it } + ) + + Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.weight(1f)) + + CrashReportingOptInFooter( + isOptInSelected = isOptInSelected, + isSaveDisabled = isButtonDisabled, + state = state + ) + } +} + +@Composable +fun CrashReportingOptInFooter( + isOptInSelected: Boolean, + isSaveDisabled: Boolean, + state: CrashReportingOptInState +) { + Column { + Row { + Image( + painter = painterResource(R.drawable.ic_crash_reporting_opt_in_info), + contentDescription = null, + colorFilter = ColorFilter.tint(ZashiColors.Text.textTertiary) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = stringResource(R.string.crash_reporting_opt_in_info), + color = ZashiColors.Text.textTertiary, + style = ZashiTypography.textXs + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + ZashiButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.crash_reporting_opt_in_save), + onClick = { state.onSaveClicked(isOptInSelected) }, + enabled = !isSaveDisabled, + colors = ZashiButtonDefaults.primaryColors() + ) + } +} + +@Composable +fun CrashReportingOptInOptions( + isOptInSelected: Boolean, + setOptInSelected: (Boolean) -> Unit, +) { + Column { + Spacer(modifier = Modifier.height(24.dp)) + Option( + modifier = Modifier.fillMaxWidth(), + image = OptIn, + selectionImage = if (isOptInSelected) Checked else Unchecked, + title = stringResource(R.string.crash_reporting_opt_in_positive), + subtitle = stringResource(R.string.crash_reporting_opt_in_positive_desc), + onClick = { setOptInSelected(true) } + ) + Spacer(modifier = Modifier.height(12.dp)) + Option( + modifier = Modifier.fillMaxWidth(), + image = OptOut, + selectionImage = if (!isOptInSelected) Checked else Unchecked, + title = stringResource(R.string.crash_reporting_opt_in_negative), + subtitle = stringResource(R.string.crash_reporting_opt_in_negative_desc), + onClick = { setOptInSelected(false) } + ) + } +} + +@Suppress("LongParameterList") +@Composable +private fun Option( + @DrawableRes image: Int, + @DrawableRes selectionImage: Int, + title: String, + subtitle: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + SecondaryCard( + modifier = + modifier.clickable( + onClick = onClick, + indication = null, + interactionSource = remember { MutableInteractionSource() }, + ) + ) { + Row( + Modifier.padding(20.dp) + ) { + Image( + painter = painterResource(image), + contentDescription = null + ) + Spacer(modifier = Modifier.width(12.dp)) + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + style = ZashiTypography.textSm, + color = ZashiColors.Text.textPrimary, + fontWeight = FontWeight.SemiBold, + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = subtitle, + style = ZashiTypography.textSm, + color = ZashiColors.Text.textTertiary, + ) + } + Image( + painter = painterResource(selectionImage), + contentDescription = null + ) + } + } +} + +private val OptIn: Int + @DrawableRes + @Composable + get() = + if (isSystemInDarkTheme()) { + R.drawable.ic_opt_in + } else { + R.drawable.ic_opt_in_light + } + +private val OptOut: Int + @DrawableRes + @Composable + get() = + if (isSystemInDarkTheme()) { + R.drawable.ic_opt_out + } else { + R.drawable.ic_opt_out_light + } + +private val Checked: Int + @DrawableRes + @Composable + get() = + if (isSystemInDarkTheme()) { + R.drawable.ic_checkbox_checked + } else { + R.drawable.ic_checkbox_checked_light + } + +private val Unchecked: Int + @DrawableRes + @Composable + get() = + if (isSystemInDarkTheme()) { + R.drawable.ic_checkbox_unchecked + } else { + R.drawable.ic_checkbox_unchecked_light + } + +@Suppress("UnusedPrivateMember") +@PreviewScreens +@Composable +private fun CrashReportingOptInPreviews() = + ZcashTheme { + BlankSurface { + CrashReportingOptIn(CrashReportingOptInState.new()) + } + } diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/viewmodel/CrashReportingViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/viewmodel/CrashReportingViewModel.kt new file mode 100644 index 000000000..6c98986e9 --- /dev/null +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/crashreporting/viewmodel/CrashReportingViewModel.kt @@ -0,0 +1,71 @@ +package co.electriccoin.zcash.ui.screen.crashreporting.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import cash.z.ecc.sdk.ANDROID_STATE_FLOW_TIMEOUT +import co.electriccoin.zcash.preference.StandardPreferenceProvider +import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault +import co.electriccoin.zcash.ui.NavigationRouter +import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys +import co.electriccoin.zcash.ui.screen.crashreporting.model.CrashReportingOptInState +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.WhileSubscribed +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class CrashReportingViewModel( + private val standardPreferenceProvider: StandardPreferenceProvider, + private val navigationRouter: NavigationRouter, +) : ViewModel() { + private val isAnalyticsEnabled = booleanStateFlow(StandardPreferenceKeys.IS_ANALYTICS_ENABLED) + + val state: StateFlow = + isAnalyticsEnabled + .filterNotNull() + .map { currentOptInState -> + CrashReportingOptInState.new( + isOptedIn = currentOptInState, + onBack = ::onBack, + onSaveClicked = { + onSaveClicked(it) + onBack() + } + ) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), + initialValue = + CrashReportingOptInState.new( + onBack = ::onBack, + onSaveClicked = { + onSaveClicked(it) + onBack() + } + ) + ) + + private fun onSaveClicked(enabled: Boolean) { + setBooleanPreference(StandardPreferenceKeys.IS_ANALYTICS_ENABLED, enabled) + } + + private fun setBooleanPreference( + default: BooleanPreferenceDefault, + newState: Boolean + ) { + viewModelScope.launch { + default.putValue(standardPreferenceProvider(), newState) + } + } + + private fun onBack() = navigationRouter.back() + + private fun booleanStateFlow(default: BooleanPreferenceDefault): StateFlow = + flow { + emitAll(default.observe(standardPreferenceProvider())) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT), null) +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/model/SettingsTroubleshootingState.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/model/SettingsTroubleshootingState.kt index 3af093fb9..a725f522d 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/model/SettingsTroubleshootingState.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/model/SettingsTroubleshootingState.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.Immutable data class SettingsTroubleshootingState( val backgroundSync: TroubleshootingItemState, val keepScreenOnDuringSync: TroubleshootingItemState, - val analytics: TroubleshootingItemState, val rescan: TroubleshootingItemState, ) diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt index 5c82865d9..f45702f78 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/view/SettingsView.kt @@ -144,14 +144,6 @@ private fun TroubleshootingMenu(state: SettingsTroubleshootingState) { }, leadingIcon = { AddIcon(state.keepScreenOnDuringSync.isEnabled) } ) - DropdownMenuItem( - text = { Text(stringResource(id = R.string.settings_troubleshooting_enable_analytics)) }, - onClick = { - state.analytics.onClick() - expanded = false - }, - leadingIcon = { AddIcon(state.analytics.isEnabled) } - ) // isRescanEnabled means if this feature should be visible, not whether it is enabled as in the case of // the previous booleans if (state.rescan.isEnabled) { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt index 5d58e9544..79ee00737 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt @@ -54,7 +54,6 @@ class SettingsViewModel( ) : ViewModel() { private val versionInfo by lazy { getVersionInfo() } - private val isAnalyticsEnabled = booleanStateFlow(StandardPreferenceKeys.IS_ANALYTICS_ENABLED) private val isBackgroundSyncEnabled = booleanStateFlow(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED) private val isKeepScreenOnWhileSyncingEnabled = booleanStateFlow(StandardPreferenceKeys.IS_KEEP_SCREEN_ON_DURING_SYNC) @@ -63,12 +62,10 @@ class SettingsViewModel( private val troubleshootingState = combine( observeConfiguration(), - isAnalyticsEnabled, isBackgroundSyncEnabled, isKeepScreenOnWhileSyncingEnabled, - ) { configuration, isAnalyticsEnabled, isBackgroundSyncEnabled, isKeepScreenOnWhileSyncingEnabled -> + ) { configuration, isBackgroundSyncEnabled, isKeepScreenOnWhileSyncingEnabled -> if (configuration != null && - isAnalyticsEnabled != null && isBackgroundSyncEnabled != null && isKeepScreenOnWhileSyncingEnabled != null && versionInfo.isDebuggable && @@ -83,10 +80,6 @@ class SettingsViewModel( TroubleshootingItemState( isKeepScreenOnWhileSyncingEnabled ) { setKeepScreenOnWhileSyncing(isKeepScreenOnWhileSyncingEnabled.not()) }, - analytics = - TroubleshootingItemState( - isAnalyticsEnabled - ) { setAnalyticsEnabled(isAnalyticsEnabled.not()) }, rescan = TroubleshootingItemState( ConfigurationEntries.IS_RESCAN_ENABLED.getValue(configuration), @@ -195,10 +188,6 @@ class SettingsViewModel( version = stringRes(R.string.settings_version, versionInfo.versionName) ) - private fun setAnalyticsEnabled(enabled: Boolean) { - setBooleanPreference(StandardPreferenceKeys.IS_ANALYTICS_ENABLED, enabled) - } - private fun setBackgroundSyncEnabled(enabled: Boolean) { setBooleanPreference(StandardPreferenceKeys.IS_BACKGROUND_SYNC_ENABLED, enabled) } diff --git a/ui-lib/src/main/res/ui/advanced_settings/drawable-night/ic_advanced_settings_crash_reporting.xml b/ui-lib/src/main/res/ui/advanced_settings/drawable-night/ic_advanced_settings_crash_reporting.xml new file mode 100644 index 000000000..dccf5d30f --- /dev/null +++ b/ui-lib/src/main/res/ui/advanced_settings/drawable-night/ic_advanced_settings_crash_reporting.xml @@ -0,0 +1,16 @@ + + + + diff --git a/ui-lib/src/main/res/ui/advanced_settings/drawable/ic_advanced_settings_crash_reporting.xml b/ui-lib/src/main/res/ui/advanced_settings/drawable/ic_advanced_settings_crash_reporting.xml new file mode 100644 index 000000000..8bb04e3dd --- /dev/null +++ b/ui-lib/src/main/res/ui/advanced_settings/drawable/ic_advanced_settings_crash_reporting.xml @@ -0,0 +1,16 @@ + + + + diff --git a/ui-lib/src/main/res/ui/advanced_settings/values-es/strings.xml b/ui-lib/src/main/res/ui/advanced_settings/values-es/strings.xml index e0ec9eeb1..19937eb68 100644 --- a/ui-lib/src/main/res/ui/advanced_settings/values-es/strings.xml +++ b/ui-lib/src/main/res/ui/advanced_settings/values-es/strings.xml @@ -5,6 +5,7 @@ Exportar Datos Privados Elegir un Servidor Conversión de Moneda + Crash Reporting Se te pedirá confirmación en la siguiente pantalla Restablecer Zashi Exportar Archivo de Impuestos diff --git a/ui-lib/src/main/res/ui/advanced_settings/values/strings.xml b/ui-lib/src/main/res/ui/advanced_settings/values/strings.xml index 43dfda73c..eeaeff7ac 100644 --- a/ui-lib/src/main/res/ui/advanced_settings/values/strings.xml +++ b/ui-lib/src/main/res/ui/advanced_settings/values/strings.xml @@ -5,6 +5,7 @@ Export Private Data Choose a Server Currency Conversion + Crash Reporting You will be asked to confirm on the next screen Reset Zashi Export Tax File diff --git a/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable-night/crash_reporting_opt_in_all_icons.xml b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable-night/crash_reporting_opt_in_all_icons.xml new file mode 100644 index 000000000..b74ad0aea --- /dev/null +++ b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable-night/crash_reporting_opt_in_all_icons.xml @@ -0,0 +1,37 @@ + + + + + + + + + diff --git a/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/crash_reporting_opt_in_all_icons.xml b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/crash_reporting_opt_in_all_icons.xml new file mode 100644 index 000000000..904131af9 --- /dev/null +++ b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/crash_reporting_opt_in_all_icons.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + diff --git a/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/ic_crash_reporting_opt_in_close.xml b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/ic_crash_reporting_opt_in_close.xml new file mode 100644 index 000000000..4ba196c91 --- /dev/null +++ b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/ic_crash_reporting_opt_in_close.xml @@ -0,0 +1,13 @@ + + + diff --git a/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/ic_crash_reporting_opt_in_info.xml b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/ic_crash_reporting_opt_in_info.xml new file mode 100644 index 000000000..7ed038b97 --- /dev/null +++ b/ui-lib/src/main/res/ui/crash_reporting_opt_in/drawable/ic_crash_reporting_opt_in_info.xml @@ -0,0 +1,13 @@ + + + diff --git a/ui-lib/src/main/res/ui/crash_reporting_opt_in/values-es/strings.xml b/ui-lib/src/main/res/ui/crash_reporting_opt_in/values-es/strings.xml new file mode 100644 index 000000000..a97b758be --- /dev/null +++ b/ui-lib/src/main/res/ui/crash_reporting_opt_in/values-es/strings.xml @@ -0,0 +1,17 @@ + + + + Help Improve %1$s + + Crash reports are an essential tool for developers to improve the + quality and reliability of apps. They are very useful in practice to help with fixing bugs that cause + crashes. + Note: The crash reports include information about app version, time + when the crash happened, and stack trace so it\’s clear where in the app the crash happened. The data + collected is completely anonymized. + Opt in + Share crash reports with Zashi developers. + Opt out + Don’t share crash reports with Zashi developers. + Save changes + diff --git a/ui-lib/src/main/res/ui/crash_reporting_opt_in/values/strings.xml b/ui-lib/src/main/res/ui/crash_reporting_opt_in/values/strings.xml new file mode 100644 index 000000000..a97b758be --- /dev/null +++ b/ui-lib/src/main/res/ui/crash_reporting_opt_in/values/strings.xml @@ -0,0 +1,17 @@ + + + + Help Improve %1$s + + Crash reports are an essential tool for developers to improve the + quality and reliability of apps. They are very useful in practice to help with fixing bugs that cause + crashes. + Note: The crash reports include information about app version, time + when the crash happened, and stack trace so it\’s clear where in the app the crash happened. The data + collected is completely anonymized. + Opt in + Share crash reports with Zashi developers. + Opt out + Don’t share crash reports with Zashi developers. + Save changes + diff --git a/ui-lib/src/main/res/ui/settings/values-es/strings.xml b/ui-lib/src/main/res/ui/settings/values-es/strings.xml index aec7aa2f7..cf7a365e4 100644 --- a/ui-lib/src/main/res/ui/settings/values-es/strings.xml +++ b/ui-lib/src/main/res/ui/settings/values-es/strings.xml @@ -14,6 +14,4 @@ Volver a escanear la blockchain Sincronización en segundo plano Mantener la pantalla encendida durante la sincronización - Reportar fallos - diff --git a/ui-lib/src/main/res/ui/settings/values/strings.xml b/ui-lib/src/main/res/ui/settings/values/strings.xml index 79a6bbc2e..43c87ace6 100644 --- a/ui-lib/src/main/res/ui/settings/values/strings.xml +++ b/ui-lib/src/main/res/ui/settings/values/strings.xml @@ -14,6 +14,4 @@ Rescan blockchain Background sync Keep screen on during sync - Report crashes -