PM-19099: Centralize app metadata (#4847)

This commit is contained in:
David Perez
2025-03-11 14:47:07 -05:00
committed by GitHub
parent 3fca61ad3e
commit e10ca9a6ec
5 changed files with 108 additions and 75 deletions

View File

@@ -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()

View File

@@ -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<AboutState, AboutEvent, AboutAction>(
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,

View File

@@ -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,

View File

@@ -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: <version_name> (<version_code>)".asText(),
deviceData = "<device_data>".asText(),
ciData = "\n<ci_info>".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,
)

View File

@@ -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)
}
}