Story Status for landing page and my stories.

This commit is contained in:
Alex Hart
2022-03-10 12:13:36 -04:00
committed by Cody Henthorne
parent 1ac8701ada
commit 1f82ceecc6
7 changed files with 172 additions and 20 deletions

View File

@@ -236,7 +236,7 @@ class MediaSelectionRepository(context: Context) {
if (isStory && preUploadResults.size > 1) {
preUploadResults.forEach {
val list = storyMessages[it] ?: mutableListOf()
list.add(OutgoingSecureMediaMessage(message))
list.add(OutgoingSecureMediaMessage(message).withSentTimestamp(System.currentTimeMillis()))
storyMessages[it] = list
// XXX We must do this to avoid sending out messages to the same recipient with the same

View File

@@ -58,4 +58,20 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
getLinkPreviews(),
getMentions());
}
public @NonNull OutgoingSecureMediaMessage withSentTimestamp(long sentTimestamp) {
return new OutgoingSecureMediaMessage(getRecipient(),
getBody(),
getAttachments(),
sentTimestamp,
getDistributionType(),
getExpiresIn(),
isViewOnce(),
getStoryType(),
getParentStoryId(),
getOutgoingQuote(),
getSharedContacts(),
getLinkPreviews(),
getMentions());
}
}

View File

@@ -1,10 +1,9 @@
package org.thoughtcrime.securesms.stories.landing
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.view.AvatarView
import org.thoughtcrime.securesms.components.ThumbnailView
@@ -17,6 +16,7 @@ import org.thoughtcrime.securesms.stories.StoryTextPostModel
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
@@ -27,6 +27,9 @@ import java.util.Locale
* Items displaying a preview and metadata for a story from a user, allowing them to launch into the story viewer.
*/
object StoriesLandingItem {
private const val STATUS_CHANGE = 0
fun register(mappingAdapter: MappingAdapter) {
mappingAdapter.registerFactory(Model::class.java, LayoutFactory(::ViewHolder, R.layout.stories_landing_item))
}
@@ -48,8 +51,30 @@ object StoriesLandingItem {
override fun areContentsTheSame(newItem: Model): Boolean {
return data.storyRecipient.hasSameContent(newItem.data.storyRecipient) &&
data == newItem.data &&
!hasStatusChange(newItem) &&
super.areContentsTheSame(newItem)
}
override fun getChangePayload(newItem: Model): Any? {
return if (isSameRecord(newItem) && hasStatusChange(newItem)) {
STATUS_CHANGE
} else {
null
}
}
private fun isSameRecord(newItem: Model): Boolean {
return data.primaryStory.messageRecord.id == newItem.data.primaryStory.messageRecord.id
}
private fun hasStatusChange(newItem: Model): Boolean {
val oldRecord = data.primaryStory.messageRecord
val newRecord = newItem.data.primaryStory.messageRecord
return oldRecord.isOutgoing &&
newRecord.isOutgoing &&
(oldRecord.isPending != newRecord.isPending || oldRecord.isSent != newRecord.isSent || oldRecord.isFailed != newRecord.isFailed)
}
}
private class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
@@ -64,19 +89,20 @@ object StoriesLandingItem {
private val sender: TextView = itemView.findViewById(R.id.sender)
private val date: TextView = itemView.findViewById(R.id.date)
private val icon: ImageView = itemView.findViewById(R.id.icon)
private val errorIndicator: View = itemView.findViewById(R.id.error_indicator)
override fun bind(model: Model) {
itemView.setOnClickListener { model.onRowClick(model) }
presentDateOrStatus(model)
setUpClickListeners(model)
if (payload.contains(STATUS_CHANGE)) {
return
}
if (model.data.storyRecipient.isMyStory) {
itemView.setOnLongClickListener(null)
avatarView.displayProfileAvatar(Recipient.self())
} else {
itemView.setOnLongClickListener {
displayContext(model)
true
}
avatarView.displayProfileAvatar(model.data.storyRecipient)
}
@@ -111,7 +137,6 @@ object StoriesLandingItem {
else -> model.data.storyRecipient.getDisplayName(context)
}
date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.data.dateInMilliseconds)
icon.visible = model.data.hasReplies || model.data.hasRepliesFromSelf
// TODO [stories] -- Set actual image resource
icon.setImageDrawable(ColorDrawable(Color.RED))
@@ -121,6 +146,32 @@ object StoriesLandingItem {
}
}
private fun presentDateOrStatus(model: Model) {
if (model.data.primaryStory.messageRecord.isOutgoing && (model.data.primaryStory.messageRecord.isPending || model.data.primaryStory.messageRecord.isMediaPending)) {
errorIndicator.visible = false
date.setText(R.string.StoriesLandingItem__sending)
} else if (model.data.primaryStory.messageRecord.isOutgoing && model.data.primaryStory.messageRecord.isFailed) {
errorIndicator.visible = true
date.text = SpanUtil.color(ContextCompat.getColor(context, R.color.signal_alert_primary), context.getString(R.string.StoriesLandingItem__couldnt_send))
} else {
errorIndicator.visible = false
date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.data.dateInMilliseconds)
}
}
private fun setUpClickListeners(model: Model) {
itemView.setOnClickListener { model.onRowClick(model) }
if (model.data.storyRecipient.isMyStory) {
itemView.setOnLongClickListener(null)
} else {
itemView.setOnLongClickListener {
displayContext(model)
true
}
}
}
private fun getGroupPresentation(model: Model): String {
return context.getString(
R.string.StoryViewerPageFragment__s_to_s,

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.my
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ThumbnailView
import org.thoughtcrime.securesms.components.menu.ActionItem
@@ -16,13 +17,17 @@ import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.stories.StoryTextPostModel
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.thoughtcrime.securesms.util.visible
import java.util.Locale
object MyStoriesItem {
private const val STATUS_CHANGE = 0
fun register(mappingAdapter: MappingAdapter) {
mappingAdapter.registerFactory(Model::class.java, LayoutFactory(::ViewHolder, R.layout.stories_my_stories_item))
}
@@ -40,7 +45,30 @@ object MyStoriesItem {
}
override fun areContentsTheSame(newItem: Model): Boolean {
return distributionStory == newItem.distributionStory && super.areContentsTheSame(newItem)
return distributionStory == newItem.distributionStory &&
!hasStatusChange(newItem) &&
super.areContentsTheSame(newItem)
}
override fun getChangePayload(newItem: Model): Any? {
return if (isSameRecord(newItem) && hasStatusChange(newItem)) {
STATUS_CHANGE
} else {
null
}
}
private fun isSameRecord(newItem: Model): Boolean {
return distributionStory.messageRecord.id == newItem.distributionStory.messageRecord.id
}
private fun hasStatusChange(newItem: Model): Boolean {
val oldRecord = distributionStory.messageRecord
val newRecord = newItem.distributionStory.messageRecord
return oldRecord.isOutgoing &&
newRecord.isOutgoing &&
(oldRecord.isPending != newRecord.isPending || oldRecord.isSent != newRecord.isSent || oldRecord.isFailed != newRecord.isFailed)
}
}
@@ -51,13 +79,23 @@ object MyStoriesItem {
private val storyPreview: ThumbnailView = itemView.findViewById(R.id.story)
private val viewCount: TextView = itemView.findViewById(R.id.view_count)
private val date: TextView = itemView.findViewById(R.id.date)
private val errorIndicator: View = itemView.findViewById(R.id.error_indicator)
override fun bind(model: Model) {
itemView.setOnClickListener { model.onClick(model) }
downloadTarget.setOnClickListener { model.onSaveClick(model) }
moreTarget.setOnClickListener { showContextMenu(model) }
viewCount.text = context.resources.getQuantityString(R.plurals.MyStories__d_views, model.distributionStory.messageRecord.readReceiptCount, model.distributionStory.messageRecord.readReceiptCount)
date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.distributionStory.messageRecord.dateSent)
presentDateOrStatus(model)
viewCount.text = context.resources.getQuantityString(
R.plurals.MyStories__d_views,
model.distributionStory.messageRecord.readReceiptCount,
model.distributionStory.messageRecord.readReceiptCount
)
if (STATUS_CHANGE in payload) {
return
}
val record: MmsMessageRecord = model.distributionStory.messageRecord as MmsMessageRecord
val thumbnail: Slide? = record.slideDeck.thumbnailSlide
@@ -72,6 +110,19 @@ object MyStoriesItem {
}
}
private fun presentDateOrStatus(model: Model) {
if (model.distributionStory.messageRecord.isPending || model.distributionStory.messageRecord.isMediaPending) {
errorIndicator.visible = false
date.setText(R.string.StoriesLandingItem__sending)
} else if (model.distributionStory.messageRecord.isFailed) {
errorIndicator.visible = true
date.text = SpanUtil.color(ContextCompat.getColor(context, R.color.signal_alert_primary), context.getString(R.string.StoriesLandingItem__couldnt_send))
} else {
errorIndicator.visible = false
date.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), model.distributionStory.messageRecord.dateSent)
}
}
private fun showContextMenu(model: Model) {
SignalContextMenu.Builder(itemView, itemView.rootView as ViewGroup)
.preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.END)