diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt index d769c0a3d3..8f31553ca7 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt @@ -203,7 +203,7 @@ private fun ContentColumn( ) { Text( modifier = Modifier.padding(end = 16.dp), - text = "© Bitwarden Inc. 2015-2023", + text = state.copyrightInfo.invoke(), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurface, ) 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 127ef32e96..eacdbfa421 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 @@ -15,6 +15,8 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize +import java.time.Clock +import java.time.Year import javax.inject.Inject private const val KEY_STATE = "state" @@ -26,8 +28,9 @@ private const val KEY_STATE = "state" class AboutViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, private val clipboardManager: BitwardenClipboardManager, + private val clock: Clock, ) : BaseViewModel( - initialState = savedStateHandle[KEY_STATE] ?: INITIAL_STATE, + initialState = savedStateHandle[KEY_STATE] ?: createInitialState(clock = clock), ) { init { stateFlow @@ -68,7 +71,9 @@ class AboutViewModel @Inject constructor( } private fun handleVersionClick() { - clipboardManager.setText(text = state.version) + clipboardManager.setText( + text = state.copyrightInfo.concat("\n\n".asText()).concat(state.version), + ) } private fun handleWebVaultClick() { @@ -76,12 +81,21 @@ class AboutViewModel @Inject constructor( } companion object { - private val INITIAL_STATE: AboutState = AboutState( - version = R.string.version - .asText() - .concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()), - isSubmitCrashLogsEnabled = false, - ) + /** + * Create initial state for the About View model. + */ + fun createInitialState(clock: Clock): 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 = false, + copyrightInfo = copyrightInfo, + ) + } } } @@ -92,6 +106,7 @@ class AboutViewModel @Inject constructor( data class AboutState( val version: Text, val isSubmitCrashLogsEnabled: Boolean, + val copyrightInfo: Text, ) : Parcelable /** 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 b8e80c21b3..18ea187d2a 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 @@ -10,6 +10,7 @@ import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo import androidx.core.net.toUri import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest @@ -26,6 +27,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import java.time.Clock +import java.time.Instant +import java.time.Year +import java.time.ZoneOffset class AboutScreenTest : BaseComposeTest() { private var haveCalledNavigateBack = false @@ -34,6 +39,7 @@ class AboutScreenTest : BaseComposeTest() { AboutState( version = "Version: 1.0.0 (1)".asText(), isSubmitCrashLogsEnabled = false, + copyrightInfo = "".asText(), ), ) private val mutableEventFlow = bufferedMutableSharedFlow() @@ -199,4 +205,18 @@ class AboutScreenTest : BaseComposeTest() { composeTestRule.onNodeWithText("Version: 1.1.0 (2)").assertIsDisplayed() } + + @Test + fun `copyright info should update according to the state`() = runTest { + val fixedClock = Clock.fixed(Instant.parse("2024-01-25T00:00:00Z"), ZoneOffset.UTC) + val currentYear = Year.now(fixedClock).value + + mutableStateFlow.update { + it.copy(copyrightInfo = "© Bitwarden Inc. 2015-$currentYear".asText()) + } + + composeTestRule.onNodeWithText("© Bitwarden Inc. 2015-$currentYear") + .performScrollTo() + .assertIsDisplayed() + } } 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 fb12bd41f8..9a706246db 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 @@ -2,9 +2,11 @@ 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.clipboard.BitwardenClipboardManager 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.every import io.mockk.just import io.mockk.mockk @@ -15,6 +17,10 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import java.time.Clock +import java.time.Instant +import java.time.Year +import java.time.ZoneId class AboutViewModelTest : BaseViewModelTest() { @@ -22,7 +28,7 @@ class AboutViewModelTest : BaseViewModelTest() { @Test fun `on BackClick should emit NavigateBack`() = runTest { - val viewModel = createViewModel(DEFAULT_ABOUT_STATE) + val viewModel = createViewModel() viewModel.eventFlow.test { viewModel.trySendAction(AboutAction.BackClick) assertEquals(AboutEvent.NavigateBack, awaitItem()) @@ -66,14 +72,24 @@ class AboutViewModelTest : BaseViewModelTest() { } @Test - fun `on VersionClick should call setText on the ClipboardManager`() { - val viewModel = createViewModel(DEFAULT_ABOUT_STATE) - every { clipboardManager.setText(text = "0".asText()) } just runs + fun `on VersionClick should call setText on the ClipboardManager with specific Text`() { + val versionName = BuildConfig.VERSION_NAME + val versionCode = BuildConfig.VERSION_CODE + val expectedText = "© Bitwarden Inc. 2015-" + .asText() + .concat(Year.now(fixedClock).value.toString().asText()) + .concat("\n\n".asText()) + .concat( + "Version: $versionName ($versionCode)".asText(), + ) + every { clipboardManager.setText(expectedText, true, null) } just runs + + val viewModel = createViewModel(DEFAULT_ABOUT_STATE) viewModel.trySendAction(AboutAction.VersionClick) verify(exactly = 1) { - clipboardManager.setText(text = "0".asText()) + clipboardManager.setText(expectedText, true, null) } } @@ -91,10 +107,18 @@ class AboutViewModelTest : BaseViewModelTest() { ): AboutViewModel = AboutViewModel( savedStateHandle = SavedStateHandle().apply { set("state", state) }, clipboardManager = clipboardManager, + clock = fixedClock, ) } -private val DEFAULT_ABOUT_STATE: AboutState = AboutState( - version = "0".asText(), - isSubmitCrashLogsEnabled = false, +private val fixedClock = Clock.fixed( + Instant.parse("2024-01-25T10:15:30.00Z"), + ZoneId.systemDefault(), +) +private val DEFAULT_ABOUT_STATE: AboutState = AboutState( + version = "Version: 1.0.0 (1)".asText(), + isSubmitCrashLogsEnabled = false, + copyrightInfo = "© Bitwarden Inc. 2015-" + .asText() + .concat(Year.now(fixedClock).value.toString().asText()), )