diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelAboutOverrideSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelAboutOverrideSheet.kt
new file mode 100644
index 0000000000..98a0e93dfe
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelAboutOverrideSheet.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2026 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.thoughtcrime.securesms.groups.memberlabel
+
+import android.content.DialogInterface
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.core.os.bundleOf
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.setFragmentResult
+import org.signal.core.ui.compose.AllDevicePreviews
+import org.signal.core.ui.compose.BottomSheets
+import org.signal.core.ui.compose.Buttons
+import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment
+import org.signal.core.ui.compose.DayNightPreviews
+import org.signal.core.ui.compose.Previews
+import org.thoughtcrime.securesms.R
+
+/**
+ * Informs the user that their member label will be displayed in place of their About text in this group.
+ */
+class MemberLabelAboutOverrideSheet : ComposeBottomSheetDialogFragment() {
+ companion object {
+ const val RESULT_KEY = "member_label_about_override_result"
+ const val KEY_DONT_SHOW_AGAIN = "dont_show_again"
+
+ private const val FRAGMENT_TAG = "MemberLabelAboutOverrideSheet"
+
+ fun show(fragmentManager: FragmentManager) {
+ MemberLabelAboutOverrideSheet().show(fragmentManager, FRAGMENT_TAG)
+ }
+ }
+
+ override val peekHeightPercentage: Float = 1f
+
+ @Composable
+ override fun SheetContent() {
+ val callbacks = remember {
+ object : MemberLabelAboutOverrideUiCallbacks {
+ override fun onOkClicked() {
+ setFragmentResult(RESULT_KEY, bundleOf(KEY_DONT_SHOW_AGAIN to false))
+ dismiss()
+ }
+
+ override fun onDontShowAgainClicked() {
+ setFragmentResult(RESULT_KEY, bundleOf(KEY_DONT_SHOW_AGAIN to true))
+ dismiss()
+ }
+ }
+ }
+
+ MemberLabelAboutOverrideSheetContent(callbacks = callbacks)
+ }
+
+ override fun onCancel(dialog: DialogInterface) {
+ setFragmentResult(RESULT_KEY, bundleOf(KEY_DONT_SHOW_AGAIN to false))
+ super.onCancel(dialog)
+ }
+}
+
+@Composable
+private fun MemberLabelAboutOverrideSheetContent(
+ callbacks: MemberLabelAboutOverrideUiCallbacks = MemberLabelAboutOverrideUiCallbacks.Empty
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .padding(top = 4.dp, bottom = 28.dp, start = 28.dp, end = 28.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ BottomSheets.Handle()
+
+ Image(
+ painter = painterResource(R.drawable.symbol_tag_filled_64),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(top = 24.dp)
+ .size(64.dp)
+ )
+
+ Text(
+ text = stringResource(R.string.MemberLabelsAboutOverride__title),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+
+ Text(
+ text = stringResource(R.string.MemberLabelsAboutOverride__body),
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(top = 12.dp)
+ )
+
+ Buttons.LargeTonal(
+ onClick = callbacks::onOkClicked,
+ modifier = Modifier
+ .padding(top = 64.dp)
+ .defaultMinSize(minWidth = 220.dp)
+ ) {
+ Text(text = stringResource(android.R.string.ok))
+ }
+
+ TextButton(
+ onClick = callbacks::onDontShowAgainClicked,
+ modifier = Modifier.padding(top = 16.dp)
+ ) {
+ Text(text = stringResource(R.string.ConversationFragment_dont_show_again))
+ }
+ }
+}
+
+private interface MemberLabelAboutOverrideUiCallbacks {
+ fun onOkClicked()
+ fun onDontShowAgainClicked()
+
+ object Empty : MemberLabelAboutOverrideUiCallbacks {
+ override fun onOkClicked() = Unit
+ override fun onDontShowAgainClicked() = Unit
+ }
+}
+
+@AllDevicePreviews
+@Composable
+private fun MemberLabelAboutOverrideSheetPreview() = Previews.Preview {
+ MemberLabelAboutOverrideSheetContent()
+}
+
+@DayNightPreviews
+@Composable
+private fun MemberLabelAboutOverrideSheetDarkPreview() = Previews.Preview {
+ MemberLabelAboutOverrideSheetContent()
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt
index 2ffc8731c5..6a4c61a533 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelFragment.kt
@@ -5,6 +5,8 @@
package org.thoughtcrime.securesms.groups.memberlabel
+import android.os.Bundle
+import android.view.View
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -89,6 +91,16 @@ class MemberLabelFragment : ComposeFragment(), ReactWithAnyEmojiBottomSheetDialo
)
}
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ childFragmentManager.setFragmentResultListener(MemberLabelAboutOverrideSheet.RESULT_KEY, viewLifecycleOwner) { _, resultData ->
+ viewModel.onAboutOverrideSheetDismissed(
+ dontShowAgain = resultData.getBoolean(MemberLabelAboutOverrideSheet.KEY_DONT_SHOW_AGAIN)
+ )
+ }
+ }
+
@Composable
override fun FragmentContent() {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
@@ -111,6 +123,13 @@ class MemberLabelFragment : ComposeFragment(), ReactWithAnyEmojiBottomSheetDialo
val networkErrorMessage = stringResource(R.string.GroupMemberLabel__error_cant_save_no_network)
+ LaunchedEffect(uiState.showAboutOverrideSheet) {
+ if (uiState.showAboutOverrideSheet) {
+ MemberLabelAboutOverrideSheet.show(childFragmentManager)
+ viewModel.onAboutOverrideSheetShown()
+ }
+ }
+
LaunchedEffect(uiState.saveState) {
when (uiState.saveState) {
is SaveState.Success -> {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt
index fae853669e..fe2233156f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelRepository.kt
@@ -19,6 +19,8 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupManager
+import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.thoughtcrime.securesms.keyvalue.UiHintValues
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.RemoteConfig
@@ -29,7 +31,8 @@ import org.whispersystems.signalservice.api.NetworkResult
*/
class MemberLabelRepository private constructor(
private val context: Context = AppDependencies.application,
- private val groupsTable: GroupTable = SignalDatabase.groups
+ private val groupsTable: GroupTable = SignalDatabase.groups,
+ private val uiHints: UiHintValues = SignalStore.uiHints
) {
companion object {
@JvmStatic
@@ -105,9 +108,7 @@ class MemberLabelRepository private constructor(
/**
* Computes the sender [NameColor] for a recipient as seen by other group members.
*/
- suspend fun getSenderNameColor(groupId: GroupId.V2, recipientId: RecipientId): NameColor = withContext(Dispatchers.IO) {
- val recipient = getRecipient(recipientId)
-
+ suspend fun getSenderNameColor(groupId: GroupId.V2, recipient: Recipient): NameColor = withContext(Dispatchers.IO) {
val groupMemberIds = groupsTable
.getGroupMembers(groupId, GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF)
.mapNotNull { it.serviceId.orNull() }
@@ -128,6 +129,14 @@ class MemberLabelRepository private constructor(
GroupManager.updateMemberLabel(context, groupId, sanitizedLabel.text, sanitizedLabel.emoji.orEmpty())
}
}
+
+ fun hasDismissedMemberLabelAboutOverrideWarning(): Boolean {
+ return uiHints.hasDismissedMemberLabelAboutOverrideWarning()
+ }
+
+ fun markMemberLabelAboutOverrideWarningDismissed() {
+ uiHints.markMemberLabelAboutOverrideWarningDismissed()
+ }
}
private fun MemberLabel.sanitized(): MemberLabel = this.copy(
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModel.kt
index af792e05d4..ea094038ad 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModel.kt
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.signal.core.util.StringUtil
import org.signal.core.util.concurrent.SignalDispatchers
+import org.signal.core.util.isNotNullOrBlank
import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException
@@ -25,7 +26,8 @@ import org.whispersystems.signalservice.api.NetworkResult
class MemberLabelViewModel(
private val memberLabelRepo: MemberLabelRepository = MemberLabelRepository.instance,
private val groupId: GroupId.V2,
- private val recipientId: RecipientId
+ private val recipientId: RecipientId,
+ private val sanitizeEmoji: (String) -> String? = MemberLabel::sanitizeEmoji
) : ViewModel() {
private var originalLabelEmoji: String = ""
@@ -40,16 +42,17 @@ class MemberLabelViewModel(
private fun loadInitialState() {
viewModelScope.launch(SignalDispatchers.IO) {
- val memberLabel = memberLabelRepo.getLabel(groupId, recipientId)
+ val recipient = memberLabelRepo.getRecipient(recipientId)
+ val memberLabel = memberLabelRepo.getLabel(groupId, recipient)
originalLabelEmoji = memberLabel?.emoji.orEmpty()
originalLabelText = memberLabel?.text.orEmpty()
internalUiState.update {
it.copy(
- recipient = memberLabelRepo.getRecipient(recipientId),
+ recipient = recipient,
labelEmoji = originalLabelEmoji,
labelText = originalLabelText,
- senderNameColor = memberLabelRepo.getSenderNameColor(groupId, recipientId)
+ senderNameColor = memberLabelRepo.getSenderNameColor(groupId, recipient)
)
}
}
@@ -85,7 +88,8 @@ class MemberLabelViewModel(
}
private fun hasChanges(labelEmoji: String, labelText: String): Boolean {
- return labelEmoji != originalLabelEmoji || MemberLabel.sanitizeLabelText(labelText) != originalLabelText
+ return sanitizeEmoji(labelEmoji).orEmpty() != originalLabelEmoji ||
+ MemberLabel.sanitizeLabelText(labelText) != originalLabelText
}
fun save() {
@@ -107,14 +111,26 @@ class MemberLabelViewModel(
)
)
- val newSaveState: SaveState = when (result) {
- is NetworkResult.Success -> SaveState.Success
+ when (result) {
+ is NetworkResult.Success -> {
+ val isLabelCleared = currentState.sanitizedLabelText.isEmpty() && currentState.labelEmoji.isEmpty()
+ val selfHasAbout = currentState.recipient?.combinedAboutAndEmoji.isNotNullOrBlank()
+ val showOverrideSheet = !isLabelCleared && selfHasAbout && !memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning()
- is NetworkResult.NetworkError<*> -> SaveState.NetworkError
+ internalUiState.update {
+ if (showOverrideSheet) {
+ it.copy(showAboutOverrideSheet = true)
+ } else {
+ it.copy(saveState = SaveState.Success)
+ }
+ }
+ }
+
+ is NetworkResult.NetworkError<*> -> internalUiState.update { it.copy(saveState = SaveState.NetworkError) }
is NetworkResult.ApplicationError<*> -> {
if (result.throwable is GroupInsufficientRightsException) {
- SaveState.InsufficientRights
+ internalUiState.update { it.copy(saveState = SaveState.InsufficientRights) }
} else {
throw result.throwable
}
@@ -122,10 +138,6 @@ class MemberLabelViewModel(
is NetworkResult.StatusCodeError<*> -> throw result.exception
}
-
- internalUiState.update {
- it.copy(saveState = newSaveState)
- }
}
}
@@ -134,6 +146,21 @@ class MemberLabelViewModel(
it.copy(saveState = null)
}
}
+
+ fun onAboutOverrideSheetShown() {
+ internalUiState.update {
+ it.copy(showAboutOverrideSheet = false)
+ }
+ }
+
+ fun onAboutOverrideSheetDismissed(dontShowAgain: Boolean) {
+ if (dontShowAgain) {
+ memberLabelRepo.markMemberLabelAboutOverrideWarningDismissed()
+ }
+ internalUiState.update {
+ it.copy(saveState = SaveState.Success)
+ }
+ }
}
data class MemberLabelUiState(
@@ -142,7 +169,8 @@ data class MemberLabelUiState(
val recipient: Recipient? = null,
val senderNameColor: NameColor? = null,
val hasChanges: Boolean = false,
- val saveState: SaveState? = null
+ val saveState: SaveState? = null,
+ val showAboutOverrideSheet: Boolean = false
) {
val sanitizedLabelText: String get() = MemberLabel.sanitizeLabelText(labelText)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java
index e0724869b7..19efeea2c4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/UiHintValues.java
@@ -10,28 +10,29 @@ public class UiHintValues extends SignalStoreValues {
private static final int NEVER_DISPLAY_PULL_TO_FILTER_TIP_THRESHOLD = 3;
private static final int HAS_SEEN_PINNED_MESSAGE_SHEET_THRESHOLD = 3;
- private static final String HAS_SEEN_GROUP_SETTINGS_MENU_TOAST = "uihints.has_seen_group_settings_menu_toast";
- private static final String HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE = "uihints.has_confirmed_delete_for_everyone_once";
- private static final String HAS_SET_OR_SKIPPED_USERNAME_CREATION = "uihints.has_set_or_skipped_username_creation";
- private static final String NEVER_DISPLAY_PULL_TO_FILTER_TIP = "uihints.never_display_pull_to_filter_tip";
- private static final String HAS_SEEN_SCHEDULED_MESSAGES_INFO_ONCE = "uihints.has_seen_scheduled_messages_info_once";
- private static final String HAS_SEEN_SAFETY_NUMBER_NUX = "uihints.has_seen_safety_number_nux";
- private static final String DECLINED_NOTIFICATION_LOGS_PROMPT = "uihints.declined_notification_logs";
- private static final String LAST_NOTIFICATION_LOGS_PROMPT_TIME = "uihints.last_notification_logs_prompt";
- private static final String DISMISSED_BATTERY_SAVER_PROMPT = "uihints.declined_battery_saver_prompt";
- private static final String LAST_BATTERY_SAVER_PROMPT = "uihints.last_battery_saver_prompt";
- private static final String LAST_CRASH_PROMPT = "uihints.last_crash_prompt";
- private static final String HAS_COMPLETED_USERNAME_ONBOARDING = "uihints.has_completed_username_onboarding";
- private static final String HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET = "uihints.has_seen_double_tap_edit_education_sheet";
- private static final String DISMISSED_CONTACTS_PERMISSION_BANNER = "uihints.dismissed_contacts_permission_banner";
- private static final String HAS_SEEN_DELETE_SYNC_EDUCATION_SHEET = "uihints.has_seen_delete_sync_education_sheet";
- private static final String LAST_SUPPORT_VERSION_SEEN = "uihints.last_support_version_seen";
- private static final String HAS_EVER_ENABLED_REMOTE_BACKUPS = "uihints.has_ever_enabled_remote_backups";
- private static final String HAS_SEEN_CHAT_FOLDERS_EDUCATION_SHEET = "uihints.has_seen_chat_folders_education_sheet";
- private static final String HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET = "uihints.has_seen_link_device_qr_education_sheet";
- private static final String HAS_DISMISSED_SAVE_STORAGE_WARNING = "uihints.has_dismissed_save_storage_warning";
- private static final String HAS_SEEN_PINNED_MESSAGE_SHEET = "uihints.has_seen_pinned_message_sheet";
- private static final String HAS_SEEN_VERIFY_AUTO_SHEET = "uihints.has_seen_verify_auto_sheet";
+ private static final String HAS_SEEN_GROUP_SETTINGS_MENU_TOAST = "uihints.has_seen_group_settings_menu_toast";
+ private static final String HAS_CONFIRMED_DELETE_FOR_EVERYONE_ONCE = "uihints.has_confirmed_delete_for_everyone_once";
+ private static final String HAS_SET_OR_SKIPPED_USERNAME_CREATION = "uihints.has_set_or_skipped_username_creation";
+ private static final String NEVER_DISPLAY_PULL_TO_FILTER_TIP = "uihints.never_display_pull_to_filter_tip";
+ private static final String HAS_SEEN_SCHEDULED_MESSAGES_INFO_ONCE = "uihints.has_seen_scheduled_messages_info_once";
+ private static final String HAS_SEEN_SAFETY_NUMBER_NUX = "uihints.has_seen_safety_number_nux";
+ private static final String DECLINED_NOTIFICATION_LOGS_PROMPT = "uihints.declined_notification_logs";
+ private static final String LAST_NOTIFICATION_LOGS_PROMPT_TIME = "uihints.last_notification_logs_prompt";
+ private static final String DISMISSED_BATTERY_SAVER_PROMPT = "uihints.declined_battery_saver_prompt";
+ private static final String LAST_BATTERY_SAVER_PROMPT = "uihints.last_battery_saver_prompt";
+ private static final String LAST_CRASH_PROMPT = "uihints.last_crash_prompt";
+ private static final String HAS_COMPLETED_USERNAME_ONBOARDING = "uihints.has_completed_username_onboarding";
+ private static final String HAS_SEEN_DOUBLE_TAP_EDIT_EDUCATION_SHEET = "uihints.has_seen_double_tap_edit_education_sheet";
+ private static final String DISMISSED_CONTACTS_PERMISSION_BANNER = "uihints.dismissed_contacts_permission_banner";
+ private static final String HAS_SEEN_DELETE_SYNC_EDUCATION_SHEET = "uihints.has_seen_delete_sync_education_sheet";
+ private static final String LAST_SUPPORT_VERSION_SEEN = "uihints.last_support_version_seen";
+ private static final String HAS_EVER_ENABLED_REMOTE_BACKUPS = "uihints.has_ever_enabled_remote_backups";
+ private static final String HAS_SEEN_CHAT_FOLDERS_EDUCATION_SHEET = "uihints.has_seen_chat_folders_education_sheet";
+ private static final String HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET = "uihints.has_seen_link_device_qr_education_sheet";
+ private static final String HAS_DISMISSED_SAVE_STORAGE_WARNING = "uihints.has_dismissed_save_storage_warning";
+ private static final String HAS_SEEN_PINNED_MESSAGE_SHEET = "uihints.has_seen_pinned_message_sheet";
+ private static final String HAS_SEEN_VERIFY_AUTO_SHEET = "uihints.has_seen_verify_auto_sheet";
+ private static final String HAS_DISMISSED_MEMBER_LABEL_ABOUT_OVERRIDE_WARNING = "uihints.has_dismissed_member_label_about_override_warning";
UiHintValues(@NonNull KeyValueStore store) {
super(store);
@@ -241,4 +242,12 @@ public class UiHintValues extends SignalStoreValues {
public void setSeenVerifyAutomaticallySheet() {
putBoolean(HAS_SEEN_VERIFY_AUTO_SHEET, true);
}
+
+ public boolean hasDismissedMemberLabelAboutOverrideWarning() {
+ return getBoolean(HAS_DISMISSED_MEMBER_LABEL_ABOUT_OVERRIDE_WARNING, false);
+ }
+
+ public void markMemberLabelAboutOverrideWarningDismissed() {
+ putBoolean(HAS_DISMISSED_MEMBER_LABEL_ABOUT_OVERRIDE_WARNING, true);
+ }
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9f5771121e..ac7b25e527 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -9495,5 +9495,10 @@
Edit your label
+
+ Member label display
+
+ In this group, your Member Label will be displayed beside your photo in place of your About.
+
diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModelTest.kt
index 269137cbf6..3a36df99ee 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModelTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/groups/memberlabel/MemberLabelViewModelTest.kt
@@ -7,7 +7,9 @@ package org.thoughtcrime.securesms.groups.memberlabel
import io.mockk.coEvery
import io.mockk.coVerify
+import io.mockk.every
import io.mockk.mockk
+import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -21,6 +23,7 @@ import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupInsufficientRightsException
import org.thoughtcrime.securesms.groups.memberlabel.MemberLabelUiState.SaveState
+import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.CoroutineDispatcherRule
import org.whispersystems.signalservice.api.NetworkResult
@@ -41,13 +44,21 @@ class MemberLabelViewModelTest {
fun setUp() {
coEvery { memberLabelRepo.getRecipient(any()) } returns mockk(relaxed = true)
coEvery { memberLabelRepo.getSenderNameColor(any(), any()) } returns NameColor(0, 0)
+ every { memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning() } returns false
}
+ private fun createViewModel() = MemberLabelViewModel(
+ memberLabelRepo = memberLabelRepo,
+ groupId = groupId,
+ recipientId = recipientId,
+ sanitizeEmoji = { emoji -> emoji.takeIf { it.isNotBlank() } }
+ )
+
@Test
fun `isSaveEnabled returns true when label text is different from the original value`() {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("Modified")
assertTrue(viewModel.uiState.value.isSaveEnabled)
@@ -55,9 +66,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when label text is the same as the original value`() {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("Original")
assertFalse(viewModel.uiState.value.isSaveEnabled)
@@ -65,9 +76,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns true when label text is valid and the emoji is different from the original value`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelEmojiChanged("🎉")
assertTrue(viewModel.uiState.value.isSaveEnabled)
@@ -75,18 +86,18 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when the label and emoji are not changed`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Label")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Label")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
assertFalse(viewModel.uiState.value.isSaveEnabled)
}
@Test
fun `isSaveEnabled returns false when the label and emoji are changed to the original value`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelEmojiChanged("🫢")
viewModel.onLabelTextChanged("Modified")
@@ -99,9 +110,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when label is too short`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("")
viewModel.onLabelEmojiChanged("🎉")
@@ -110,9 +121,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns true when clearLabel is called with existing label and emoji`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.clearLabel()
assertTrue(viewModel.uiState.value.isSaveEnabled)
@@ -120,9 +131,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns true when clearLabel is called with existing label without emoji`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.clearLabel()
assertTrue(viewModel.uiState.value.isSaveEnabled)
@@ -130,9 +141,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when clearLabel is called with no existing label`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.clearLabel()
assertFalse(viewModel.uiState.value.isSaveEnabled)
@@ -140,9 +151,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns true when both emoji and label are modified`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("New Label")
viewModel.onLabelEmojiChanged("🚀")
@@ -151,9 +162,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when only emoji is changed without an existing label`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelEmojiChanged("🎉")
assertFalse(viewModel.uiState.value.isSaveEnabled)
@@ -161,9 +172,9 @@ class MemberLabelViewModelTest {
@Test
fun `save does not call setLabel when isSaveEnabled is false`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.save()
coVerify(exactly = 0) { memberLabelRepo.setLabel(groupId, any()) }
@@ -171,9 +182,9 @@ class MemberLabelViewModelTest {
@Test
fun `save does not call setLabel when label is less than 1 character`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Label")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("")
viewModel.onLabelEmojiChanged("🎉")
viewModel.save()
@@ -183,10 +194,10 @@ class MemberLabelViewModelTest {
@Test
fun `save calls setLabel with truncated label when label exceeds max length`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("A".repeat(30))
viewModel.save()
@@ -200,9 +211,9 @@ class MemberLabelViewModelTest {
@Test
fun `save does not call setLabel when emoji is set with no label`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelEmojiChanged("🎉")
viewModel.save()
@@ -211,10 +222,10 @@ class MemberLabelViewModelTest {
@Test
fun `save calls setLabel when label change is valid`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("New Label")
viewModel.onLabelEmojiChanged("🎉")
viewModel.save()
@@ -226,10 +237,10 @@ class MemberLabelViewModelTest {
@Test
fun `save calls setLabel with cleared values when clearLabel is called`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.clearLabel()
viewModel.save()
@@ -240,9 +251,9 @@ class MemberLabelViewModelTest {
@Test
fun `onLabelTextChanged counts emoji as single grapheme`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
val emoji = "\uD83C\uDF89" // 🎉
viewModel.onLabelTextChanged(emoji.repeat(30))
@@ -251,9 +262,9 @@ class MemberLabelViewModelTest {
@Test
fun `remainingCharacters counts emoji as single grapheme`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
val emoji = "\uD83C\uDF89" // 🎉
viewModel.onLabelTextChanged(emoji.repeat(10))
@@ -262,9 +273,9 @@ class MemberLabelViewModelTest {
@Test
fun `remainingCharacters counts mixed ascii and emoji correctly`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("Hello \uD83C\uDF89") // "Hello 🎉" = 7 graphemes
assertEquals(17, viewModel.uiState.value.remainingCharacters)
@@ -272,9 +283,9 @@ class MemberLabelViewModelTest {
@Test
fun `onLabelTextChanged does not truncate text within grapheme limit`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("Short label")
assertEquals("Short label", viewModel.uiState.value.labelText)
@@ -282,9 +293,9 @@ class MemberLabelViewModelTest {
@Test
fun `onLabelTextChanged truncates at exactly 24 graphemes with emoji`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
val input = "A".repeat(23) + "\uD83C\uDF89\uD83C\uDF89" // 25 graphemes
viewModel.onLabelTextChanged(input)
@@ -294,9 +305,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when the only change is trailing whitespace`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("Original ")
assertFalse(viewModel.uiState.value.isSaveEnabled)
@@ -304,9 +315,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns false when the only change is leading whitespace`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged(" Original")
assertFalse(viewModel.uiState.value.isSaveEnabled)
@@ -314,9 +325,9 @@ class MemberLabelViewModelTest {
@Test
fun `isSaveEnabled returns true when text differs beyond whitespace`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged(" Modified ")
assertTrue(viewModel.uiState.value.isSaveEnabled)
@@ -324,10 +335,10 @@ class MemberLabelViewModelTest {
@Test
fun `save sets saveState to Success when setLabel returns NetworkResult Success`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("New Label")
viewModel.save()
@@ -336,10 +347,10 @@ class MemberLabelViewModelTest {
@Test
fun `save sets saveState to NetworkError when setLabel returns NetworkResult NetworkError`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.NetworkError(IOException("Network failure"))
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("New Label")
viewModel.save()
@@ -348,13 +359,131 @@ class MemberLabelViewModelTest {
@Test
fun `save sets saveState to InsufficientRights when setLabel returns ApplicationError with GroupInsufficientRightsException`() = runTest(testDispatcher) {
- coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = null, text = "Original")
coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.ApplicationError(GroupInsufficientRightsException(RuntimeException("Insufficient rights (test)")))
- val viewModel = MemberLabelViewModel(memberLabelRepo, groupId, recipientId)
+ val viewModel = createViewModel()
viewModel.onLabelTextChanged("New Label")
viewModel.save()
assertEquals(SaveState.InsufficientRights, viewModel.uiState.value.saveState)
}
+
+ @Test
+ fun `save shows about override warning when recipient has about text and the warning hasn't been dismissed`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getRecipient(any()) } returns Recipient(about = "Some about text")
+ coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
+ every { memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning() } returns false
+
+ val viewModel = createViewModel()
+ viewModel.onLabelTextChanged("New Label")
+ viewModel.save()
+
+ assertTrue(viewModel.uiState.value.showAboutOverrideSheet)
+ }
+
+ @Test
+ fun `save shows about override warning when recipient has about emoji and the warning hasn't been dismissed`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getRecipient(any()) } returns Recipient(about = null, aboutEmoji = "😎")
+ coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
+ every { memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning() } returns false
+
+ val viewModel = createViewModel()
+ viewModel.onLabelTextChanged("New Label")
+ viewModel.save()
+
+ assertTrue(viewModel.uiState.value.showAboutOverrideSheet)
+ }
+
+ @Test
+ fun `save does not show about override warning when label is cleared`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns MemberLabel(emoji = "🎉", text = "Original")
+ coEvery { memberLabelRepo.getRecipient(any()) } returns Recipient(about = "Some about text", aboutEmoji = null)
+ coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
+ every { memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning() } returns false
+
+ val viewModel = createViewModel()
+ viewModel.clearLabel()
+ viewModel.save()
+
+ assertFalse(viewModel.uiState.value.showAboutOverrideSheet)
+ assertEquals(SaveState.Success, viewModel.uiState.value.saveState)
+ }
+
+ @Test
+ fun `save does not show about override warning when recipient has no about text or emoji`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getRecipient(any()) } returns Recipient(about = null, aboutEmoji = null)
+ coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
+
+ val viewModel = createViewModel()
+ viewModel.onLabelTextChanged("New Label")
+ viewModel.save()
+
+ assertFalse(viewModel.uiState.value.showAboutOverrideSheet)
+ assertEquals(SaveState.Success, viewModel.uiState.value.saveState)
+ }
+
+ @Test
+ fun `save does not show about override warning if the warning has been dismissed`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getRecipient(any()) } returns Recipient(about = "Some about text")
+ coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
+ every { memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning() } returns true
+
+ val viewModel = createViewModel()
+ viewModel.onLabelTextChanged("New Label")
+ viewModel.save()
+
+ assertFalse(viewModel.uiState.value.showAboutOverrideSheet)
+ assertEquals(SaveState.Success, viewModel.uiState.value.saveState)
+ }
+
+ @Test
+ fun `onAboutOverrideSheetShown resets showAboutOverrideSheet`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+ coEvery { memberLabelRepo.getRecipient(any()) } returns Recipient(about = "Some about text")
+ coEvery { memberLabelRepo.setLabel(any(), any()) } returns NetworkResult.Success(Unit)
+ every { memberLabelRepo.hasDismissedMemberLabelAboutOverrideWarning() } returns false
+
+ val viewModel = createViewModel()
+ viewModel.onLabelTextChanged("New Label")
+ viewModel.save()
+
+ assertTrue(viewModel.uiState.value.showAboutOverrideSheet)
+ viewModel.onAboutOverrideSheetShown()
+ assertFalse(viewModel.uiState.value.showAboutOverrideSheet)
+ }
+
+ @Test
+ fun `onAboutOverrideSheetDismissed sets saveState to Success`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+
+ val viewModel = createViewModel()
+ viewModel.onAboutOverrideSheetDismissed(dontShowAgain = false)
+
+ assertEquals(SaveState.Success, viewModel.uiState.value.saveState)
+ }
+
+ @Test
+ fun `onAboutOverrideSheetDismissed marks about override warning as dismissed when dontShowAgain = true`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+
+ val viewModel = createViewModel()
+ viewModel.onAboutOverrideSheetDismissed(dontShowAgain = true)
+
+ verify(exactly = 1) { memberLabelRepo.markMemberLabelAboutOverrideWarningDismissed() }
+ }
+
+ @Test
+ fun `onAboutOverrideSheetDismissed does not mark about override warning as dismissed when dontShowAgain = false`() = runTest(testDispatcher) {
+ coEvery { memberLabelRepo.getLabel(groupId, any()) } returns null
+
+ val viewModel = createViewModel()
+ viewModel.onAboutOverrideSheetDismissed(dontShowAgain = false)
+
+ verify(exactly = 0) { memberLabelRepo.markMemberLabelAboutOverrideWarningDismissed() }
+ }
}