From e10ca9a6eceb59e4806bc5a461e8b620b291d8b8 Mon Sep 17 00:00:00 2001 From: David Perez Date: Tue, 11 Mar 2025 14:47:07 -0500 Subject: [PATCH] PM-19099: Centralize app metadata (#4847) --- .../data/platform/util/BuildConfigUtils.kt | 53 +++++++++++++++ .../feature/settings/about/AboutViewModel.kt | 65 +++++-------------- .../feature/settings/about/AboutScreenTest.kt | 2 + .../settings/about/AboutViewModelTest.kt | 36 +++------- .../ui/platform/util/BuildConfigTest.kt | 27 ++++++++ 5 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 app/src/test/java/com/x8bit/bitwarden/ui/platform/util/BuildConfigTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/BuildConfigUtils.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/BuildConfigUtils.kt index 7fbb13b567..883d8d267e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/util/BuildConfigUtils.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/util/BuildConfigUtils.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.platform.util +import android.os.Build import com.x8bit.bitwarden.BuildConfig /** @@ -7,3 +8,55 @@ import com.x8bit.bitwarden.BuildConfig */ val isFdroid: Boolean get() = BuildConfig.FLAVOR == "fdroid" + +/** + * A string that represents a displayable app version. + */ +val versionData: String + get() = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + +/** + * A string that represents device data. + */ +val deviceData: String get() = "$deviceBrandModel $osInfo $buildInfo" + +/** + * A string representing the CI information if available. + */ +val ciBuildInfo: String? get() = BuildConfig.CI_INFO.takeUnless { it.isBlank() } + +/** + * A string representing the build flavor or blank if it is the standard configuration. + */ +private val buildFlavorName: String + get() = when (BuildConfig.FLAVOR) { + "standard" -> "" + else -> "-${BuildConfig.FLAVOR}" + } + +/** + * A string representing the build type. + */ +private val buildTypeName: String + get() = when (BuildConfig.BUILD_TYPE) { + "debug" -> "dev" + "release" -> "prod" + else -> BuildConfig.BUILD_TYPE + } + +/** + * A string representing the device brand and model. + */ +private val deviceBrandModel: String get() = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}" + +/** + * A string representing the operating system information. + */ +private val osInfo: String get() = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}" + +/** + * A string representing the build information. + */ +private val buildInfo: String + get() = "\uD83D\uDCE6 $buildTypeName" + + buildFlavorName.takeUnless { it.isBlank() }?.let { " $it" }.orEmpty() diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModel.kt index c345c09e50..6a9c6ba3b3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModel.kt @@ -1,16 +1,17 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about -import android.os.Build import android.os.Parcelable 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.LogsManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.platform.repository.util.baseWebVaultUrlOrDefault +import com.x8bit.bitwarden.data.platform.util.ciBuildInfo +import com.x8bit.bitwarden.data.platform.util.deviceData import com.x8bit.bitwarden.data.platform.util.isFdroid +import com.x8bit.bitwarden.data.platform.util.versionData 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 @@ -37,10 +38,15 @@ class AboutViewModel @Inject constructor( private val logsManager: LogsManager, private val environmentRepository: EnvironmentRepository, ) : BaseViewModel( - initialState = savedStateHandle[KEY_STATE] ?: createInitialState( - clock = clock, - isCrashLoggingEnabled = logsManager.isEnabled, - ), + initialState = savedStateHandle[KEY_STATE] + ?: AboutState( + version = R.string.version.asText().concat(": $versionData".asText()), + deviceData = deviceData.asText(), + ciData = ciBuildInfo?.let { "\n$it" }.orEmpty().asText(), + isSubmitCrashLogsEnabled = logsManager.isEnabled, + shouldShowCrashLogsButton = !isFdroid, + copyrightInfo = "© Bitwarden Inc. 2015-${Year.now(clock).value}".asText(), + ), ) { init { stateFlow @@ -92,34 +98,13 @@ class AboutViewModel @Inject constructor( } private fun handleVersionClick() { - val buildFlavour = when (BuildConfig.FLAVOR) { - "standard" -> "" - else -> "-${BuildConfig.FLAVOR}" - } - - val buildVariant = when (BuildConfig.BUILD_TYPE) { - "debug" -> "dev" - "release" -> "prod" - else -> BuildConfig.BUILD_TYPE - } - - val deviceBrandModel = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}" - val osInfo = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}" - val buildInfo = "\uD83D\uDCE6 $buildVariant$buildFlavour" - val ciBuildInfoString = BuildConfig.CI_INFO - clipboardManager.setText( text = state.copyrightInfo .concat("\n\n".asText()) .concat(state.version) .concat("\n".asText()) - .concat("$deviceBrandModel $osInfo $buildInfo".asText()) - .concat( - "\n$ciBuildInfoString" - .takeUnless { ciBuildInfoString.isEmpty() } - .orEmpty() - .asText(), - ), + .concat(state.deviceData) + .concat(state.ciData), ) } @@ -130,26 +115,6 @@ class AboutViewModel @Inject constructor( ), ) } - - @Suppress("UndocumentedPublicClass") - companion object { - /** - * Create initial state for the About View model. - */ - fun createInitialState(clock: Clock, isCrashLoggingEnabled: Boolean): AboutState { - val currentYear = Year.now(clock).value - val copyrightInfo = "© Bitwarden Inc. 2015-$currentYear".asText() - - return AboutState( - version = R.string.version - .asText() - .concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()), - isSubmitCrashLogsEnabled = isCrashLoggingEnabled, - shouldShowCrashLogsButton = !isFdroid, - copyrightInfo = copyrightInfo, - ) - } - } } /** @@ -158,6 +123,8 @@ class AboutViewModel @Inject constructor( @Parcelize data class AboutState( val version: Text, + val deviceData: Text, + val ciData: Text, val isSubmitCrashLogsEnabled: Boolean, val shouldShowCrashLogsButton: Boolean, val copyrightInfo: Text, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreenTest.kt index 941f50b7aa..5d5767e0aa 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreenTest.kt @@ -39,6 +39,8 @@ class AboutScreenTest : BaseComposeTest() { private val mutableStateFlow = MutableStateFlow( AboutState( version = "Version: 1.0.0 (1)".asText(), + deviceData = "device_data".asText(), + ciData = "ci_data".asText(), isSubmitCrashLogsEnabled = false, copyrightInfo = "".asText(), shouldShowCrashLogsButton = true, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModelTest.kt index a08fa48a2f..943cbdcfab 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModelTest.kt @@ -1,9 +1,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about -import android.os.Build import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test -import com.x8bit.bitwarden.BuildConfig import com.x8bit.bitwarden.data.platform.manager.LogsManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository @@ -110,31 +108,17 @@ class AboutViewModelTest : BaseViewModelTest() { @Test fun `on VersionClick should call setText on the ClipboardManager with specific Text`() { - val versionName = BuildConfig.VERSION_NAME - val versionCode = BuildConfig.VERSION_CODE - - val deviceBrandModel = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}" - val osInfo = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}" - val buildInfo = "\uD83D\uDCE6 dev" - val ciInfo = BuildConfig.CI_INFO - - val expectedText = "© Bitwarden Inc. 2015-" - .asText() - .concat(Year.now(fixedClock).value.toString().asText()) + val state = DEFAULT_ABOUT_STATE + val expectedText = state.copyrightInfo .concat("\n\n".asText()) - .concat("Version: $versionName ($versionCode)".asText()) + .concat(state.version) .concat("\n".asText()) - .concat("$deviceBrandModel $osInfo $buildInfo".asText()) - .concat( - "\n$ciInfo" - .takeUnless { ciInfo.isEmpty() } - .orEmpty() - .asText(), - ) + .concat(state.deviceData) + .concat(state.ciData) every { clipboardManager.setText(expectedText, true, null) } just runs - val viewModel = createViewModel(DEFAULT_ABOUT_STATE) + val viewModel = createViewModel(state) viewModel.trySendAction(AboutAction.VersionClick) verify(exactly = 1) { @@ -175,10 +159,10 @@ private val fixedClock = Clock.fixed( ZoneId.systemDefault(), ) private val DEFAULT_ABOUT_STATE: AboutState = AboutState( - version = "Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText(), + version = "Version: ()".asText(), + deviceData = "".asText(), + ciData = "\n".asText(), isSubmitCrashLogsEnabled = false, - copyrightInfo = "© Bitwarden Inc. 2015-" - .asText() - .concat(Year.now(fixedClock).value.toString().asText()), + copyrightInfo = "© Bitwarden Inc. 2015-${Year.now(fixedClock).value}".asText(), shouldShowCrashLogsButton = true, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/util/BuildConfigTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/util/BuildConfigTest.kt new file mode 100644 index 0000000000..3c79457602 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/util/BuildConfigTest.kt @@ -0,0 +1,27 @@ +package com.x8bit.bitwarden.ui.platform.util + +import android.os.Build +import com.x8bit.bitwarden.BuildConfig +import com.x8bit.bitwarden.data.platform.util.deviceData +import com.x8bit.bitwarden.data.platform.util.versionData +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class BuildConfigTest { + @Test + fun `deviceData should be formatted correctly`() { + val deviceBrandModel = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}" + val osInfo = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}" + val buildInfo = "\uD83D\uDCE6 dev" + + assertEquals("$deviceBrandModel $osInfo $buildInfo", deviceData) + } + + @Test + fun `versionData should be formatted correctly`() { + val versionName = BuildConfig.VERSION_NAME + val versionCode = BuildConfig.VERSION_CODE + + assertEquals("$versionName ($versionCode)", versionData) + } +}