mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-27 05:03:28 +00:00
Update message request states for 1:1 and groups chats.
This commit is contained in:
committed by
Greyson Parrelli
parent
20cecbd5cd
commit
886bebb088
@@ -135,5 +135,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
void onItemDoubleClick(MultiselectPart multiselectPart);
|
||||
void onPaymentTombstoneClicked();
|
||||
void onDisplayMediaNoLongerAvailableSheet();
|
||||
void onShowUnverifiedProfileSheet(boolean forGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.signal.core.util.Result
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.concurrent.addTo
|
||||
import org.signal.core.util.getParcelableArrayListExtraCompat
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.AvatarPreviewActivity
|
||||
import org.thoughtcrime.securesms.BlockUnblockDialog
|
||||
import org.thoughtcrime.securesms.InviteActivity
|
||||
@@ -52,6 +53,8 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.NO_TINT
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.AvatarPreference
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.preferences.BioTextPreference
|
||||
@@ -492,6 +495,18 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
dividerPref()
|
||||
}
|
||||
|
||||
if (state.recipient.isReleaseNotes) {
|
||||
textPref(
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_official_20),
|
||||
title = DSLSettingsText.from(R.string.ReleaseNotes__this_is_official_chat)
|
||||
)
|
||||
textPref(
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_bell_20),
|
||||
title = DSLSettingsText.from(R.string.ReleaseNotes__keep_up_to_date)
|
||||
)
|
||||
dividerPref()
|
||||
}
|
||||
|
||||
val summary = DSLSettingsText.from(formatDisappearingMessagesLifespan(state.disappearingMessagesLifespan))
|
||||
val icon = if (state.disappearingMessagesLifespan <= 0 || state.recipient.isBlocked) {
|
||||
R.drawable.symbol_timer_slash_24
|
||||
@@ -633,6 +648,27 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||
)
|
||||
}
|
||||
|
||||
if (state.recipient.isReleaseNotes) {
|
||||
dividerPref()
|
||||
sectionHeaderPref(R.string.preferences__help)
|
||||
|
||||
externalLinkPref(
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_help_24),
|
||||
title = DSLSettingsText.from(R.string.HelpSettingsFragment__support_center),
|
||||
linkId = R.string.support_center_url
|
||||
)
|
||||
clickPref(
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_invite_24),
|
||||
title = DSLSettingsText.from(R.string.HelpSettingsFragment__contact_us),
|
||||
onClick = { startActivity(AppSettingsActivity.help(requireContext())) }
|
||||
)
|
||||
clickPref(
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_heart_24),
|
||||
title = DSLSettingsText.from(R.string.preferences__donate_to_signal),
|
||||
onClick = { startActivity(CheckoutFlowActivity.createIntent(requireContext(), InAppPaymentType.ONE_TIME_DONATION)) }
|
||||
)
|
||||
}
|
||||
|
||||
state.withRecipientSettingsState { recipientSettingsState ->
|
||||
if (state.recipient.badges.isNotEmpty() && !state.recipient.isSelf) {
|
||||
dividerPref()
|
||||
|
||||
@@ -213,9 +213,10 @@ class DSLConfiguration {
|
||||
|
||||
fun textPref(
|
||||
title: DSLSettingsText? = null,
|
||||
summary: DSLSettingsText? = null
|
||||
summary: DSLSettingsText? = null,
|
||||
icon: DSLSettingsIcon? = null
|
||||
) {
|
||||
val preference = TextPreference(title, summary)
|
||||
val preference = TextPreference(title, summary, icon)
|
||||
children.add(preference)
|
||||
}
|
||||
|
||||
@@ -257,8 +258,9 @@ abstract class PreferenceModel<T : PreferenceModel<T>>(
|
||||
|
||||
class TextPreference(
|
||||
title: DSLSettingsText?,
|
||||
summary: DSLSettingsText?
|
||||
) : PreferenceModel<TextPreference>(title = title, summary = summary)
|
||||
summary: DSLSettingsText?,
|
||||
icon: DSLSettingsIcon? = null
|
||||
) : PreferenceModel<TextPreference>(title = title, summary = summary, icon = icon)
|
||||
|
||||
class LearnMoreTextPreference(
|
||||
override val title: DSLSettingsText?,
|
||||
|
||||
@@ -5,12 +5,14 @@ import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewKt;
|
||||
@@ -92,25 +94,39 @@ public class ConversationHeaderView extends ConstraintLayout {
|
||||
return title.toString();
|
||||
}
|
||||
|
||||
public void setAbout(@NonNull Recipient recipient) {
|
||||
String about;
|
||||
if (recipient.isReleaseNotes()) {
|
||||
about = getContext().getString(R.string.ReleaseNotes__signal_release_notes_and_news);
|
||||
} else {
|
||||
about = recipient.getCombinedAboutAndEmoji();
|
||||
}
|
||||
|
||||
binding.messageRequestAbout.setText(about);
|
||||
binding.messageRequestAbout.setVisibility(TextUtils.isEmpty(about) ? GONE : VISIBLE);
|
||||
public void showReleaseNoteHeader() {
|
||||
binding.messageRequestInfo.setVisibility(View.GONE);
|
||||
binding.releaseHeaderContainer.setVisibility(View.VISIBLE);
|
||||
binding.releaseHeaderDescription1.setText(prependIcon(getContext().getString(R.string.ReleaseNotes__this_is_official_chat_period), R.drawable.symbol_official_20));
|
||||
binding.releaseHeaderDescription2.setText(prependIcon(getContext().getString(R.string.ReleaseNotes__keep_up_to_date_period), R.drawable.symbol_bell_20));
|
||||
}
|
||||
|
||||
public void setSubtitle(@NonNull CharSequence subtitle, @DrawableRes int iconRes) {
|
||||
public void setAbout(@NonNull Recipient recipient) {
|
||||
String about = recipient.getCombinedAboutAndEmoji();
|
||||
binding.messageRequestAbout.setText(about);
|
||||
binding.messageRequestAbout.setVisibility(TextUtils.isEmpty(about) || recipient.isReleaseNotes() ? GONE : VISIBLE);
|
||||
}
|
||||
|
||||
public void setSubtitle(@NonNull CharSequence subtitle, @DrawableRes int iconRes, @Nullable Runnable onClick) {
|
||||
if (TextUtils.isEmpty(subtitle)) {
|
||||
hideSubtitle();
|
||||
return;
|
||||
}
|
||||
|
||||
binding.messageRequestSubtitle.setText(prependIcon(subtitle, iconRes));
|
||||
if (onClick != null) {
|
||||
binding.messageRequestSubtitle.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
CharSequence builder = SpanUtil.clickSubstring(
|
||||
subtitle,
|
||||
subtitle,
|
||||
listener -> onClick.run(),
|
||||
ContextCompat.getColor(getContext(), R.color.signal_colorOnSurface),
|
||||
true
|
||||
);
|
||||
binding.messageRequestSubtitle.setText(prependIcon(builder, iconRes));
|
||||
} else {
|
||||
binding.messageRequestSubtitle.setText(prependIcon(subtitle, iconRes));
|
||||
}
|
||||
|
||||
binding.messageRequestSubtitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@@ -134,6 +150,32 @@ public class ConversationHeaderView extends ConstraintLayout {
|
||||
binding.messageRequestButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void showWarningSubtitle() {
|
||||
binding.messageRequestReviewCarefully.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideWarningSubtitle() {
|
||||
binding.messageRequestReviewCarefully.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void setUnverifiedNameSubtitle(@DrawableRes int iconRes, @StringRes int clickableRes, boolean forGroup, @Nullable Runnable onClick) {
|
||||
binding.messageRequestProfileNameUnverified.setVisibility(View.VISIBLE);
|
||||
binding.messageRequestProfileNameUnverified.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
CharSequence builder = SpanUtil.clickSubstring(
|
||||
getContext(),
|
||||
R.string.ConversationFragment_profile_names_not_verified,
|
||||
clickableRes,
|
||||
listener -> onClick.run(),
|
||||
true,
|
||||
R.color.signal_colorOnSurface
|
||||
);
|
||||
binding.messageRequestProfileNameUnverified.setText(prependIcon(builder, iconRes, forGroup));
|
||||
}
|
||||
|
||||
public void hideUnverifiedNameSubtitle() {
|
||||
binding.messageRequestProfileNameUnverified.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void showBackgroundBubble(boolean enabled) {
|
||||
if (enabled) {
|
||||
setBackgroundResource(R.drawable.wallpaper_bubble_background_18);
|
||||
@@ -176,6 +218,9 @@ public class ConversationHeaderView extends ConstraintLayout {
|
||||
binding.messageRequestInfoOutline.setVisibility(View.VISIBLE);
|
||||
binding.messageRequestDivider.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
} else if (ViewKt.isVisible(binding.releaseHeaderContainer)) {
|
||||
binding.messageRequestInfoOutline.setVisibility(View.GONE);
|
||||
binding.messageRequestDivider.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
binding.messageRequestInfoOutline.setVisibility(View.GONE);
|
||||
binding.messageRequestDivider.setVisibility(View.GONE);
|
||||
@@ -183,9 +228,15 @@ public class ConversationHeaderView extends ConstraintLayout {
|
||||
}
|
||||
|
||||
private @NonNull CharSequence prependIcon(@NonNull CharSequence input, @DrawableRes int iconRes) {
|
||||
return prependIcon(input, iconRes, false);
|
||||
}
|
||||
|
||||
|
||||
private @NonNull CharSequence prependIcon(@NonNull CharSequence input, @DrawableRes int iconRes, boolean useIntrinsicWidth) {
|
||||
Drawable drawable = ContextCompat.getDrawable(getContext(), iconRes);
|
||||
Preconditions.checkNotNull(drawable);
|
||||
drawable.setBounds(0, 0, (int) DimensionUnit.SP.toPixels(20), (int) DimensionUnit.SP.toPixels(20));
|
||||
int width = useIntrinsicWidth ? drawable.getIntrinsicWidth() : (int) DimensionUnit.SP.toPixels(20);
|
||||
drawable.setBounds(0, 0, width, (int) DimensionUnit.SP.toPixels(20));
|
||||
drawable.setColorFilter(ContextCompat.getColor(getContext(), R.color.signal_colorOnSurface), PorterDuff.Mode.SRC_ATOP);
|
||||
|
||||
return new SpannableStringBuilder()
|
||||
|
||||
@@ -186,9 +186,10 @@ public class ConversationTitleView extends ConstraintLayout {
|
||||
}
|
||||
|
||||
private void setRecipientTitle(@NonNull Recipient recipient) {
|
||||
if (recipient.isGroup()) setGroupRecipientTitle(recipient);
|
||||
else if (recipient.isSelf()) setSelfTitle();
|
||||
else setIndividualRecipientTitle(recipient);
|
||||
if (recipient.isGroup()) setGroupRecipientTitle(recipient);
|
||||
else if (recipient.isSelf()) setSelfTitle();
|
||||
else if (recipient.isReleaseNotes()) setReleaseNotesTitle(recipient);
|
||||
else setIndividualRecipientTitle(recipient);
|
||||
}
|
||||
|
||||
private void setGroupRecipientTitle(@NonNull Recipient recipient) {
|
||||
@@ -200,6 +201,13 @@ public class ConversationTitleView extends ConstraintLayout {
|
||||
updateSubtitleVisibility();
|
||||
}
|
||||
|
||||
private void setReleaseNotesTitle(@NonNull Recipient recipient) {
|
||||
final String displayName = recipient.getDisplayName(getContext());
|
||||
this.title.setText(displayName);
|
||||
this.subtitle.setText(R.string.ReleaseNotes__official_only_chat);
|
||||
updateSubtitleVisibility();
|
||||
}
|
||||
|
||||
private void setIndividualRecipientTitle(@NonNull Recipient recipient) {
|
||||
final String displayName = recipient.getDisplayName(getContext());
|
||||
this.title.setText(displayName);
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.view.GestureDetector
|
||||
import android.view.GestureDetector.SimpleOnGestureListener
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
@@ -23,6 +23,7 @@ import org.signal.core.util.toOptional
|
||||
import org.thoughtcrime.securesms.BindableConversationItem
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.Unbindable
|
||||
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity
|
||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener
|
||||
import org.thoughtcrime.securesms.conversation.ConversationAdapterBridge
|
||||
import org.thoughtcrime.securesms.conversation.ConversationHeaderView
|
||||
@@ -78,6 +79,7 @@ class ConversationAdapterV2(
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationAdapterV2::class.java)
|
||||
private val MIN_GROUPS_THRESHOLD = 2
|
||||
}
|
||||
|
||||
private val _selected = hashSetOf<MultiselectPart>()
|
||||
@@ -539,41 +541,66 @@ class ConversationAdapterV2(
|
||||
val title: String = conversationBanner.setTitle(recipient) {
|
||||
displayDialogFragment(AboutSheet.create(recipient))
|
||||
}
|
||||
|
||||
if (recipient.isReleaseNotes) {
|
||||
conversationBanner.showReleaseNoteHeader()
|
||||
}
|
||||
|
||||
conversationBanner.setAbout(recipient)
|
||||
|
||||
if (recipient.isGroup) {
|
||||
if (!groupInfo.hasExistingContacts) {
|
||||
conversationBanner.setUnverifiedNameSubtitle(R.drawable.symbol_group_question_20, R.string.ConversationFragment_group_names, true) {
|
||||
clickListener.onShowUnverifiedProfileSheet(true)
|
||||
}
|
||||
} else {
|
||||
conversationBanner.hideUnverifiedNameSubtitle()
|
||||
}
|
||||
|
||||
if (groupInfo.pendingMemberCount > 0) {
|
||||
val invited = context.resources.getQuantityString(R.plurals.MessageRequestProfileView_invited, groupInfo.pendingMemberCount, groupInfo.pendingMemberCount)
|
||||
conversationBanner.setSubtitle(context.resources.getQuantityString(R.plurals.MessageRequestProfileView_members_and_invited, groupInfo.fullMemberCount, groupInfo.fullMemberCount, invited), R.drawable.symbol_group_light_20)
|
||||
conversationBanner.setSubtitle(context.resources.getQuantityString(R.plurals.MessageRequestProfileView_members_and_invited, groupInfo.fullMemberCount, groupInfo.fullMemberCount, invited), R.drawable.symbol_group_light_20) { goToGroupSettings(recipient) }
|
||||
} else if (groupInfo.fullMemberCount > 0) {
|
||||
conversationBanner.setSubtitle(context.resources.getQuantityString(R.plurals.MessageRequestProfileView_members, groupInfo.fullMemberCount, groupInfo.fullMemberCount), R.drawable.symbol_group_light_20)
|
||||
conversationBanner.setSubtitle(context.resources.getQuantityString(R.plurals.MessageRequestProfileView_members, groupInfo.fullMemberCount, groupInfo.fullMemberCount), R.drawable.symbol_group_light_20) { goToGroupSettings(recipient) }
|
||||
} else {
|
||||
conversationBanner.hideSubtitle()
|
||||
}
|
||||
} else if (isSelf) {
|
||||
conversationBanner.setSubtitle(context.getString(R.string.ConversationFragment__you_can_add_notes_for_yourself_in_this_conversation), R.drawable.symbol_note_light_24)
|
||||
conversationBanner.setSubtitle(context.getString(R.string.ConversationFragment__you_can_add_notes_for_yourself_in_this_conversation), R.drawable.symbol_note_light_24, null)
|
||||
} else {
|
||||
if (recipient.nickname.isEmpty && !recipient.isSystemContact) {
|
||||
conversationBanner.setUnverifiedNameSubtitle(R.drawable.symbol_person_question_16, R.string.ConversationFragment_profile_names, false) {
|
||||
clickListener.onShowUnverifiedProfileSheet(false)
|
||||
}
|
||||
} else {
|
||||
conversationBanner.hideUnverifiedNameSubtitle()
|
||||
}
|
||||
|
||||
val subtitle: String? = recipient.takeIf { it.shouldShowE164 }?.e164?.map { e164: String? -> PhoneNumberFormatter.prettyPrint(e164!!) }?.orElse(null)
|
||||
if (subtitle == null || subtitle == title) {
|
||||
conversationBanner.hideSubtitle()
|
||||
} else {
|
||||
conversationBanner.setSubtitle(subtitle, R.drawable.symbol_phone_light_20)
|
||||
conversationBanner.setSubtitle(subtitle, R.drawable.symbol_phone_light_20, null)
|
||||
}
|
||||
}
|
||||
|
||||
conversationBanner.hideButton()
|
||||
|
||||
if (messageRequestState?.isAccepted == false && sharedGroups.isEmpty() && !isSelf && !recipient.isGroup) {
|
||||
conversationBanner.setDescription(context.getString(R.string.ConversationUpdateItem_no_groups_in_common_review_requests_carefully), R.drawable.symbol_error_circle_24)
|
||||
if (messageRequestState?.isAccepted == false && !isSelf && !recipient.isGroup) {
|
||||
if (sharedGroups.size < MIN_GROUPS_THRESHOLD) {
|
||||
conversationBanner.showWarningSubtitle()
|
||||
}
|
||||
conversationBanner.setButton(context.getString(R.string.ConversationFragment_safety_tips)) {
|
||||
clickListener.onShowSafetyTips(false)
|
||||
}
|
||||
} else if (messageRequestState?.isAccepted == false && recipient.isGroup && !groupInfo.hasExistingContacts) {
|
||||
conversationBanner.setDescription(context.getString(R.string.ConversationUpdateItem_no_contacts_in_this_group_review_requests_carefully), R.drawable.symbol_error_circle_24)
|
||||
conversationBanner.setDescription(getDescription(context, sharedGroups), R.drawable.symbol_group_light_20)
|
||||
} else if (messageRequestState?.isAccepted == false && recipient.isGroup) {
|
||||
conversationBanner.showWarningSubtitle()
|
||||
conversationBanner.setButton(context.getString(R.string.ConversationFragment_safety_tips)) {
|
||||
clickListener.onShowSafetyTips(true)
|
||||
}
|
||||
} else if (sharedGroups.isEmpty() || isSelf) {
|
||||
} else if ((recipient.isGroup && sharedGroups.isEmpty()) || isSelf) {
|
||||
conversationBanner.hideWarningSubtitle()
|
||||
if (TextUtils.isEmpty(groupInfo.description)) {
|
||||
conversationBanner.setLinkifyDescription(false)
|
||||
conversationBanner.hideDescription()
|
||||
@@ -592,23 +619,37 @@ class ConversationAdapterV2(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val description: String = when (sharedGroups.size) {
|
||||
1 -> context.getString(R.string.MessageRequestProfileView_member_of_one_group, sharedGroups[0])
|
||||
2 -> context.getString(R.string.MessageRequestProfileView_member_of_two_groups, sharedGroups[0], sharedGroups[1])
|
||||
3 -> context.getString(R.string.MessageRequestProfileView_member_of_many_groups, sharedGroups[0], sharedGroups[1], sharedGroups[2])
|
||||
else -> {
|
||||
val others: Int = sharedGroups.size - 2
|
||||
context.getString(
|
||||
R.string.MessageRequestProfileView_member_of_many_groups,
|
||||
sharedGroups[0],
|
||||
sharedGroups[1],
|
||||
context.resources.getQuantityString(R.plurals.MessageRequestProfileView_member_of_d_additional_groups, others, others)
|
||||
)
|
||||
}
|
||||
}
|
||||
conversationBanner.setDescription(HtmlCompat.fromHtml(description, 0), R.drawable.symbol_group_light_20)
|
||||
conversationBanner.hideWarningSubtitle()
|
||||
conversationBanner.setDescription(getDescription(context, sharedGroups), R.drawable.symbol_group_light_20)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDescription(context: Context, sharedGroups: List<String>): String {
|
||||
return when (sharedGroups.size) {
|
||||
0 -> context.getString(R.string.ConversationUpdateItem_no_groups_in_common_review_requests_carefully)
|
||||
1 -> context.getString(R.string.MessageRequestProfileView_member_of_one_group, sharedGroups[0])
|
||||
2 -> context.getString(R.string.MessageRequestProfileView_member_of_two_groups, sharedGroups[0], sharedGroups[1])
|
||||
3 -> context.getString(R.string.MessageRequestProfileView_member_of_many_groups, sharedGroups[0], sharedGroups[1], sharedGroups[2])
|
||||
else -> {
|
||||
val others: Int = sharedGroups.size - 2
|
||||
context.getString(
|
||||
R.string.MessageRequestProfileView_member_of_many_groups,
|
||||
sharedGroups[0],
|
||||
sharedGroups[1],
|
||||
context.resources.getQuantityString(R.plurals.MessageRequestProfileView_member_of_d_additional_groups, others, others)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToGroupSettings(recipient: Recipient) {
|
||||
val intent = ConversationSettingsActivity.forGroup(getContext(), recipient.requireGroupId())
|
||||
val bundle = ConversationSettingsActivity.createTransitionBundle(
|
||||
getContext(),
|
||||
conversationBanner.getViewById(R.id.message_request_avatar)
|
||||
)
|
||||
getContext().startActivity(intent, bundle)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OnScrollStateChangedListener : RecyclerView.OnScrollListener() {
|
||||
|
||||
@@ -2952,6 +2952,10 @@ class ConversationFragment :
|
||||
ConversationDialogs.displaySafetyNumberLearnMoreDialog(this@ConversationFragment, recipient)
|
||||
}
|
||||
|
||||
override fun onShowUnverifiedProfileSheet(forGroup: Boolean) {
|
||||
UnverifiedProfileNameBottomSheet.show(parentFragmentManager, forGroup)
|
||||
}
|
||||
|
||||
override fun onJoinGroupCallClicked() {
|
||||
val activity = activity ?: return
|
||||
val recipient = viewModel.recipientSnapshot ?: return
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestState
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestsBottomView
|
||||
@@ -82,7 +83,25 @@ class DisabledInputView @JvmOverloads constructor(
|
||||
setMessageRequestData(recipient, messageRequestState)
|
||||
setWallpaperEnabled(recipient.hasWallpaper)
|
||||
|
||||
setAcceptOnClickListener { listener?.onAcceptMessageRequestClicked() }
|
||||
setAcceptOnClickListener {
|
||||
if (messageRequestState.isFewConnectionsIndividual) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.MessageRequestBottomView_accept_request)
|
||||
.setMessage(R.string.MessageRequestBottomView_review_requests_carefully)
|
||||
.setPositiveButton(R.string.MessageRequestBottomView_accept) { _, _ -> listener?.onAcceptMessageRequestClicked() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
} else if (messageRequestState.isGroupV2Add) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.MessageRequestBottomView_join_group)
|
||||
.setMessage(R.string.MessageRequestBottomView_review_requests_carefully_groups)
|
||||
.setPositiveButton(R.string.MessageRequestBottomView_join) { _, _ -> listener?.onAcceptMessageRequestClicked() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
listener?.onAcceptMessageRequestClicked()
|
||||
}
|
||||
}
|
||||
setDeleteOnClickListener { listener?.onDeleteClicked() }
|
||||
setBlockOnClickListener { listener?.onBlockClicked() }
|
||||
setUnblockOnClickListener { listener?.onUnblockClicked() }
|
||||
|
||||
@@ -88,6 +88,7 @@ data class SafetyTipData(
|
||||
)
|
||||
|
||||
private val tips = listOf(
|
||||
SafetyTipData(heroImage = R.drawable.safety_tip0, titleText = R.string.SafetyTips_tip0_title, messageText = R.string.SafetyTips_tip0_message),
|
||||
SafetyTipData(heroImage = R.drawable.safety_tip1, titleText = R.string.SafetyTips_tip1_title, messageText = R.string.SafetyTips_tip1_message),
|
||||
SafetyTipData(heroImage = R.drawable.safety_tip2, titleText = R.string.SafetyTips_tip2_title, messageText = R.string.SafetyTips_tip2_message),
|
||||
SafetyTipData(heroImage = R.drawable.safety_tip3, titleText = R.string.SafetyTips_tip3_title, messageText = R.string.SafetyTips_tip3_message),
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.horizontalGutters
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
|
||||
/**
|
||||
* Bottom sheet shown in message request state that explains that profile names are unverified
|
||||
*/
|
||||
class UnverifiedProfileNameBottomSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 0.75f
|
||||
|
||||
companion object {
|
||||
private const val FOR_GROUP_ARG = "for_group"
|
||||
|
||||
@JvmStatic
|
||||
fun show(fragmentManager: FragmentManager, forGroup: Boolean) {
|
||||
UnverifiedProfileNameBottomSheet()
|
||||
.apply {
|
||||
arguments = bundleOf(
|
||||
FOR_GROUP_ARG to forGroup
|
||||
)
|
||||
}
|
||||
.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
ProfileNameSheet(
|
||||
forGroup = requireArguments().getBoolean(FOR_GROUP_ARG, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileNameSheet(forGroup: Boolean = true) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalGutters()
|
||||
) {
|
||||
BottomSheets.Handle()
|
||||
|
||||
val (imageVector, placeholder, text) =
|
||||
if (forGroup) {
|
||||
Triple(
|
||||
R.drawable.symbol_group_question_55,
|
||||
stringResource(R.string.ConversationFragment_group_names),
|
||||
stringResource(id = R.string.ProfileNameBottomSheet__group_names_on_signal, stringResource(R.string.ConversationFragment_group_names))
|
||||
)
|
||||
} else {
|
||||
Triple(
|
||||
R.drawable.symbol_person_question_40,
|
||||
stringResource(R.string.ConversationFragment_profile_names),
|
||||
stringResource(id = R.string.ProfileNameBottomSheet__profile_names_on_signal, stringResource(R.string.ConversationFragment_profile_names))
|
||||
)
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(imageVector),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(top = 38.dp, bottom = 24.dp)
|
||||
.size(height = 56.dp, width = 72.dp)
|
||||
)
|
||||
|
||||
val annotatedText = remember {
|
||||
buildAnnotatedString {
|
||||
val start = text.indexOf(placeholder)
|
||||
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(text.substring(start, start + placeholder.length))
|
||||
}
|
||||
append(text.substring(start + placeholder.length))
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = annotatedText,
|
||||
modifier = Modifier.padding(bottom = 20.dp)
|
||||
)
|
||||
|
||||
if (forGroup) {
|
||||
InfoRow(stringResource(R.string.ProfileNameBottomSheet__be_cautious_of_groups))
|
||||
InfoRow(stringResource(R.string.ProfileNameBottomSheet__profile_names_in_groups))
|
||||
} else {
|
||||
InfoRow(stringResource(R.string.ProfileNameBottomSheet__profile_names_arent_verified))
|
||||
InfoRow(stringResource(R.string.ProfileNameBottomSheet__be_cautious_of_accounts))
|
||||
}
|
||||
|
||||
InfoRow(stringResource(R.string.ProfileNameBottomSheet__dont_share_personal))
|
||||
|
||||
Spacer(Modifier.size(55.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InfoRow(text: String) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(IntrinsicSize.Min)
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, bottom = 12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(4.dp)
|
||||
.padding(vertical = 5.dp)
|
||||
.fillMaxHeight()
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(color = MaterialTheme.colorScheme.outline.copy(.4f))
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun ProfileNameSheetPreview() {
|
||||
Previews.BottomSheetPreview {
|
||||
ProfileNameSheet()
|
||||
}
|
||||
}
|
||||
@@ -386,6 +386,10 @@ class MessageDetailsFragment : FullScreenDialogFragment(), MessageDetailsAdapter
|
||||
Log.w(TAG, "Not yet implemented!", Exception())
|
||||
}
|
||||
|
||||
override fun onShowUnverifiedProfileSheet(forGroup: Boolean) {
|
||||
Log.w(TAG, "Not yet implemented!", Exception())
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onMessageDetailsFragmentDismissed()
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ import kotlin.Unit;
|
||||
|
||||
public final class MessageRequestRepository {
|
||||
|
||||
private static final String TAG = Log.tag(MessageRequestRepository.class);
|
||||
private static final String TAG = Log.tag(MessageRequestRepository.class);
|
||||
private static final int MIN_GROUPS_THRESHOLD = 2;
|
||||
|
||||
private final Context context;
|
||||
private final Executor executor;
|
||||
@@ -68,7 +69,7 @@ public final class MessageRequestRepository {
|
||||
if (groupRecord.get().isV2Group()) {
|
||||
List<Recipient> recipients = Recipient.resolvedList(groupRecord.get().getMembers());
|
||||
for (Recipient recipient : recipients) {
|
||||
if ((recipient.isProfileSharing() || recipient.getHasGroupsInCommon()) && !recipient.isSelf()) {
|
||||
if ((recipient.isProfileSharing() || recipient.isSystemContact()) && !recipient.isSelf()) {
|
||||
groupHasExistingContacts = true;
|
||||
break;
|
||||
}
|
||||
@@ -139,8 +140,11 @@ public final class MessageRequestRepository {
|
||||
} else {
|
||||
Recipient.HiddenState hiddenState = RecipientUtil.getRecipientHiddenState(threadId);
|
||||
boolean reportedAsSpam = reportedAsSpam(threadId);
|
||||
List<String> sharedGroups = SignalDatabase.groups().getPushGroupNamesContainingMember(recipient.getId());
|
||||
|
||||
if (hiddenState == Recipient.HiddenState.NOT_HIDDEN) {
|
||||
if (hiddenState == Recipient.HiddenState.NOT_HIDDEN && sharedGroups.size() < MIN_GROUPS_THRESHOLD) {
|
||||
return new MessageRequestState(MessageRequestState.State.INDIVIDUAL_FEW_CONNECTIONS, reportedAsSpam);
|
||||
} else if (hiddenState == Recipient.HiddenState.NOT_HIDDEN) {
|
||||
return new MessageRequestState(MessageRequestState.State.INDIVIDUAL, reportedAsSpam);
|
||||
} else if (hiddenState == Recipient.HiddenState.HIDDEN) {
|
||||
return new MessageRequestState(MessageRequestState.State.NONE_HIDDEN, reportedAsSpam);
|
||||
|
||||
@@ -19,6 +19,12 @@ data class MessageRequestState @JvmOverloads constructor(val state: State = Stat
|
||||
val isBlocked: Boolean
|
||||
get() = state == State.INDIVIDUAL_BLOCKED || state == State.BLOCKED_GROUP
|
||||
|
||||
val isFewConnectionsIndividual: Boolean
|
||||
get() = state == State.INDIVIDUAL_FEW_CONNECTIONS
|
||||
|
||||
val isGroupV2Add: Boolean
|
||||
get() = state == State.GROUP_V2_ADD
|
||||
|
||||
/**
|
||||
* An enum representing the possible message request states a user can be in.
|
||||
*/
|
||||
@@ -50,6 +56,9 @@ data class MessageRequestState @JvmOverloads constructor(val state: State = Stat
|
||||
/** A user is blocked */
|
||||
INDIVIDUAL_BLOCKED,
|
||||
|
||||
/** A message request and secondary confirmation is needed for an individual with less than 2 common groups */
|
||||
INDIVIDUAL_FEW_CONNECTIONS,
|
||||
|
||||
/** A message request is needed for an individual since they have been hidden */
|
||||
INDIVIDUAL_HIDDEN
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ class MessageRequestsBottomView @JvmOverloads constructor(context: Context, attr
|
||||
accept.setText(R.string.MessageRequestBottomView_accept)
|
||||
}
|
||||
|
||||
MessageRequestState.State.INDIVIDUAL -> {
|
||||
MessageRequestState.State.INDIVIDUAL,
|
||||
MessageRequestState.State.INDIVIDUAL_FEW_CONNECTIONS -> {
|
||||
question.text = HtmlCompat.fromHtml(
|
||||
context.getString(
|
||||
R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_they_wont_know_youve_seen_their_messages_until_you_accept,
|
||||
|
||||
@@ -28,11 +28,11 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import org.signal.core.ui.BottomSheets
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
@@ -48,6 +49,7 @@ import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.avatar.AvatarImage
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.conversation.v2.UnverifiedProfileNameBottomSheet
|
||||
import org.thoughtcrime.securesms.nicknames.ViewNoteSheet
|
||||
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
@@ -112,7 +114,8 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
),
|
||||
onClickSignalConnections = this::openSignalConnectionsSheet,
|
||||
onAvatarClicked = this::openProfilePhotoViewer,
|
||||
onNoteClicked = this::openNoteSheet
|
||||
onNoteClicked = this::openNoteSheet,
|
||||
onUnverifiedProfileClicked = this::openUnverifiedProfileSheet
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -130,6 +133,11 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
|
||||
dismiss()
|
||||
ViewNoteSheet.create(recipientId).show(parentFragmentManager, null)
|
||||
}
|
||||
|
||||
private fun openUnverifiedProfileSheet() {
|
||||
dismiss()
|
||||
UnverifiedProfileNameBottomSheet.show(fragmentManager = parentFragmentManager, forGroup = false)
|
||||
}
|
||||
}
|
||||
|
||||
private data class AboutModel(
|
||||
@@ -153,7 +161,8 @@ private fun Content(
|
||||
model: AboutModel,
|
||||
onClickSignalConnections: () -> Unit,
|
||||
onAvatarClicked: () -> Unit,
|
||||
onNoteClicked: () -> Unit
|
||||
onNoteClicked: () -> Unit,
|
||||
onUnverifiedProfileClicked: () -> Unit = {}
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
@@ -190,7 +199,7 @@ private fun Content(
|
||||
)
|
||||
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_person_24),
|
||||
startIcon = ImageVector.vectorResource(R.drawable.symbol_person_24),
|
||||
text = if (!model.isSelf && model.displayName.isNotBlank() && model.profileName.isNotBlank() && model.displayName != model.profileName) {
|
||||
stringResource(id = R.string.AboutSheet__user_set_display_name_and_profile_name, model.displayName, model.profileName)
|
||||
} else {
|
||||
@@ -203,7 +212,7 @@ private fun Content(
|
||||
val textColor = LocalContentColor.current
|
||||
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_edit_24),
|
||||
startIcon = ImageVector.vectorResource(R.drawable.symbol_edit_24),
|
||||
text = {
|
||||
Row {
|
||||
AndroidView(factory = ::EmojiTextView) {
|
||||
@@ -219,9 +228,19 @@ private fun Content(
|
||||
)
|
||||
}
|
||||
|
||||
if (!model.isSelf && !model.profileSharing && !model.systemContact) {
|
||||
AboutRow(
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_person_question_24),
|
||||
text = stringResource(id = R.string.AboutSheet__profile_names_are_not_verified),
|
||||
endIcon = ImageVector.vectorResource(id = R.drawable.symbol_chevron_right_compact_bold_16),
|
||||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onUnverifiedProfileClicked
|
||||
)
|
||||
}
|
||||
|
||||
if (!model.isSelf && model.verified) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_safety_number_24),
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_safety_number_24),
|
||||
text = stringResource(id = R.string.AboutSheet__verified),
|
||||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onClickSignalConnections
|
||||
@@ -231,25 +250,30 @@ private fun Content(
|
||||
if (!model.isSelf) {
|
||||
if (model.profileSharing || model.systemContact) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_connections_24),
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_connections_24),
|
||||
text = stringResource(id = R.string.AboutSheet__signal_connection),
|
||||
endIcon = painterResource(id = R.drawable.symbol_chevron_right_compact_bold_16),
|
||||
endIcon = ImageVector.vectorResource(id = R.drawable.symbol_chevron_right_compact_bold_16),
|
||||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onClickSignalConnections
|
||||
)
|
||||
} else if (model.groupsInCommon == 0) {
|
||||
AboutRow(
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_chat_badge_24),
|
||||
text = stringResource(id = R.string.AboutSheet__pending_message_request),
|
||||
modifier = Modifier.align(alignment = Alignment.Start)
|
||||
)
|
||||
} else {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_chat_x),
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_chat_x),
|
||||
text = stringResource(id = R.string.AboutSheet__no_direct_message, model.shortName),
|
||||
modifier = Modifier.align(alignment = Alignment.Start),
|
||||
onClick = onClickSignalConnections
|
||||
modifier = Modifier.align(alignment = Alignment.Start)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!model.isSelf && model.systemContact) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_person_circle_24),
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_person_circle_24),
|
||||
text = stringResource(id = R.string.AboutSheet__s_is_in_your_system_contacts, model.shortName),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -257,7 +281,7 @@ private fun Content(
|
||||
|
||||
if (model.formattedE164.isNotNullOrBlank()) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_phone_24),
|
||||
startIcon = ImageVector.vectorResource(R.drawable.symbol_phone_24),
|
||||
text = model.formattedE164,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -271,9 +295,9 @@ private fun Content(
|
||||
}
|
||||
|
||||
val groupsInCommonIcon = if (!model.profileSharing && model.groupsInCommon == 0) {
|
||||
painterResource(R.drawable.symbol_error_circle_24)
|
||||
ImageVector.vectorResource(R.drawable.symbol_error_circle_24)
|
||||
} else {
|
||||
painterResource(R.drawable.symbol_group_24)
|
||||
ImageVector.vectorResource(R.drawable.symbol_group_24)
|
||||
}
|
||||
|
||||
AboutRow(
|
||||
@@ -285,10 +309,10 @@ private fun Content(
|
||||
|
||||
if (model.note.isNotBlank()) {
|
||||
AboutRow(
|
||||
startIcon = painterResource(id = R.drawable.symbol_note_light_24),
|
||||
startIcon = ImageVector.vectorResource(id = R.drawable.symbol_note_light_24),
|
||||
text = model.note,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
endIcon = painterResource(id = R.drawable.symbol_chevron_right_compact_bold_16),
|
||||
endIcon = ImageVector.vectorResource(id = R.drawable.symbol_chevron_right_compact_bold_16),
|
||||
onClick = onNoteClicked
|
||||
)
|
||||
}
|
||||
@@ -299,10 +323,10 @@ private fun Content(
|
||||
|
||||
@Composable
|
||||
private fun AboutRow(
|
||||
startIcon: Painter,
|
||||
startIcon: ImageVector,
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
endIcon: Painter? = null,
|
||||
endIcon: ImageVector? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
AboutRow(
|
||||
@@ -324,10 +348,10 @@ private fun AboutRow(
|
||||
|
||||
@Composable
|
||||
private fun AboutRow(
|
||||
startIcon: Painter,
|
||||
startIcon: ImageVector,
|
||||
text: @Composable RowScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
endIcon: Painter? = null,
|
||||
endIcon: ImageVector? = null,
|
||||
onClick: (() -> Unit)? = null
|
||||
) {
|
||||
val padHorizontal = if (onClick != null) 19.dp else 32.dp
|
||||
@@ -350,7 +374,7 @@ private fun AboutRow(
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = startIcon,
|
||||
imageVector = startIcon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(end = 16.dp)
|
||||
@@ -361,7 +385,7 @@ private fun AboutRow(
|
||||
|
||||
if (endIcon != null) {
|
||||
Icon(
|
||||
painter = endIcon,
|
||||
imageVector = endIcon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
@@ -549,6 +573,35 @@ private fun ContentPreviewNotAConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
private fun ContentPreviewNotAConnectionNoGroups() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
Content(
|
||||
model = AboutModel(
|
||||
isSelf = false,
|
||||
displayName = "Peter Parker",
|
||||
shortName = "Peter",
|
||||
profileName = "Peter Parker",
|
||||
about = "(spoilers) dead",
|
||||
verified = false,
|
||||
hasAvatar = true,
|
||||
recipientForAvatar = Recipient.UNKNOWN,
|
||||
formattedE164 = null,
|
||||
profileSharing = false,
|
||||
systemContact = false,
|
||||
groupsInCommon = 0,
|
||||
note = ""
|
||||
),
|
||||
onClickSignalConnections = {},
|
||||
onAvatarClicked = {},
|
||||
onNoteClicked = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light Theme", group = "about row", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark Theme", group = "about row", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
@@ -556,9 +609,9 @@ private fun AboutRowPreview() {
|
||||
SignalTheme {
|
||||
Surface {
|
||||
AboutRow(
|
||||
startIcon = painterResource(R.drawable.symbol_person_24),
|
||||
startIcon = ImageVector.vectorResource(R.drawable.symbol_person_24),
|
||||
text = "Maya Johnson",
|
||||
endIcon = painterResource(id = R.drawable.symbol_chevron_right_compact_bold_16)
|
||||
endIcon = ImageVector.vectorResource(id = R.drawable.symbol_chevron_right_compact_bold_16)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,10 @@ public final class SpanUtil {
|
||||
return spannable;
|
||||
}
|
||||
|
||||
public static Spannable clickSubstring(@NonNull Context context, @StringRes int mainString, @StringRes int clickableString, @NonNull View.OnClickListener clickListener) {
|
||||
return clickSubstring(context, mainString, clickableString, clickListener, false, R.color.signal_accent_primary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes two resources:
|
||||
* - one resource that has a single string placeholder
|
||||
@@ -198,8 +202,10 @@ public final class SpanUtil {
|
||||
*
|
||||
* -> This is a clickable string.
|
||||
* (where "clickable" is blue and will trigger the provided click listener when clicked)
|
||||
*
|
||||
* Can optionally configure the color & if it's underlined. Default is blue with no underline.
|
||||
*/
|
||||
public static Spannable clickSubstring(@NonNull Context context, @StringRes int mainString, @StringRes int clickableString, @NonNull View.OnClickListener clickListener) {
|
||||
public static Spannable clickSubstring(@NonNull Context context, @StringRes int mainString, @StringRes int clickableString, @NonNull View.OnClickListener clickListener, boolean shouldUnderline, int linkColor) {
|
||||
String main = context.getString(mainString, SPAN_PLACE_HOLDER);
|
||||
String clickable = context.getString(clickableString);
|
||||
|
||||
@@ -217,8 +223,8 @@ public final class SpanUtil {
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
ds.setColor(context.getResources().getColor(R.color.signal_accent_primary));
|
||||
ds.setUnderlineText(shouldUnderline);
|
||||
ds.setColor(context.getResources().getColor(linkColor));
|
||||
}
|
||||
}, start, start + clickable.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
|
||||
@@ -238,13 +244,21 @@ public final class SpanUtil {
|
||||
public static CharSequence clickSubstring(@NonNull CharSequence fullString,
|
||||
@NonNull CharSequence substring,
|
||||
@NonNull View.OnClickListener clickListener,
|
||||
@ColorInt int linkColor)
|
||||
@ColorInt int linkColor) {
|
||||
return clickSubstring(fullString, substring, clickListener, linkColor, false);
|
||||
}
|
||||
|
||||
public static CharSequence clickSubstring(@NonNull CharSequence fullString,
|
||||
@NonNull CharSequence substring,
|
||||
@NonNull View.OnClickListener clickListener,
|
||||
@ColorInt int linkColor,
|
||||
boolean shouldUnderline)
|
||||
{
|
||||
ClickableSpan clickable = new ClickableSpan() {
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
ds.setUnderlineText(shouldUnderline);
|
||||
ds.setColor(linkColor);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user