BIT-478: Implementation for crash logging switch in settings screen (#864)

This commit is contained in:
Joshua Queen 2024-01-30 01:11:01 -05:00 committed by Álison Fernandes
parent 085fa0153d
commit 2de2ade7a6
11 changed files with 135 additions and 13 deletions

View file

@ -0,0 +1,12 @@
package com.x8bit.bitwarden.data.platform.manager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
/**
* CrashLogsManager implementation for F-droid flavor builds.
*/
class CrashLogsManagerImpl(
settingsRepository: SettingsRepository,
) : CrashLogsManager {
override var isEnabled: Boolean = true
}

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden
import android.app.Application
import com.x8bit.bitwarden.data.platform.manager.CrashLogsManager
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
@ -14,4 +15,7 @@ class BitwardenApplication : Application() {
// other callers.
@Inject
lateinit var networkConfigManager: NetworkConfigManager
@Inject
lateinit var crashLogsManager: CrashLogsManager
}

View file

@ -0,0 +1,12 @@
package com.x8bit.bitwarden.data.platform.manager
/**
* Implementations of this interface provide a way to enable or disable the collection of crash
* logs, giving control over whether crash logs are generated and stored.
*/
interface CrashLogsManager {
/**
* Gets or sets whether the collection of crash logs is enabled.
*/
var isEnabled: Boolean
}

View file

@ -14,6 +14,8 @@ import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManagerImpl
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManagerImpl
import com.x8bit.bitwarden.data.platform.manager.CrashLogsManager
import com.x8bit.bitwarden.data.platform.manager.CrashLogsManagerImpl
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManagerImpl
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager
@ -135,4 +137,12 @@ object PlatformManagerModule {
clock = clock,
json = json,
)
@Provides
@Singleton
fun provideCrashLogsManager(
settingsRepository: SettingsRepository,
): CrashLogsManager = CrashLogsManagerImpl(
settingsRepository = settingsRepository,
)
}

View file

@ -0,0 +1,9 @@
package com.x8bit.bitwarden.data.platform.util
import com.x8bit.bitwarden.BuildConfig
/**
* A boolean property that indicates whether the current build flavor is "fdroid".
*/
val isFdroid: Boolean
get() = BuildConfig.FLAVOR == "fdroid"

View file

@ -150,16 +150,18 @@ private fun ContentColumn(
.verticalScroll(rememberScrollState()),
) {
Spacer(modifier = Modifier.height(8.dp))
BitwardenWideSwitch(
label = stringResource(id = R.string.submit_crash_logs),
isChecked = state.isSubmitCrashLogsEnabled,
onCheckedChange = onSubmitCrashLogsCheckedChange,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
contentDescription = stringResource(id = R.string.submit_crash_logs),
)
Spacer(modifier = Modifier.height(16.dp))
if (state.shouldShowCrashLogsButton) {
BitwardenWideSwitch(
label = stringResource(id = R.string.submit_crash_logs),
isChecked = state.isSubmitCrashLogsEnabled,
onCheckedChange = onSubmitCrashLogsCheckedChange,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
contentDescription = stringResource(id = R.string.submit_crash_logs),
)
Spacer(modifier = Modifier.height(16.dp))
}
BitwardenExternalLinkRow(
text = stringResource(id = R.string.bitwarden_help_center),
onConfirmClick = onHelpCenterClick,

View file

@ -5,7 +5,10 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.manager.CrashLogsManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.util.isFdroid
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
@ -29,8 +32,13 @@ class AboutViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val clipboardManager: BitwardenClipboardManager,
private val clock: Clock,
private val settingsRepository: SettingsRepository,
private val crashLogsManager: CrashLogsManager,
) : BaseViewModel<AboutState, AboutEvent, AboutAction>(
initialState = savedStateHandle[KEY_STATE] ?: createInitialState(clock = clock),
initialState = savedStateHandle[KEY_STATE] ?: createInitialState(
clock = clock,
isCrashLoggingEnabled = crashLogsManager.isEnabled,
),
) {
init {
stateFlow
@ -65,6 +73,7 @@ class AboutViewModel @Inject constructor(
}
private fun handleSubmitCrashLogsClick(action: AboutAction.SubmitCrashLogsClick) {
crashLogsManager.isEnabled = action.enabled
mutableStateFlow.update { currentState ->
currentState.copy(isSubmitCrashLogsEnabled = action.enabled)
}
@ -84,7 +93,7 @@ class AboutViewModel @Inject constructor(
/**
* Create initial state for the About View model.
*/
fun createInitialState(clock: Clock): AboutState {
fun createInitialState(clock: Clock, isCrashLoggingEnabled: Boolean): AboutState {
val currentYear = Year.now(clock).value
val copyrightInfo = "© Bitwarden Inc. 2015-$currentYear".asText()
@ -92,7 +101,8 @@ class AboutViewModel @Inject constructor(
version = R.string.version
.asText()
.concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()),
isSubmitCrashLogsEnabled = false,
isSubmitCrashLogsEnabled = isCrashLoggingEnabled,
shouldShowCrashLogsButton = !isFdroid,
copyrightInfo = copyrightInfo,
)
}
@ -106,6 +116,7 @@ class AboutViewModel @Inject constructor(
data class AboutState(
val version: Text,
val isSubmitCrashLogsEnabled: Boolean,
val shouldShowCrashLogsButton: Boolean,
val copyrightInfo: Text,
) : Parcelable

View file

@ -0,0 +1,24 @@
package com.x8bit.bitwarden.data.platform.manager
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
/**
* CrashLogsManager implementation for standard flavor builds.
*/
class CrashLogsManagerImpl(
private val settingsRepository: SettingsRepository,
) : CrashLogsManager {
override var isEnabled: Boolean
get() = settingsRepository.isCrashLoggingEnabled
set(value) {
settingsRepository.isCrashLoggingEnabled = value
Firebase.crashlytics.setCrashlyticsCollectionEnabled(value)
}
init {
isEnabled = settingsRepository.isCrashLoggingEnabled
}
}

View file

@ -32,6 +32,7 @@ class MainViewModelTest : BaseViewModelTest() {
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
private val mutableUserStateFlow = MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
private val mutableCrashLoggingEnabledFlow = MutableStateFlow(true)
val authRepository = mockk<AuthRepository> {
every { userStateFlow } returns mutableUserStateFlow
every { activeUserId } returns USER_ID

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.filterToOne
@ -40,6 +41,7 @@ class AboutScreenTest : BaseComposeTest() {
version = "Version: 1.0.0 (1)".asText(),
isSubmitCrashLogsEnabled = false,
copyrightInfo = "".asText(),
shouldShowCrashLogsButton = true,
),
)
private val mutableEventFlow = bufferedMutableSharedFlow<AboutEvent>()
@ -174,6 +176,21 @@ class AboutScreenTest : BaseComposeTest() {
}
}
@Test
fun `submit crash logs switch should be displayed according to state`() {
mutableStateFlow.update { it.copy(shouldShowCrashLogsButton = true) }
composeTestRule
.onNodeWithText("Submit crash logs")
.assertIsDisplayed()
mutableStateFlow.update { it.copy(shouldShowCrashLogsButton = false) }
composeTestRule
.onNodeWithText("Submit crash logs")
.assertIsNotDisplayed()
}
@Test
fun `on submit crash logs toggle should send SubmitCrashLogsClick`() {
val enabled = true

View file

@ -3,10 +3,13 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.data.platform.manager.CrashLogsManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@ -25,6 +28,11 @@ import java.time.ZoneId
class AboutViewModelTest : BaseViewModelTest() {
private val clipboardManager: BitwardenClipboardManager = mockk()
private val settingsRepository: SettingsRepository = mockk()
private val crashLogsManager: CrashLogsManager = mockk {
every { isEnabled } returns false
every { isEnabled = any() } just runs
}
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
@ -71,6 +79,15 @@ class AboutViewModelTest : BaseViewModelTest() {
assertTrue(viewModel.stateFlow.value.isSubmitCrashLogsEnabled)
}
@Test
fun `on SubmitCrashLogsClick should update crashLogsManager isEnabled`() = runTest {
val viewModel = createViewModel(DEFAULT_ABOUT_STATE)
viewModel.trySendAction(AboutAction.SubmitCrashLogsClick(true))
coVerify(exactly = 1) { crashLogsManager.isEnabled = true }
}
@Test
fun `on VersionClick should call setText on the ClipboardManager with specific Text`() {
val versionName = BuildConfig.VERSION_NAME
@ -108,6 +125,8 @@ class AboutViewModelTest : BaseViewModelTest() {
savedStateHandle = SavedStateHandle().apply { set("state", state) },
clipboardManager = clipboardManager,
clock = fixedClock,
settingsRepository = settingsRepository,
crashLogsManager = crashLogsManager,
)
}
@ -121,4 +140,5 @@ private val DEFAULT_ABOUT_STATE: AboutState = AboutState(
copyrightInfo = "© Bitwarden Inc. 2015-"
.asText()
.concat(Year.now(fixedClock).value.toString().asText()),
shouldShowCrashLogsButton = true,
)