Story Status for landing page and my stories.

fork-5.53.8
Alex Hart 2022-03-10 12:13:36 -04:00 zatwierdzone przez Cody Henthorne
rodzic 1ac8701ada
commit 1f82ceecc6
7 zmienionych plików z 172 dodań i 20 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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());
}
}

Wyświetl plik

@ -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,

Wyświetl plik

@ -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)

Wyświetl plik

@ -40,18 +40,33 @@
app:layout_constraintVertical_chainStyle="packed"
tools:text="My Stories" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/error_indicator"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="16dp"
android:importantForAccessibility="no"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/date"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/date"
app:srcCompat="@drawable/ic_error_outline_24"
app:tint="@color/signal_alert_primary"
tools:visibility="visible" />
<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="7dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/signal_text_secondary"
app:layout_constraintBottom_toTopOf="@id/icon"
app:layout_constraintEnd_toStartOf="@id/story"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintStart_toEndOf="@id/error_indicator"
app:layout_constraintTop_toBottomOf="@id/sender"
app:layout_goneMarginStart="16dp"
tools:text="10m" />
<ImageView

Wyświetl plik

@ -4,9 +4,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/dsl_settings_gutter"
android:background="?selectableItemBackground">
android:paddingEnd="@dimen/dsl_settings_gutter">
<org.thoughtcrime.securesms.components.OutlinedThumbnailView
android:id="@+id/story"
@ -35,16 +35,31 @@
app:layout_constraintVertical_chainStyle="packed"
tools:text="12 views" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/error_indicator"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="16dp"
android:importantForAccessibility="no"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/date"
app:layout_constraintStart_toEndOf="@id/story"
app:layout_constraintTop_toTopOf="@id/date"
app:srcCompat="@drawable/ic_error_outline_24"
app:tint="@color/signal_alert_primary"
tools:visibility="visible" />
<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="7dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/signal_text_secondary"
app:layout_constraintStart_toEndOf="@id/story"
app:layout_constraintStart_toEndOf="@id/error_indicator"
app:layout_constraintTop_toBottomOf="@id/view_count"
app:layout_goneMarginStart="16dp"
tools:text="10m" />
<View

Wyświetl plik

@ -4381,6 +4381,10 @@
<string name="StoriesLandingItem__share">Share…</string>
<!-- Context menu option to go to story chat -->
<string name="StoriesLandingItem__go_to_chat">Go to chat</string>
<!-- Label when a story is pending sending -->
<string name="StoriesLandingItem__sending">Sending…</string>
<!-- Label when a story fails to send -->
<string name="StoriesLandingItem__couldnt_send">Couldn\'t send</string>
<!-- Title of dialog confirming decision to hide a story -->
<string name="StoriesLandingFragment__hide_story">Hide story?</string>
<!-- Message of dialog confirming decision to hide a story -->