mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-02 08:23:00 +01:00
Warn user when their member label will show instead of their about text.
This commit is contained in:
committed by
Greyson Parrelli
parent
622d9c909f
commit
dc1fdffe6a
@@ -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()
|
||||
}
|
||||
@@ -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 -> {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9495,5 +9495,10 @@
|
||||
<!-- Button to edit an existing member label. -->
|
||||
<string name="MemberLabelsEducation__edit_label">Edit your label</string>
|
||||
|
||||
<!-- Title for screen shown to let the user know that displaying their member label will take priority over their about text. -->
|
||||
<string name="MemberLabelsAboutOverride__title">Member label display</string>
|
||||
<!-- Body for screen shown to let the user know that displaying their member label will take priority over their about text. -->
|
||||
<string name="MemberLabelsAboutOverride__body">In this group, your Member Label will be displayed beside your photo in place of your About.</string>
|
||||
|
||||
<!-- EOF -->
|
||||
</resources>
|
||||
|
||||
@@ -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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Label")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = "🎉", text = "Label")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = "🎉", text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Label")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = "🎉", text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = "🎉", text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Label")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Label")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = "🎉", text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns null
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<RecipientId>()) } returns MemberLabel(emoji = null, text = "Original")
|
||||
coEvery { memberLabelRepo.getLabel(groupId, any<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } 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<Recipient>()) } returns null
|
||||
|
||||
val viewModel = createViewModel()
|
||||
viewModel.onAboutOverrideSheetDismissed(dontShowAgain = false)
|
||||
|
||||
verify(exactly = 0) { memberLabelRepo.markMemberLabelAboutOverrideWarningDismissed() }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user