diff --git a/app/build.gradle b/app/build.gradle index fa9171c96a..9a0678f326 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -297,9 +297,9 @@ dependencies { implementation "com.opencsv:opencsv:4.6" testImplementation "junit:junit:$junitVersion" testImplementation 'org.assertj:assertj-core:3.11.1' - testImplementation "org.mockito:mockito-inline:4.10.0" + testImplementation "org.mockito:mockito-inline:4.11.0" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" - androidTestImplementation "org.mockito:mockito-android:4.10.0" + androidTestImplementation "org.mockito:mockito-android:4.11.0" androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" testImplementation "androidx.test:core:$testCoreVersion" testImplementation "androidx.arch.core:core-testing:2.2.0" diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettings.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettings.kt index bb30a7bca8..2d79c874e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettings.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettings.kt @@ -86,7 +86,7 @@ fun OptionsCard(card: CardModel, callbacks: Callbacks) { ) { itemsIndexed(card.options) { i, it -> if (i != 0) Divider() - TitledRadioButton(it) { it.onClick(callbacks) } + TitledRadioButton(it) { callbacks.setMode(it.value) } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModel.kt index 756c2f4aed..5235c45d89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModel.kt @@ -111,15 +111,15 @@ class ExpirationSettingsViewModel( expirationConfig = storage.getExpirationConfiguration(threadId) val expiryMode = expirationConfig?.expiryMode ?: ExpiryMode.NONE val recipient = threadDb.getRecipientForThreadId(threadId) - val groupInfo = recipient?.takeIf { it.isClosedGroupRecipient } + val groupRecord = recipient?.takeIf { it.isClosedGroupRecipient } ?.run { address.toGroupString().let(groupDb::getGroup).orNull() } _state.update { state -> state.copy( address = recipient?.address, - isGroup = groupInfo != null, + isGroup = groupRecord != null, isNoteToSelf = recipient?.address?.serialize() == textSecurePreferences.getLocalNumber(), - isSelfAdmin = groupInfo == null || groupInfo.admins.any{ it.serialize() == textSecurePreferences.getLocalNumber() }, + isSelfAdmin = groupRecord == null || groupRecord.admins.any{ it.serialize() == textSecurePreferences.getLocalNumber() }, expiryMode = expiryMode, persistedMode = expiryMode ) @@ -222,21 +222,21 @@ private fun typeOptions(state: State) = state.takeUnless { it.modeOptionsHidden }?.run { listOfNotNull( typeOption( - ExpiryType.NONE, + ExpiryMode.NONE, state, R.string.expiration_off, contentDescription = R.string.AccessibilityId_disable_disappearing_messages, enabled = isSelfAdmin ), if (!isNewConfigEnabled) typeOption( - ExpiryType.LEGACY, + ExpiryType.LEGACY.defaultMode(persistedMode), state, R.string.expiration_type_disappear_legacy, contentDescription = R.string.expiration_type_disappear_legacy_description, enabled = isSelfAdmin ) else null, if (!isGroup) typeOption( - ExpiryType.AFTER_READ, + ExpiryType.AFTER_READ.defaultMode(persistedMode), state, R.string.expiration_type_disappear_after_read, R.string.expiration_type_disappear_after_read_description, @@ -244,7 +244,7 @@ private fun typeOptions(state: State) = enabled = isNewConfigEnabled && isSelfAdmin ) else null, typeOption( - ExpiryType.AFTER_SEND, + ExpiryType.AFTER_SEND.defaultMode(persistedMode), state, R.string.expiration_type_disappear_after_send, R.string.expiration_type_disappear_after_read_description, @@ -254,21 +254,6 @@ private fun typeOptions(state: State) = ) } -private fun typeOption( - type: ExpiryType, - state: State, - @StringRes title: Int, - @StringRes subtitle: Int? = null, - @StringRes contentDescription: Int = title, - enabled: Boolean = true, -) = typeOption( - mode = type.defaultMode(state.persistedMode), - state = state, - title = title, - subtitle = subtitle, - contentDescription = contentDescription, - enabled = enabled -) private fun typeOption( mode: ExpiryMode, state: State, @@ -276,26 +261,27 @@ private fun typeOption( @StringRes subtitle: Int? = null, @StringRes contentDescription: Int = title, enabled: Boolean = true, - onClick: Action = Action.SelectMode(mode) ) = OptionModel( - GetString(title), - subtitle?.let(::GetString), + value = mode, + title = GetString(title), + subtitle = subtitle?.let(::GetString), + contentDescription = GetString(contentDescription), selected = state.expiryType == mode.type, - enabled = enabled, - onClick = onClick + enabled = enabled ) private fun debugTimes(isDebug: Boolean) = if (isDebug) listOf(10.seconds, 1.minutes) else emptyList() private fun debugModes(isDebug: Boolean, type: ExpiryType) = debugTimes(isDebug).map { type.mode(it.inWholeSeconds) } private fun debugOptions(state: State): List = - debugModes(state.showDebugOptions, state.expiryType.takeIf { it != ExpiryType.NONE } ?: ExpiryType.AFTER_SEND) + debugModes(state.showDebugOptions, state.expiryType.takeIf { it == ExpiryType.AFTER_READ } ?: ExpiryType.AFTER_SEND) .map { timeOption(it, state, subtitle = GetString("for testing purposes")) } val defaultTimes = listOf(12.hours, 1.days, 7.days, 14.days) val afterSendTimes = defaultTimes val afterSendModes = afterSendTimes.map { it.inWholeSeconds }.map(ExpiryMode::AfterSend) +fun afterSendOptions(state: State) = afterSendModes.map { timeOption(it, state) } val afterReadTimes = buildList { add(5.minutes) @@ -303,17 +289,18 @@ val afterReadTimes = buildList { addAll(defaultTimes) } val afterReadModes = afterReadTimes.map { it.inWholeSeconds }.map(ExpiryMode::AfterRead) +fun afterReadOptions(state: State) = afterReadModes.map { timeOption(it, state) } private fun timeOptions(state: State): List? = if (state.modeOptionsHidden) timeOptionsAfterSend(state) else when (state.expiryMode) { - is ExpiryMode.Legacy, is ExpiryMode.AfterRead -> debugOptions(state) + afterReadModes.map { timeOption(it, state) } - is ExpiryMode.AfterSend -> debugOptions(state) + afterSendModes.map { timeOption(it, state) } + is ExpiryMode.Legacy, is ExpiryMode.AfterRead -> debugOptions(state) + afterReadOptions(state) + is ExpiryMode.AfterSend -> debugOptions(state) + afterSendOptions(state) else -> null } -private fun timeOptionsAfterSend(state: State) = listOfNotNull( - typeOption(ExpiryType.NONE, state, R.string.expiration_off, enabled = state.isSelfAdmin), +private fun timeOptionsAfterSend(state: State) = listOf( + typeOption(ExpiryMode.NONE, state, R.string.expiration_off, enabled = state.isSelfAdmin), ) + debugOptions(state) + afterSendModes.map { timeOption(it, state) } private fun timeOption( @@ -321,29 +308,21 @@ private fun timeOption( state: State, title: GetString = GetString(mode.duration, ExpirationUtil::getExpirationDisplayValue), subtitle: GetString? = null, - onClick: Action = Action.SelectMode(mode) ) = OptionModel( + value = mode, title = title, subtitle = subtitle, selected = state.expiryMode == mode, - enabled = state.isTimeOptionsEnabled, - onClick = onClick + enabled = state.isTimeOptionsEnabled ) -sealed interface Action { - operator fun invoke(callbacks: Callbacks) {} - - data class SelectMode(val mode: ExpiryMode): Action { - override operator fun invoke(callbacks: Callbacks) = callbacks.setMode(mode) - } -} - data class OptionModel( + val value: ExpiryMode, val title: GetString, val subtitle: GetString? = null, + val contentDescription: GetString = title, val selected: Boolean = false, val enabled: Boolean = true, - val onClick: Action ) enum class ExpiryType(private val createMode: (Long) -> ExpiryMode) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt index d9073f6885..981b6e9c6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -257,7 +257,7 @@ fun RowScope.CarouselButton( onClick = { animationScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + delta) } }) { Icon( painter = painterResource(id = id), - contentDescription = "", + contentDescription = null, ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f917caa536..c634dfaa88 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -566,7 +566,7 @@ Default High Max - Off + Off 5 Minutes 1 Hour 12 Hours diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModelTest.kt index f3b0000543..a7373ccc0e 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/expiration/ExpirationSettingsViewModelTest.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import network.loki.messenger.libsession_util.util.ExpiryMode -import network.loki.messenger.R import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert import org.junit.Rule @@ -15,8 +14,6 @@ import org.mockito.Mockito.mock import org.mockito.kotlin.whenever import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.GroupUtil.CLOSED_GROUP_PREFIX import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient @@ -26,8 +23,10 @@ import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.ui.GetString -import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.hours +import network.loki.messenger.R +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days @OptIn(ExperimentalCoroutinesApi::class) class ExpirationSettingsViewModelTest { @@ -42,12 +41,8 @@ class ExpirationSettingsViewModelTest { private val threadDb: ThreadDatabase = mock(ThreadDatabase::class.java) private val groupDb: GroupDatabase = mock(GroupDatabase::class.java) private val storage: Storage = mock(Storage::class.java) - private val recipient = mock(Recipient::class.java) - private val groupRecord = mock(GroupRecord::class.java) - private val optionalGroupRecord = Optional.of(groupRecord) - @Test fun `UI should show a list of times and an Off option`() = runTest { val threadId = 1L @@ -59,43 +54,75 @@ class ExpirationSettingsViewModelTest { ) whenever(threadDb.getRecipientForThreadId(Mockito.anyLong())).thenReturn(recipient) whenever(storage.getExpirationConfiguration(Mockito.anyLong())).thenReturn(expirationConfig) + whenever(textSecurePreferences.getLocalNumber()).thenReturn("05---LOCAL---ADDRESS") - val address = Address.fromSerialized("${CLOSED_GROUP_PREFIX}94198734289") + val userAddress = Address.fromSerialized(textSecurePreferences.getLocalNumber()!!) + val someAddress = Address.fromSerialized("05---SOME---ADDRESS") - whenever(recipient.isClosedGroupRecipient).thenReturn(true) - whenever(recipient.address).thenReturn(address) + whenever(recipient.isClosedGroupRecipient).thenReturn(false) + whenever(recipient.address).thenReturn(someAddress) - whenever(groupDb.getGroup(Mockito.anyString())).thenReturn(optionalGroupRecord) + whenever(groupDb.getGroup(Mockito.anyString())).thenReturn(Optional.absent()) val viewModel = createViewModel() advanceUntilIdle() - val state = viewModel.state.value - MatcherAssert.assertThat( - state.isGroup, - CoreMatchers.equalTo(true) - ) - - MatcherAssert.assertThat( - viewModel.uiState.value.cards.count(), - CoreMatchers.equalTo(1) - ) - - val options = viewModel.uiState.value.cards[0].options - MatcherAssert.assertThat( - options.map { it.title }, + viewModel.state.value, CoreMatchers.equalTo( - listOf( - GetString(R.string.expiration_off), - GetString(12.hours), - GetString(1.days), - GetString(7.days), - GetString(14.days) + State( + isGroup = false, + isSelfAdmin = true, + address = someAddress, + isNoteToSelf = false, + expiryMode = ExpiryMode.AfterSend(12.hours.inWholeSeconds), + isNewConfigEnabled = true, + persistedMode = ExpiryMode.AfterSend(12.hours.inWholeSeconds), + showDebugOptions = false ) ) ) + + val uiState = viewModel.uiState.value + + MatcherAssert.assertThat( + uiState.cards.map { it.title }, + CoreMatchers.equalTo( + listOf( + R.string.activity_expiration_settings_delete_type, + R.string.activity_expiration_settings_timer + ).map(::GetString) + ) + ) + + MatcherAssert.assertThat( + uiState.cards[0].options.map { it.title }, + CoreMatchers.equalTo( + listOf( + R.string.expiration_off, + R.string.expiration_type_disappear_after_read, + R.string.expiration_type_disappear_after_send, + ).map(::GetString) + ) + ) + + MatcherAssert.assertThat( + uiState.cards[1].options.map { it.title }, + CoreMatchers.equalTo( + listOf( + 12.hours, + 1.days, + 7.days, + 14.days, + ).map(::GetString) + ) + ) + + MatcherAssert.assertThat( + uiState.showGroupFooter, + CoreMatchers.equalTo(false) + ) } private fun createViewModel(isNewConfigEnabled: Boolean = true) = ExpirationSettingsViewModel( diff --git a/libsession/build.gradle b/libsession/build.gradle index 3cfb66cd32..09bf6cef60 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -46,7 +46,7 @@ dependencies { implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" testImplementation "junit:junit:$junitVersion" testImplementation 'org.assertj:assertj-core:3.11.1' - testImplementation "org.mockito:mockito-inline:4.0.0" + testImplementation "org.mockito:mockito-inline:4.11.0" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" testImplementation "androidx.test:core:$testCoreVersion" testImplementation "androidx.arch.core:core-testing:2.1.0"