mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-04 04:55:16 +01:00
Update release notes chat styling.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package org.thoughtcrime.securesms.components
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Shader
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
* Draws [bitmap] as a repeating tiled pattern rotated by [rotationDegrees].
|
||||
*/
|
||||
class RotatedTiledDrawable(
|
||||
private val bitmap: Bitmap,
|
||||
private val rotationDegrees: Float
|
||||
) : Drawable() {
|
||||
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
shader = android.graphics.BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
|
||||
}
|
||||
|
||||
override fun onBoundsChange(bounds: android.graphics.Rect) {
|
||||
paint.shader.setLocalMatrix(
|
||||
Matrix().apply { setRotate(rotationDegrees, bounds.exactCenterX(), bounds.exactCenterY()) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.drawRect(bounds, paint)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
paint.alpha = alpha
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
paint.colorFilter = colorFilter
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
}
|
||||
+5
-1
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord
|
||||
import org.thoughtcrime.securesms.database.model.addButton
|
||||
import org.thoughtcrime.securesms.database.model.addLink
|
||||
import org.thoughtcrime.securesms.database.model.addStyle
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -48,9 +49,12 @@ class InternalSettingsRepository(context: Context) {
|
||||
|
||||
val title = "Release Note Title"
|
||||
val bodyText = "Release note body. Aren't I awesome?"
|
||||
val body = "$title\n\n$bodyText"
|
||||
val linkUrl = "https://signal.org"
|
||||
val body = "$title\n\n$bodyText\n\n$linkUrl"
|
||||
val linkStart = body.length - linkUrl.length
|
||||
val bodyRangeList = BodyRangeList.Builder()
|
||||
.addStyle(BodyRangeList.BodyRange.Style.BOLD, 0, title.length)
|
||||
.addLink(linkUrl, linkStart, linkUrl.length)
|
||||
|
||||
bodyRangeList.addButton("Call to Action Text", callToAction, body.lastIndex, 0)
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ object BioTextPreference {
|
||||
|
||||
override fun getSubhead1Text(context: Context): String? {
|
||||
return if (recipient.isReleaseNotes) {
|
||||
context.getString(R.string.ReleaseNotes__signal_release_notes_and_news)
|
||||
null
|
||||
} else {
|
||||
recipient.combinedAboutAndEmoji
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -209,7 +210,12 @@ private fun ConversationHeaderContent(
|
||||
.padding(top = AvatarOverlapAbove)
|
||||
.width(277.dp)
|
||||
.then(
|
||||
if (hasWallpaper) {
|
||||
if (isReleaseNotes) {
|
||||
Modifier
|
||||
.clip(BorderShape)
|
||||
.background(colorResource(R.color.release_notes_header_background))
|
||||
.border(width = 2.dp, color = colorResource(R.color.release_notes_header_border), shape = BorderShape)
|
||||
} else if (hasWallpaper) {
|
||||
Modifier
|
||||
.clip(BorderShape)
|
||||
.background(if (isSystemInDarkTheme()) SignalTheme.colors.colorTransparentInverse5 else SignalTheme.colors.colorTransparent5)
|
||||
|
||||
@@ -204,6 +204,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
private Optional<MessageRecord> nextMessageRecord;
|
||||
private Locale locale;
|
||||
private boolean groupThread;
|
||||
private boolean isReleaseNotes;
|
||||
private LiveRecipient author;
|
||||
private RequestManager requestManager;
|
||||
private Optional<MessageRecord> previousMessage;
|
||||
@@ -412,6 +413,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
this.batchSelected = batchSelected;
|
||||
this.conversationRecipient = conversationRecipient.live();
|
||||
this.groupThread = conversationRecipient.isGroup();
|
||||
this.isReleaseNotes = conversationRecipient.isReleaseNotes();
|
||||
this.author = messageRecord.getFromRecipient().live();
|
||||
this.canPlayContent = false;
|
||||
this.mediaItem = null;
|
||||
@@ -772,6 +774,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
}
|
||||
|
||||
private @ColorInt int getDefaultBubbleColor(boolean hasWallpaper) {
|
||||
if (isReleaseNotes) {
|
||||
return ContextCompat.getColor(context, R.color.release_notes_bubble);
|
||||
}
|
||||
return hasWallpaper ? defaultBubbleColorForWallpaper : defaultBubbleColor;
|
||||
}
|
||||
|
||||
@@ -919,9 +924,18 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
footer.setOnlyShowSendingStatus(messageRecord.isRemoteDelete(), messageRecord);
|
||||
} else {
|
||||
bodyBubble.getBackground().setColorFilter(getDefaultBubbleColor(hasWallpaper), PorterDuff.Mode.SRC_IN);
|
||||
footer.setTextColor(colorizer.getIncomingFooterTextColor(context, hasWallpaper));
|
||||
footer.setIconColor(colorizer.getIncomingFooterIconColor(context, hasWallpaper));
|
||||
footer.setRevealDotColor(colorizer.getIncomingFooterIconColor(context, hasWallpaper));
|
||||
if (isReleaseNotes) {
|
||||
int releaseNotesTextColor = ContextCompat.getColor(context, R.color.release_notes_bubble_text);
|
||||
bodyText.setTextColor(releaseNotesTextColor);
|
||||
bodyText.setLinkTextColor(releaseNotesTextColor);
|
||||
footer.setTextColor(releaseNotesTextColor);
|
||||
footer.setIconColor(releaseNotesTextColor);
|
||||
footer.setRevealDotColor(releaseNotesTextColor);
|
||||
} else {
|
||||
footer.setTextColor(colorizer.getIncomingFooterTextColor(context, hasWallpaper));
|
||||
footer.setIconColor(colorizer.getIncomingFooterIconColor(context, hasWallpaper));
|
||||
footer.setRevealDotColor(colorizer.getIncomingFooterIconColor(context, hasWallpaper));
|
||||
}
|
||||
footer.setOnlyShowSendingStatus(false, messageRecord);
|
||||
}
|
||||
|
||||
@@ -1718,8 +1732,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
int end = messageBody.getSpanEnd(placeholder);
|
||||
URLSpan span = new InterceptableLongClickCopyLinkSpan(placeholder.getValue(),
|
||||
urlClickListener,
|
||||
ContextCompat.getColor(getContext(), R.color.signal_accent_primary),
|
||||
false);
|
||||
ContextCompat.getColor(getContext(), isReleaseNotes ? R.color.release_notes_bubble_text : R.color.signal_accent_primary),
|
||||
isReleaseNotes);
|
||||
|
||||
messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
@@ -167,6 +167,14 @@ internal object ConversationOptionsMenu {
|
||||
|
||||
if (recipient.isReleaseNotes) {
|
||||
hideMenuItem(menu, R.id.menu_add_shortcut)
|
||||
menu.findItem(R.id.menu_mute_notifications)?.apply {
|
||||
setIcon(R.drawable.symbol_bell_24)
|
||||
setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||
}
|
||||
menu.findItem(R.id.menu_unmute_notifications)?.apply {
|
||||
setIcon(R.drawable.symbol_bell_slash_24)
|
||||
setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||
}
|
||||
}
|
||||
|
||||
if (!SignalStore.labs.individualChatPlaintextExport) {
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ public class ConversationTitleView extends ConstraintLayout {
|
||||
if (recipient != null && recipient.isBlocked()) {
|
||||
startDrawable = ContextUtil.requireDrawable(getContext(), R.drawable.symbol_block_16);
|
||||
startDrawable.setBounds(0, 0, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18));
|
||||
} else if (recipient != null && recipient.isMuted()) {
|
||||
} else if (recipient != null && recipient.isMuted() && !recipient.isReleaseNotes()) {
|
||||
startDrawable = ContextUtil.requireDrawable(getContext(), R.drawable.ic_bell_disabled_16);
|
||||
startDrawable.setBounds(0, 0, ViewUtil.dpToPx(18), ViewUtil.dpToPx(18));
|
||||
}
|
||||
|
||||
+86
-43
@@ -18,6 +18,7 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.Rect
|
||||
@@ -42,6 +43,7 @@ import android.view.WindowManager
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import android.widget.Toast
|
||||
@@ -49,7 +51,9 @@ import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
@@ -59,6 +63,7 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentResultListener
|
||||
@@ -149,6 +154,7 @@ import org.thoughtcrime.securesms.components.InputAwareConstraintLayout
|
||||
import org.thoughtcrime.securesms.components.InputPanel
|
||||
import org.thoughtcrime.securesms.components.InsetAwareConstraintLayout
|
||||
import org.thoughtcrime.securesms.components.ProgressCardDialogFragment
|
||||
import org.thoughtcrime.securesms.components.RotatedTiledDrawable
|
||||
import org.thoughtcrime.securesms.components.ScrollToPositionDelegate
|
||||
import org.thoughtcrime.securesms.components.SendButton
|
||||
import org.thoughtcrime.securesms.components.SignalProgressDialog
|
||||
@@ -184,7 +190,6 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter
|
||||
import org.thoughtcrime.securesms.conversation.ConversationArgs
|
||||
import org.thoughtcrime.securesms.conversation.ConversationBottomSheetCallback
|
||||
import org.thoughtcrime.securesms.conversation.ConversationData
|
||||
import org.thoughtcrime.securesms.conversation.ConversationHeaderView
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents.ConversationScreenType
|
||||
import org.thoughtcrime.securesms.conversation.ConversationItem
|
||||
@@ -573,7 +578,7 @@ class ConversationFragment :
|
||||
private lateinit var attachmentManager: AttachmentManager
|
||||
private lateinit var multiselectItemDecoration: MultiselectItemDecoration
|
||||
private lateinit var openableGiftItemDecoration: OpenableGiftItemDecoration
|
||||
private lateinit var threadHeaderMarginDecoration: ThreadHeaderMarginDecoration
|
||||
private lateinit var conversationHeaderPositionDecoration: ConversationHeaderPositionDecoration
|
||||
private lateinit var conversationItemDecorations: ConversationItemDecorations
|
||||
private lateinit var optionsMenuCallback: ConversationOptionsMenuCallback
|
||||
|
||||
@@ -601,6 +606,8 @@ class ConversationFragment :
|
||||
private var firstPinRender: Boolean = true
|
||||
private var skipNextBackPressHandling: Boolean = false
|
||||
private var collapsibleEventScrollPosition: CollapsibleEventScrollPosition? = null
|
||||
private var releaseNotesLayoutApplied: Boolean = false
|
||||
private var releaseNotesWallpaperApplied: Boolean = false
|
||||
|
||||
private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy {
|
||||
override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) {
|
||||
@@ -694,6 +701,7 @@ class ConversationFragment :
|
||||
requireActivity(),
|
||||
binding.toolbarBackground,
|
||||
viewModel::wallpaperSnapshot,
|
||||
{ viewModel.recipientSnapshot?.isReleaseNotes == true },
|
||||
viewLifecycleOwner,
|
||||
incognito = args.isIncognito
|
||||
)
|
||||
@@ -761,10 +769,10 @@ class ConversationFragment :
|
||||
|
||||
binding.toolbar.addOnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
|
||||
binding.conversationItemRecycler.padding(top = bottom)
|
||||
if (bottom != oldBottom && ::threadHeaderMarginDecoration.isInitialized) {
|
||||
if (bottom != oldBottom && ::conversationHeaderPositionDecoration.isInitialized) {
|
||||
val newMargin = bottom + 16.dp
|
||||
if (threadHeaderMarginDecoration.toolbarMargin != newMargin) {
|
||||
threadHeaderMarginDecoration.toolbarMargin = newMargin
|
||||
if (conversationHeaderPositionDecoration.toolbarMargin != newMargin) {
|
||||
conversationHeaderPositionDecoration.toolbarMargin = newMargin
|
||||
binding.conversationItemRecycler.invalidateItemDecorations()
|
||||
}
|
||||
}
|
||||
@@ -1562,6 +1570,10 @@ class ConversationFragment :
|
||||
presentConversationTitle(inputReadyState.conversationRecipient)
|
||||
|
||||
val disabledInputView = binding.conversationDisabledInput
|
||||
val isReleaseNotes = inputReadyState.conversationRecipient.isReleaseNotes
|
||||
if (isReleaseNotes) {
|
||||
applyReleaseNotesLayout()
|
||||
}
|
||||
|
||||
var inputDisabled = true
|
||||
when {
|
||||
@@ -1572,22 +1584,42 @@ class ConversationFragment :
|
||||
inputReadyState.isRequestingMember == true -> disabledInputView.showAsRequestingMember()
|
||||
inputReadyState.isActiveGroup == false -> disabledInputView.showAsNoLongerAMember()
|
||||
inputReadyState.isAnnouncementGroup == true && inputReadyState.isAdmin == false -> disabledInputView.showAsAnnouncementGroupAdminsOnly()
|
||||
inputReadyState.conversationRecipient.isReleaseNotes -> disabledInputView.showAsReleaseNotesChannel(inputReadyState.conversationRecipient)
|
||||
isReleaseNotes -> Unit
|
||||
inputReadyState.shouldShowInviteToSignal() -> disabledInputView.showAsInviteToSignal(requireContext(), inputReadyState.conversationRecipient, inputReadyState.threadContainsSms)
|
||||
else -> inputDisabled = false
|
||||
}
|
||||
|
||||
inputPanel.setHideForMessageRequestState(inputDisabled)
|
||||
|
||||
if (inputDisabled) {
|
||||
if (inputDisabled && !isReleaseNotes) {
|
||||
binding.navBar.setBackgroundColor(disabledInputView.color)
|
||||
} else {
|
||||
} else if (!inputDisabled) {
|
||||
disabledInputView.clear()
|
||||
}
|
||||
|
||||
composeText.setMessageSendType(MessageSendType.SignalMessageSendType)
|
||||
}
|
||||
|
||||
private fun applyReleaseNotesLayout() {
|
||||
if (releaseNotesLayoutApplied) {
|
||||
return
|
||||
}
|
||||
releaseNotesLayoutApplied = true
|
||||
|
||||
binding.conversationReleaseNotesFloatingLabel.visible = true
|
||||
binding.conversationDisabledInput.visible = false
|
||||
|
||||
val navBarInset = ViewCompat.getRootWindowInsets(binding.root)?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
|
||||
binding.conversationItemRecycler.updatePadding(bottom = ViewUtil.dpToPx(72) + navBarInset)
|
||||
binding.navBar.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
ConstraintSet().apply {
|
||||
clone(binding.root)
|
||||
connect(binding.conversationItemRecyclerFrame.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
|
||||
applyTo(binding.root)
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentIdentityRecordsState(identityRecordsState: IdentityRecordsState) {
|
||||
binding.conversationTitleView.root.setVerified(identityRecordsState.isVerified)
|
||||
|
||||
@@ -1713,16 +1745,13 @@ class ConversationFragment :
|
||||
}
|
||||
|
||||
private fun onRecipientChanged(recipient: Recipient) {
|
||||
presentWallpaper(recipient.wallpaper)
|
||||
presentWallpaper(recipient)
|
||||
presentConversationTitle(recipient)
|
||||
presentChatColors(recipient.chatColors)
|
||||
invalidateOptionsMenu()
|
||||
updateMessageRequestAcceptedState(!viewModel.hasMessageRequestState)
|
||||
|
||||
recyclerViewColorizer.setChatColors(recipient.chatColors)
|
||||
if (adapter.onHasWallpaperChanged(hasWallpaper = recipient.wallpaper != null)) {
|
||||
conversationItemDecorations.hasWallpaper = recipient.wallpaper != null
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -1842,17 +1871,19 @@ class ConversationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentWallpaper(chatWallpaper: ChatWallpaper?) {
|
||||
if (chatWallpaper != null) {
|
||||
chatWallpaper.loadInto(binding.conversationWallpaper)
|
||||
ChatWallpaperDimLevelUtil.applyDimLevelForNightMode(binding.conversationWallpaperDim, chatWallpaper)
|
||||
private fun presentWallpaper(recipient: Recipient) {
|
||||
val chatWallpaper = recipient.wallpaper
|
||||
if (recipient.isReleaseNotes) {
|
||||
applyReleaseNotesWallpaper()
|
||||
} else {
|
||||
binding.conversationWallpaperDim.visible = false
|
||||
applyChatWallpaper(chatWallpaper)
|
||||
}
|
||||
|
||||
val wallpaperEnabled = chatWallpaper != null || recipient.isReleaseNotes
|
||||
|
||||
val toolbarTint = ContextCompat.getColor(
|
||||
requireContext(),
|
||||
if (chatWallpaper != null) {
|
||||
if (wallpaperEnabled) {
|
||||
CoreUiR.color.signal_colorNeutralInverse
|
||||
} else {
|
||||
CoreUiR.color.signal_colorOnSurface
|
||||
@@ -1863,7 +1894,6 @@ class ConversationFragment :
|
||||
binding.toolbar.setActionItemTint(toolbarTint)
|
||||
binding.toolbar.navigationIcon?.setTint(toolbarTint)
|
||||
|
||||
val wallpaperEnabled = chatWallpaper != null
|
||||
binding.conversationWallpaper.visible = wallpaperEnabled
|
||||
binding.scrollToBottom.setWallpaperEnabled(wallpaperEnabled)
|
||||
binding.scrollToMention.setWallpaperEnabled(wallpaperEnabled)
|
||||
@@ -1872,6 +1902,7 @@ class ConversationFragment :
|
||||
|
||||
val stateChanged = adapter.onHasWallpaperChanged(wallpaperEnabled)
|
||||
conversationItemDecorations.hasWallpaper = wallpaperEnabled
|
||||
conversationItemDecorations.isReleaseNotes = recipient.isReleaseNotes
|
||||
if (stateChanged) {
|
||||
binding.conversationItemRecycler.invalidateItemDecorations()
|
||||
}
|
||||
@@ -1888,12 +1919,39 @@ class ConversationFragment :
|
||||
)
|
||||
|
||||
if (!inputPanel.isHidden) {
|
||||
setNavBarBackgroundColor(chatWallpaper)
|
||||
setNavBarBackgroundColor(wallpaperEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNavBarBackgroundColor(chatWallpaper: ChatWallpaper?) {
|
||||
val navColor = if (chatWallpaper != null) {
|
||||
private fun applyReleaseNotesWallpaper() {
|
||||
if (releaseNotesWallpaperApplied) {
|
||||
return
|
||||
}
|
||||
releaseNotesWallpaperApplied = true
|
||||
|
||||
val tinted = DrawableUtil.tint(
|
||||
AppCompatResources.getDrawable(requireContext(), R.drawable.release_chat_background)!!,
|
||||
ContextCompat.getColor(requireContext(), R.color.release_notes_background_pattern)
|
||||
)
|
||||
val bitmap = DrawableUtil.toBitmap(tinted, tinted.intrinsicWidth, tinted.intrinsicHeight)
|
||||
|
||||
binding.conversationWallpaper.scaleType = ImageView.ScaleType.MATRIX
|
||||
binding.conversationWallpaper.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.release_notes_background))
|
||||
binding.conversationWallpaper.setImageDrawable(RotatedTiledDrawable(bitmap, -45f))
|
||||
binding.conversationWallpaperDim.visible = false
|
||||
}
|
||||
|
||||
private fun applyChatWallpaper(chatWallpaper: ChatWallpaper?) {
|
||||
if (chatWallpaper != null) {
|
||||
chatWallpaper.loadInto(binding.conversationWallpaper)
|
||||
ChatWallpaperDimLevelUtil.applyDimLevelForNightMode(binding.conversationWallpaperDim, chatWallpaper)
|
||||
} else {
|
||||
binding.conversationWallpaperDim.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setNavBarBackgroundColor(hasWallpaper: Boolean) {
|
||||
val navColor = if (hasWallpaper) {
|
||||
R.color.conversation_navigation_wallpaper
|
||||
} else {
|
||||
CoreUiR.color.signal_colorBackground
|
||||
@@ -2237,12 +2295,11 @@ class ConversationFragment :
|
||||
}
|
||||
)
|
||||
|
||||
threadHeaderMarginDecoration = ThreadHeaderMarginDecoration()
|
||||
conversationHeaderPositionDecoration = ConversationHeaderPositionDecoration()
|
||||
|
||||
val statusBarInset = ViewCompat.getRootWindowInsets(binding.root)?.getInsets(WindowInsetsCompat.Type.systemBars())?.top ?: 0
|
||||
threadHeaderMarginDecoration.toolbarMargin = statusBarInset + resources.getDimensionPixelSize(R.dimen.signal_m3_toolbar_height) + 16.dp
|
||||
binding.conversationItemRecycler.addItemDecoration(threadHeaderMarginDecoration)
|
||||
binding.conversationItemRecycler.addItemDecoration(ConversationHeaderPositionDecoration())
|
||||
conversationHeaderPositionDecoration.toolbarMargin = statusBarInset + resources.getDimensionPixelSize(R.dimen.signal_m3_toolbar_height) + 16.dp
|
||||
binding.conversationItemRecycler.addItemDecoration(conversationHeaderPositionDecoration)
|
||||
|
||||
conversationItemDecorations = ConversationItemDecorations(hasWallpaper = args.hasWallpaper)
|
||||
binding.conversationItemRecycler.addItemDecoration(conversationItemDecorations, 0)
|
||||
@@ -3330,7 +3387,8 @@ class ConversationFragment :
|
||||
|
||||
private fun presentComposeDivider() {
|
||||
val isAtBottom = isScrolledToBottom()
|
||||
if (isAtBottom && !wasAtBottom) {
|
||||
val suppress = viewModel.recipientSnapshot?.isReleaseNotes == true
|
||||
if ((isAtBottom && !wasAtBottom) || suppress) {
|
||||
ViewUtil.fadeOut(binding.composeDivider, 50, View.INVISIBLE)
|
||||
} else if (wasAtBottom && !isAtBottom) {
|
||||
ViewUtil.fadeIn(binding.composeDivider, 500)
|
||||
@@ -4734,10 +4792,6 @@ class ConversationFragment :
|
||||
launchIntent = this@ConversationFragment::startActivity
|
||||
)
|
||||
}
|
||||
|
||||
override fun onUnmuteReleaseNotesChannel() {
|
||||
viewModel.muteConversation(0L)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
@@ -5126,7 +5180,7 @@ class ConversationFragment :
|
||||
}
|
||||
|
||||
override fun onInputHidden() {
|
||||
setNavBarBackgroundColor(viewModel.wallpaperSnapshot)
|
||||
setNavBarBackgroundColor(viewModel.wallpaperSnapshot != null || viewModel.recipientSnapshot?.isReleaseNotes == true)
|
||||
viewModel.setIsMediaKeyboardShowing(false)
|
||||
}
|
||||
|
||||
@@ -5248,17 +5302,6 @@ class ConversationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ThreadHeaderMarginDecoration : RecyclerView.ItemDecoration() {
|
||||
var toolbarMargin: Int = 0
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
if (view is ConversationHeaderView) {
|
||||
outRect.top = toolbarMargin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class VoiceMessageRecordingSessionCallbacks : VoiceMessageRecordingDelegate.SessionCallback {
|
||||
override fun onSessionWillBegin() {
|
||||
getVoiceNoteMediaController().pausePlayback()
|
||||
|
||||
+29
-19
@@ -7,35 +7,45 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.conversation.ConversationHeaderView
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Adjusts the Conversation's recycler view translationY so that the conversation header
|
||||
* is pinned to the top of the visible area when content is too short to
|
||||
* fill the screen.
|
||||
* Reserves space above the [ConversationHeaderView] for the toolbar and adjusts the conversation RecyclerView's translationY so the header is pinned below the
|
||||
* toolbar when content is short enough to fit the viewport. The toolbar margin is only contributed when a translation is actually going to be applied; when
|
||||
* content overflows, no margin is added and no translation is applied.
|
||||
*/
|
||||
class ConversationHeaderPositionDecoration : RecyclerView.ItemDecoration() {
|
||||
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
if (parent.childCount == 0 || parent.canScrollVertically(-1) || parent.canScrollVertically(1)) {
|
||||
parent.translationY = 0f
|
||||
} else {
|
||||
val threadHeaderView: ConversationHeaderView = parent.children
|
||||
.filterIsInstance<ConversationHeaderView>()
|
||||
.firstOrNull() ?: run {
|
||||
parent.translationY = 0f
|
||||
return
|
||||
}
|
||||
private val bounds = Rect()
|
||||
|
||||
// A decorator adds the margin for the toolbar, margin is the difference of the bounds "height" and the view height
|
||||
val bounds = Rect()
|
||||
parent.getDecoratedBoundsWithMargins(threadHeaderView, bounds)
|
||||
val toolbarMargin = bounds.bottom - bounds.top - threadHeaderView.height
|
||||
var toolbarMargin: Int = 0
|
||||
|
||||
val childTop: Int = threadHeaderView.top - toolbarMargin
|
||||
parent.translationY = min(0, -childTop).toFloat()
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.getItemOffsets(outRect, view, parent, state)
|
||||
if (view is ConversationHeaderView && !parent.canScrollVertically(1)) {
|
||||
outRect.top = toolbarMargin
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
if (parent.canScrollVertically(1)) {
|
||||
parent.translationY = 0f
|
||||
return
|
||||
}
|
||||
|
||||
val threadHeaderView: ConversationHeaderView = parent.children
|
||||
.filterIsInstance<ConversationHeaderView>()
|
||||
.firstOrNull() ?: run {
|
||||
parent.translationY = 0f
|
||||
return
|
||||
}
|
||||
|
||||
parent.getDecoratedBoundsWithMargins(threadHeaderView, bounds)
|
||||
val margin = bounds.bottom - bounds.top - threadHeaderView.height
|
||||
val childTop: Int = threadHeaderView.top - margin
|
||||
parent.translationY = min(0, -childTop).toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -59,6 +59,12 @@ class ConversationItemDecorations(hasWallpaper: Boolean = false, private val sch
|
||||
unreadViewHolder?.updateForWallpaper()
|
||||
}
|
||||
|
||||
var isReleaseNotes: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
headerCache.values.forEach { it.updateForWallpaper() }
|
||||
}
|
||||
|
||||
var selfRecipientId: RecipientId? = null
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
@@ -307,7 +313,10 @@ class ConversationItemDecorations(hasWallpaper: Boolean = false, private val sch
|
||||
}
|
||||
|
||||
fun updateForWallpaper() {
|
||||
if (hasWallpaper) {
|
||||
if (isReleaseNotes) {
|
||||
date.setBackgroundResource(R.drawable.release_notes_date_header_background)
|
||||
date.setTextColor(ContextCompat.getColor(itemView.context, CoreUiR.color.signal_colorOnSurfaceVariant))
|
||||
} else if (hasWallpaper) {
|
||||
date.setBackgroundResource(R.drawable.wallpaper_bubble_background_18)
|
||||
date.setTextColor(ContextCompat.getColor(itemView.context, CoreUiR.color.signal_colorNeutralInverse))
|
||||
} else {
|
||||
|
||||
+11
-2
@@ -16,6 +16,7 @@ class ConversationToolbarOnScrollHelper(
|
||||
activity: FragmentActivity,
|
||||
toolbarBackground: View,
|
||||
private val wallpaperProvider: () -> ChatWallpaper?,
|
||||
private val releaseNotesProvider: () -> Boolean,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
private val incognito: Boolean = false
|
||||
) : Material3OnScrollHelper(
|
||||
@@ -25,10 +26,18 @@ class ConversationToolbarOnScrollHelper(
|
||||
setStatusBarColor = {}
|
||||
) {
|
||||
override val activeColorSet: ColorSet
|
||||
get() = if (incognito) ColorSet(R.color.conversation_toolbar_color_incognito) else ColorSet(getActiveToolbarColor(wallpaperProvider() != null))
|
||||
get() = when {
|
||||
incognito -> ColorSet(R.color.conversation_toolbar_color_incognito)
|
||||
releaseNotesProvider() -> ColorSet(R.color.release_notes_toolbar_scrolled)
|
||||
else -> ColorSet(getActiveToolbarColor(wallpaperProvider() != null))
|
||||
}
|
||||
|
||||
override val inactiveColorSet: ColorSet
|
||||
get() = if (incognito) ColorSet(R.color.conversation_toolbar_color_incognito) else ColorSet(getInactiveToolbarColor(wallpaperProvider() != null))
|
||||
get() = when {
|
||||
incognito -> ColorSet(R.color.conversation_toolbar_color_incognito)
|
||||
releaseNotesProvider() -> ColorSet(R.color.release_notes_toolbar_transparent)
|
||||
else -> ColorSet(getInactiveToolbarColor(wallpaperProvider() != null))
|
||||
}
|
||||
|
||||
@ColorRes
|
||||
private fun getActiveToolbarColor(hasWallpaper: Boolean): Int {
|
||||
|
||||
@@ -48,7 +48,6 @@ class DisabledInputView @JvmOverloads constructor(
|
||||
private var requestingGroup: View? = null
|
||||
private var announcementGroupOnly: TextView? = null
|
||||
private var inviteToSignal: View? = null
|
||||
private var releaseNoteChannel: View? = null
|
||||
private var incognitoView: View? = null
|
||||
|
||||
private var currentView: View? = null
|
||||
@@ -187,21 +186,6 @@ class DisabledInputView @JvmOverloads constructor(
|
||||
)
|
||||
}
|
||||
|
||||
fun showAsReleaseNotesChannel(recipient: Recipient) {
|
||||
releaseNoteChannel = show(
|
||||
existingView = releaseNoteChannel,
|
||||
create = { inflater.inflate(R.layout.conversation_activity_unmute, this, false) },
|
||||
bind = {
|
||||
if (recipient.isMuted) {
|
||||
visible = true
|
||||
findViewById<View>(R.id.conversation_activity_unmute_button).setOnClickListener { listener?.onUnmuteReleaseNotesChannel() }
|
||||
} else {
|
||||
visible = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setWallpaperEnabled(wallpaperEnabled: Boolean) {
|
||||
color = ContextCompat.getColor(context, if (wallpaperEnabled) R.color.wallpaper_bubble_color else CoreUiR.color.signal_colorBackground)
|
||||
setBackgroundColor(color)
|
||||
@@ -272,7 +256,6 @@ class DisabledInputView @JvmOverloads constructor(
|
||||
fun onBlockClicked()
|
||||
fun onUnblockClicked()
|
||||
fun onInviteToSignal(recipient: Recipient)
|
||||
fun onUnmuteReleaseNotesChannel()
|
||||
fun onReportSpamClicked()
|
||||
}
|
||||
}
|
||||
|
||||
+9
-2
@@ -444,14 +444,21 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
|
||||
V2ConversationItemUtils.linkifyUrlLinks(messageBody, conversationContext.selectedItems.isEmpty(), conversationContext.clickListener::onUrlClicked)
|
||||
|
||||
if (conversationMessage.hasStyleLinks()) {
|
||||
val isReleaseNotes = conversationMessage.threadRecipient.isReleaseNotes
|
||||
val linkColor = if (isReleaseNotes) {
|
||||
themeDelegate.getBodyTextColor(conversationMessage)
|
||||
} else {
|
||||
ContextCompat.getColor(getContext(), R.color.signal_accent_primary)
|
||||
}
|
||||
val underline = isReleaseNotes
|
||||
messageBody.getSpans(0, messageBody.length, PlaceholderURLSpan::class.java).forEach { placeholder ->
|
||||
val start = messageBody.getSpanStart(placeholder)
|
||||
val end = messageBody.getSpanEnd(placeholder)
|
||||
val span: URLSpan = InterceptableLongClickCopyLinkSpan(
|
||||
placeholder.value,
|
||||
conversationContext.clickListener::onUrlClicked,
|
||||
ContextCompat.getColor(getContext(), R.color.signal_accent_primary),
|
||||
false
|
||||
linkColor,
|
||||
underline
|
||||
)
|
||||
|
||||
messageBody.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
||||
+9
@@ -44,6 +44,10 @@ class V2ConversationItemTheme(
|
||||
return conversationContext.getColorizer().getIncomingFooterTextColor(context, conversationContext.hasWallpaper())
|
||||
}
|
||||
|
||||
if (!conversationMessage.messageRecord.isOutgoing && conversationMessage.threadRecipient.isReleaseNotes) {
|
||||
return ContextCompat.getColor(context, R.color.release_notes_bubble_text)
|
||||
}
|
||||
|
||||
return getColor(
|
||||
conversationMessage,
|
||||
conversationContext.getColorizer()::getOutgoingFooterTextColor,
|
||||
@@ -55,6 +59,9 @@ class V2ConversationItemTheme(
|
||||
fun getBodyTextColor(
|
||||
conversationMessage: ConversationMessage
|
||||
): Int {
|
||||
if (!conversationMessage.messageRecord.isOutgoing && conversationMessage.threadRecipient.isReleaseNotes) {
|
||||
return ContextCompat.getColor(context, R.color.release_notes_bubble_text)
|
||||
}
|
||||
return getColor(
|
||||
conversationMessage,
|
||||
conversationContext.getColorizer()::getOutgoingBodyTextColor,
|
||||
@@ -79,6 +86,8 @@ class V2ConversationItemTheme(
|
||||
): Int {
|
||||
return if (conversationMessage.messageRecord.isOutgoing) {
|
||||
Color.TRANSPARENT
|
||||
} else if (conversationMessage.threadRecipient.isReleaseNotes) {
|
||||
ContextCompat.getColor(context, R.color.release_notes_bubble)
|
||||
} else {
|
||||
if (conversationContext.hasWallpaper()) {
|
||||
ContextCompat.getColor(context, R.color.conversation_item_recv_bubble_color_wallpaper)
|
||||
|
||||
@@ -370,7 +370,7 @@ class Recipient(
|
||||
|
||||
/** A cheap way to check if wallpaper is set without doing any unnecessary proto parsing. */
|
||||
val hasWallpaper: Boolean
|
||||
get() = wallpaperValue != null || SignalStore.wallpaper.hasWallpaperSet()
|
||||
get() = wallpaperValue != null || SignalStore.wallpaper.hasWallpaperSet() || isReleaseNotes
|
||||
|
||||
/** The color of the chat bubbles to use in a chat with this recipient. */
|
||||
val chatColors: ChatColors
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ final class RecipientDialogViewModel extends ViewModel {
|
||||
|
||||
private void updateRecipientDetailsState(@NonNull Recipient recipient) {
|
||||
GroupId groupId = recipientDialogRepository.getGroupId();
|
||||
String aboutText = recipient.isReleaseNotes() ? context.getString(R.string.ReleaseNotes__signal_release_notes_and_news) : recipient.getCombinedAboutAndEmoji();
|
||||
String aboutText = recipient.isReleaseNotes() ? null : recipient.getCombinedAboutAndEmoji();
|
||||
|
||||
if (groupId != null && groupId.isV2() && recipient.isIndividual() && !recipient.isSelf()) {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
|
||||
Reference in New Issue
Block a user