kopia lustrzana https://github.com/ryukoposting/Signal-Android
Story Status for landing page and my stories.
rodzic
1ac8701ada
commit
1f82ceecc6
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -->
|
||||
|
|
Ładowanie…
Reference in New Issue