Display failure state in story info and other places.
This commit is contained in:
parent
25c0dc801f
commit
ea3fb774f8
13 changed files with 233 additions and 40 deletions
|
@ -10,13 +10,14 @@ import org.thoughtcrime.securesms.R
|
|||
|
||||
object StoryDialogs {
|
||||
|
||||
fun resendStory(context: Context, resend: () -> Unit) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.StoryDialogs__story_could_not_be_sent)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.StoryDialogs__send) { _, _ -> resend() }
|
||||
.show()
|
||||
}
|
||||
fun resendStory(context: Context, onDismiss: () -> Unit = {}, resend: () -> Unit) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.StoryDialogs__story_could_not_be_sent)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.StoryDialogs__send) { _, _ -> resend() }
|
||||
.setOnDismissListener { onDismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun displayStoryOrProfileImage(
|
||||
context: Context,
|
||||
|
|
|
@ -72,9 +72,20 @@ object MyStoriesItem {
|
|||
val oldRecord = distributionStory.messageRecord
|
||||
val newRecord = newItem.distributionStory.messageRecord
|
||||
|
||||
val oldRecordHasIdentityMismatch = distributionStory.messageRecord.identityKeyMismatches.isNotEmpty()
|
||||
val newRecordHasIdentityMismatch = newItem.distributionStory.messageRecord.identityKeyMismatches.isNotEmpty()
|
||||
val oldRecordHasNetworkFailures = distributionStory.messageRecord.hasNetworkFailures()
|
||||
val newRecordHasNetworkFailures = newItem.distributionStory.messageRecord.hasNetworkFailures()
|
||||
|
||||
return oldRecord.isOutgoing &&
|
||||
newRecord.isOutgoing &&
|
||||
(oldRecord.isPending != newRecord.isPending || oldRecord.isSent != newRecord.isSent || oldRecord.isFailed != newRecord.isFailed)
|
||||
(
|
||||
oldRecord.isPending != newRecord.isPending ||
|
||||
oldRecord.isSent != newRecord.isSent ||
|
||||
oldRecord.isFailed != newRecord.isFailed ||
|
||||
oldRecordHasIdentityMismatch != newRecordHasIdentityMismatch ||
|
||||
oldRecordHasNetworkFailures != newRecordHasNetworkFailures
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,6 +168,11 @@ object MyStoriesItem {
|
|||
date.visible = true
|
||||
viewCount.setText(R.string.StoriesLandingItem__send_failed)
|
||||
date.setText(R.string.StoriesLandingItem__tap_to_retry)
|
||||
} else if (model.distributionStory.messageRecord.isIdentityMismatchFailure) {
|
||||
errorIndicator.visible = true
|
||||
date.visible = true
|
||||
viewCount.setText(R.string.StoriesLandingItem__partially_sent)
|
||||
date.setText(R.string.StoriesLandingItem__tap_to_retry)
|
||||
} else {
|
||||
errorIndicator.visible = false
|
||||
date.visible = true
|
||||
|
|
|
@ -58,20 +58,26 @@ class StoryInfoBottomSheetDialogFragment : DSLSettingsBottomSheetFragment() {
|
|||
)
|
||||
)
|
||||
|
||||
sectionHeaderPref(
|
||||
title = if (state.isOutgoing) {
|
||||
R.string.StoryInfoBottomSheetDialogFragment__sent_to
|
||||
} else {
|
||||
R.string.StoryInfoBottomSheetDialogFragment__sent_from
|
||||
}
|
||||
)
|
||||
|
||||
state.recipients.forEach {
|
||||
customPref(it)
|
||||
state.sections.map { (section, recipients) ->
|
||||
renderSection(section, recipients)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DSLConfiguration.renderSection(sectionKey: StoryInfoState.SectionKey, recipients: List<StoryInfoRecipientRow.Model>) {
|
||||
sectionHeaderPref(
|
||||
title = when (sectionKey) {
|
||||
StoryInfoState.SectionKey.FAILED -> R.string.StoryInfoBottomSheetDialogFragment__failed
|
||||
StoryInfoState.SectionKey.SENT_TO -> R.string.StoryInfoBottomSheetDialogFragment__sent_to
|
||||
StoryInfoState.SectionKey.SENT_FROM -> R.string.StoryInfoBottomSheetDialogFragment__sent_from
|
||||
}
|
||||
)
|
||||
|
||||
recipients.forEach {
|
||||
customPref(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
findListener<OnInfoSheetDismissedListener>()?.onInfoSheetDismissed()
|
||||
|
|
|
@ -23,7 +23,8 @@ object StoryInfoRecipientRow {
|
|||
class Model(
|
||||
val recipient: Recipient,
|
||||
val date: Long,
|
||||
val status: Int
|
||||
val status: Int,
|
||||
val isFailed: Boolean
|
||||
) : MappingModel<Model> {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||
return recipient.id == newItem.recipient.id
|
||||
|
|
|
@ -8,6 +8,12 @@ data class StoryInfoState(
|
|||
val receivedMillis: Long = -1L,
|
||||
val size: Long = -1L,
|
||||
val isOutgoing: Boolean = false,
|
||||
val recipients: List<StoryInfoRecipientRow.Model> = emptyList(),
|
||||
val sections: Map<SectionKey, List<StoryInfoRecipientRow.Model>> = emptyMap(),
|
||||
val isLoaded: Boolean = false
|
||||
)
|
||||
) {
|
||||
enum class SectionKey {
|
||||
FAILED,
|
||||
SENT_TO,
|
||||
SENT_FROM
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,11 @@ import io.reactivex.rxjava3.core.BackpressureStrategy
|
|||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
/**
|
||||
|
@ -29,31 +32,47 @@ class StoryInfoViewModel(storyId: Long, repository: StoryInfoRepository = StoryI
|
|||
receivedMillis = storyInfo.messageRecord.dateReceived,
|
||||
size = (storyInfo.messageRecord as? MmsMessageRecord)?.let { it.slideDeck.firstSlide?.fileSize } ?: -1L,
|
||||
isOutgoing = storyInfo.messageRecord.isOutgoing,
|
||||
recipients = buildRecipients(storyInfo)
|
||||
sections = buildSections(storyInfo)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRecipients(storyInfo: StoryInfoRepository.StoryInfo): List<StoryInfoRecipientRow.Model> {
|
||||
private fun buildSections(storyInfo: StoryInfoRepository.StoryInfo): Map<StoryInfoState.SectionKey, List<StoryInfoRecipientRow.Model>> {
|
||||
return if (storyInfo.messageRecord.isOutgoing) {
|
||||
storyInfo.receiptInfo.map {
|
||||
storyInfo.receiptInfo.map { groupReceiptInfo ->
|
||||
StoryInfoRecipientRow.Model(
|
||||
recipient = Recipient.resolved(it.recipientId),
|
||||
date = it.timestamp,
|
||||
status = it.status
|
||||
recipient = Recipient.resolved(groupReceiptInfo.recipientId),
|
||||
date = groupReceiptInfo.timestamp,
|
||||
status = groupReceiptInfo.status,
|
||||
isFailed = hasFailure(storyInfo.messageRecord, groupReceiptInfo.recipientId)
|
||||
)
|
||||
}.groupBy {
|
||||
when {
|
||||
it.isFailed -> StoryInfoState.SectionKey.FAILED
|
||||
else -> StoryInfoState.SectionKey.SENT_TO
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listOf(
|
||||
StoryInfoRecipientRow.Model(
|
||||
recipient = storyInfo.messageRecord.individualRecipient,
|
||||
date = storyInfo.messageRecord.dateSent,
|
||||
status = -1
|
||||
mapOf(
|
||||
StoryInfoState.SectionKey.SENT_FROM to listOf(
|
||||
StoryInfoRecipientRow.Model(
|
||||
recipient = storyInfo.messageRecord.individualRecipient,
|
||||
date = storyInfo.messageRecord.dateSent,
|
||||
status = -1,
|
||||
isFailed = false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasFailure(messageRecord: MessageRecord, recipientId: RecipientId): Boolean {
|
||||
val hasNetworkFailure = messageRecord.networkFailures.any { it.getRecipientId(ApplicationDependencies.getApplication()) == recipientId }
|
||||
val hasIdentityFailure = messageRecord.identityKeyMismatches.any { it.getRecipientId(ApplicationDependencies.getApplication()) == recipientId }
|
||||
|
||||
return hasNetworkFailure || hasIdentityFailure
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.animation.AnimatorSet
|
|||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.RenderEffect
|
||||
import android.graphics.Shader
|
||||
import android.graphics.drawable.Drawable
|
||||
|
@ -14,7 +15,6 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.view.GestureDetector
|
||||
import android.view.GestureDetector.SimpleOnGestureListener
|
||||
import android.view.MotionEvent
|
||||
import android.view.ScaleGestureDetector
|
||||
import android.view.View
|
||||
|
@ -24,6 +24,7 @@ import android.widget.TextView
|
|||
import androidx.cardview.widget.CardView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import androidx.core.view.animation.PathInterpolatorCompat
|
||||
|
@ -32,9 +33,12 @@ import androidx.fragment.app.DialogFragment
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicatorSpec
|
||||
import com.google.android.material.progressindicator.IndeterminateDrawable
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener
|
||||
|
@ -44,6 +48,7 @@ import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgr
|
|||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
|
||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto20dp
|
||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardBottomSheet
|
||||
|
@ -57,6 +62,7 @@ import org.thoughtcrime.securesms.mediapreview.VideoControlsDelegate
|
|||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||
import org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView
|
||||
import org.thoughtcrime.securesms.stories.StorySlateView
|
||||
import org.thoughtcrime.securesms.stories.StoryVolumeOverlayView
|
||||
|
@ -94,7 +100,8 @@ class StoryViewerPageFragment :
|
|||
StorySlateView.Callback,
|
||||
StoryTextPostPreviewFragment.Callback,
|
||||
StoryFirstTimeNavigationView.Callback,
|
||||
StoryInfoBottomSheetDialogFragment.OnInfoSheetDismissedListener {
|
||||
StoryInfoBottomSheetDialogFragment.OnInfoSheetDismissedListener,
|
||||
SafetyNumberBottomSheet.Callbacks {
|
||||
|
||||
private val storyVolumeViewModel: StoryVolumeViewModel by viewModels(ownerProducer = { requireActivity() })
|
||||
|
||||
|
@ -140,6 +147,8 @@ class StoryViewerPageFragment :
|
|||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private val timeoutDisposable = LifecycleDisposable()
|
||||
|
||||
private var sendingProgressDrawable: IndeterminateDrawable<CircularProgressIndicatorSpec>? = null
|
||||
|
||||
private val storyRecipientId: RecipientId
|
||||
get() = requireArguments().getParcelable(ARG_STORY_RECIPIENT_ID)!!
|
||||
|
||||
|
@ -374,7 +383,7 @@ class StoryViewerPageFragment :
|
|||
if (state.posts.isNotEmpty() && state.selectedPostIndex in state.posts.indices) {
|
||||
val post = state.posts[state.selectedPostIndex]
|
||||
|
||||
presentViewsAndReplies(post, state.replyState, state.isReceiptsEnabled)
|
||||
presentBottomBar(post, state.replyState, state.isReceiptsEnabled)
|
||||
presentSenderAvatar(senderAvatar, post)
|
||||
presentGroupAvatar(groupAvatar, post)
|
||||
presentFrom(from, post)
|
||||
|
@ -649,6 +658,15 @@ class StoryViewerPageFragment :
|
|||
isFromNotification,
|
||||
groupReplyStartPosition
|
||||
)
|
||||
StoryViewerPageState.ReplyState.PARTIAL_SEND -> {
|
||||
handleResend(storyPost)
|
||||
return
|
||||
}
|
||||
StoryViewerPageState.ReplyState.SEND_FAILURE -> {
|
||||
handleResend(storyPost)
|
||||
return
|
||||
}
|
||||
StoryViewerPageState.ReplyState.SENDING -> return
|
||||
}
|
||||
|
||||
if (viewModel.getSwipeToReplyState() == StoryViewerPageState.ReplyState.PRIVATE) {
|
||||
|
@ -660,6 +678,19 @@ class StoryViewerPageFragment :
|
|||
replyFragment.showNow(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
|
||||
private fun handleResend(storyPost: StoryPost) {
|
||||
viewModel.setIsDisplayingPartialSendDialog(true)
|
||||
if (storyPost.conversationMessage.messageRecord.isIdentityMismatchFailure) {
|
||||
SafetyNumberBottomSheet
|
||||
.forMessageRecord(requireContext(), storyPost.conversationMessage.messageRecord)
|
||||
.show(childFragmentManager)
|
||||
} else {
|
||||
StoryDialogs.resendStory(requireContext(), { viewModel.setIsDisplayingPartialSendDialog(false) }) {
|
||||
lifecycleDisposable += viewModel.resend(storyPost).subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showInfo(storyPost: StoryPost) {
|
||||
viewModel.setIsDisplayingInfoDialog(true)
|
||||
StoryInfoBottomSheetDialogFragment.create(storyPost.id).show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
|
@ -889,7 +920,7 @@ class StoryViewerPageFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun presentViewsAndReplies(post: StoryPost, replyState: StoryViewerPageState.ReplyState, isReceiptsEnabled: Boolean) {
|
||||
private fun presentBottomBar(post: StoryPost, replyState: StoryViewerPageState.ReplyState, isReceiptsEnabled: Boolean) {
|
||||
if (replyState == StoryViewerPageState.ReplyState.NONE) {
|
||||
viewsAndReplies.visible = false
|
||||
return
|
||||
|
@ -897,6 +928,51 @@ class StoryViewerPageFragment :
|
|||
viewsAndReplies.visible = true
|
||||
}
|
||||
|
||||
viewsAndReplies.iconTint = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurface))
|
||||
|
||||
when (replyState) {
|
||||
StoryViewerPageState.ReplyState.SENDING -> presentSendingBottomBar()
|
||||
StoryViewerPageState.ReplyState.PARTIAL_SEND -> presentPartialSendBottomBar()
|
||||
StoryViewerPageState.ReplyState.SEND_FAILURE -> presentSendFailureBottomBar()
|
||||
else -> presentViewsAndRepliesBottomBar(post, isReceiptsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun presentSendingBottomBar() {
|
||||
if (sendingProgressDrawable == null) {
|
||||
sendingProgressDrawable = IndeterminateDrawable.createCircularDrawable(
|
||||
requireContext(),
|
||||
CircularProgressIndicatorSpec(requireContext(), null).apply {
|
||||
indicatorSize = 18.dp
|
||||
indicatorInset = 2.dp
|
||||
trackColor = ContextCompat.getColor(requireContext(), R.color.transparent_white_40)
|
||||
indicatorColors = intArrayOf(ContextCompat.getColor(requireContext(), R.color.signal_dark_colorNeutralInverse))
|
||||
trackThickness = 2.dp
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
viewsAndReplies.icon = sendingProgressDrawable
|
||||
viewsAndReplies.iconGravity = MaterialButton.ICON_GRAVITY_TEXT_START
|
||||
viewsAndReplies.iconSize = 20.dp
|
||||
viewsAndReplies.setText(R.string.StoriesLandingItem__sending)
|
||||
}
|
||||
|
||||
private fun presentPartialSendBottomBar() {
|
||||
viewsAndReplies.setIconResource(R.drawable.ic_error_outline_24)
|
||||
viewsAndReplies.iconTint = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.signal_light_colorError))
|
||||
viewsAndReplies.iconSize = 20.dp
|
||||
viewsAndReplies.setText(R.string.StoryViewerPageFragment__partially_sent)
|
||||
}
|
||||
|
||||
private fun presentSendFailureBottomBar() {
|
||||
viewsAndReplies.setIconResource(R.drawable.ic_error_outline_24)
|
||||
viewsAndReplies.iconTint = ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.signal_light_colorError))
|
||||
viewsAndReplies.iconSize = 20.dp
|
||||
viewsAndReplies.setText(R.string.StoryViewerPageFragment__send_failed)
|
||||
}
|
||||
|
||||
private fun presentViewsAndRepliesBottomBar(post: StoryPost, isReceiptsEnabled: Boolean) {
|
||||
val views = resources.getQuantityString(R.plurals.StoryViewerFragment__d_views, post.viewCount, post.viewCount)
|
||||
val replies = resources.getQuantityString(R.plurals.StoryViewerFragment__d_replies, post.replyCount, post.replyCount)
|
||||
|
||||
|
@ -1280,4 +1356,16 @@ class StoryViewerPageFragment :
|
|||
override fun onInfoSheetDismissed() {
|
||||
viewModel.setIsDisplayingInfoDialog(false)
|
||||
}
|
||||
|
||||
override fun sendAnywayAfterSafetyNumberChangedInBottomSheet(destinations: List<ContactSearchKey.RecipientSearchKey>) {
|
||||
error("Not supported, we handed a message record to the bottom sheet.")
|
||||
}
|
||||
|
||||
override fun onMessageResentAfterSafetyNumberChangeInBottomSheet() {
|
||||
viewModel.setIsDisplayingPartialSendDialog(false)
|
||||
}
|
||||
|
||||
override fun onCanceled() {
|
||||
viewModel.setIsDisplayingPartialSendDialog(false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.stories.viewer.page
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.annotation.CheckResult
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
|
@ -22,6 +23,7 @@ import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.Base64
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
@ -194,6 +196,13 @@ open class StoryViewerPageRepository(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun resend(messageRecord: MessageRecord): Completable {
|
||||
return Completable.fromAction {
|
||||
MessageSender.resend(ApplicationDependencies.getApplication(), messageRecord)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
private fun getContent(record: MmsMessageRecord): StoryPost.Content {
|
||||
return if (record.storyType.isTextStory || record.slideDeck.asAttachments().isEmpty()) {
|
||||
StoryPost.Content.TextContent(
|
||||
|
|
|
@ -36,7 +36,22 @@ data class StoryViewerPageState(
|
|||
/**
|
||||
* Story is from self and in a group
|
||||
*/
|
||||
GROUP_SELF;
|
||||
GROUP_SELF,
|
||||
|
||||
/**
|
||||
* Story was not sent to all recipients.
|
||||
*/
|
||||
PARTIAL_SEND,
|
||||
|
||||
/**
|
||||
* Story failed to send.
|
||||
*/
|
||||
SEND_FAILURE,
|
||||
|
||||
/**
|
||||
* Story is currently being sent.
|
||||
*/
|
||||
SENDING;
|
||||
|
||||
companion object {
|
||||
fun resolve(isFromSelf: Boolean, isToGroup: Boolean): ReplyState {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package org.thoughtcrime.securesms.stories.viewer.page
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
|
@ -266,16 +268,27 @@ class StoryViewerPageViewModel(
|
|||
storyViewerPlaybackStore.update { it.copy(isUserScaling = isUserScaling) }
|
||||
}
|
||||
|
||||
fun setIsDisplayingPartialSendDialog(isDisplayingPartialSendDialog: Boolean) {
|
||||
storyViewerPlaybackStore.update { it.copy(isDisplayingPartialSendDialog = isDisplayingPartialSendDialog) }
|
||||
}
|
||||
|
||||
private fun resolveSwipeToReplyState(state: StoryViewerPageState, index: Int): StoryViewerPageState.ReplyState {
|
||||
if (index !in state.posts.indices) {
|
||||
return StoryViewerPageState.ReplyState.NONE
|
||||
}
|
||||
|
||||
val post = state.posts[index]
|
||||
val message = post.conversationMessage.messageRecord
|
||||
val isFromSelf = post.sender.isSelf
|
||||
val isToGroup = post.group != null
|
||||
val isFailed = message.isFailed
|
||||
val isPartialSend = message.isIdentityMismatchFailure
|
||||
val isInProgress = !post.conversationMessage.messageRecord.isSent
|
||||
|
||||
return when {
|
||||
isFromSelf && isPartialSend -> StoryViewerPageState.ReplyState.PARTIAL_SEND
|
||||
isFromSelf && isFailed -> StoryViewerPageState.ReplyState.SEND_FAILURE
|
||||
isFromSelf && isInProgress -> StoryViewerPageState.ReplyState.SENDING
|
||||
post.allowsReplies -> StoryViewerPageState.ReplyState.resolve(isFromSelf, isToGroup)
|
||||
isFromSelf -> StoryViewerPageState.ReplyState.SELF
|
||||
else -> StoryViewerPageState.ReplyState.NONE
|
||||
|
@ -290,6 +303,13 @@ class StoryViewerPageViewModel(
|
|||
return store.state.posts.getOrNull(index)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun resend(storyPost: StoryPost): Completable {
|
||||
return repository
|
||||
.resend(storyPost.conversationMessage.messageRecord)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val recipientId: RecipientId,
|
||||
private val initialStoryId: Long,
|
||||
|
|
|
@ -21,7 +21,8 @@ data class StoryViewerPlaybackState(
|
|||
val isDisplayingInfoDialog: Boolean = false,
|
||||
val isUserLongTouching: Boolean = false,
|
||||
val isUserScrollingChild: Boolean = false,
|
||||
val isUserScaling: Boolean = false
|
||||
val isUserScaling: Boolean = false,
|
||||
val isDisplayingPartialSendDialog: Boolean = false
|
||||
) {
|
||||
val hideChromeImmediate: Boolean = isRunningSharedElementAnimation
|
||||
|
||||
|
@ -49,5 +50,6 @@ data class StoryViewerPlaybackState(
|
|||
isDisplayingFirstTimeNavigation ||
|
||||
isDisplayingInfoDialog ||
|
||||
isUserScaling ||
|
||||
isDisplayingHideDialog
|
||||
isDisplayingHideDialog ||
|
||||
isDisplayingPartialSendDialog
|
||||
}
|
||||
|
|
|
@ -4756,6 +4756,10 @@
|
|||
<string name="StoryViewerPageFragment__s_to_s">%1$s to %2$s</string>
|
||||
<!-- Displayed when viewing a post from another user with no replies -->
|
||||
<string name="StoryViewerPageFragment__reply">Reply</string>
|
||||
<!-- Displayed when viewing a post that has failed to send to some users -->
|
||||
<string name="StoryViewerPageFragment__partially_sent">Partially sent. Tap for details</string>
|
||||
<!-- Displayed when viewing a post that has failed to send -->
|
||||
<string name="StoryViewerPageFragment__send_failed">Send failed. Tap to retry</string>
|
||||
<!-- Label for the reply button in story viewer, which will launch the group story replies bottom sheet. -->
|
||||
<string name="StoryViewerPageFragment__reply_to_group">Reply to group</string>
|
||||
<!-- Displayed when a story has no views -->
|
||||
|
@ -5162,6 +5166,8 @@
|
|||
<string name="StoryInfoBottomSheetDialogFragment__sent_to">Sent to</string>
|
||||
<!-- Story info "Sent from" header -->
|
||||
<string name="StoryInfoBottomSheetDialogFragment__sent_from">Sent from</string>
|
||||
<!-- Story info "Failed" header -->
|
||||
<string name="StoryInfoBottomSheetDialogFragment__failed">Failed</string>
|
||||
<!-- Story Info context menu label -->
|
||||
<string name="StoryInfoBottomSheetDialogFragment__info">Info</string>
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.mockito.kotlin.mock
|
|||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.database.FakeMessageRecords
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
|
@ -186,7 +187,10 @@ class StoryViewerPageViewModelTest {
|
|||
conversationMessage = mock(),
|
||||
allowsReplies = true,
|
||||
hasSelfViewed = isViewed(it)
|
||||
)
|
||||
).apply {
|
||||
val messageRecord = FakeMessageRecords.buildMediaMmsMessageRecord()
|
||||
whenever(conversationMessage.messageRecord).thenReturn(messageRecord)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue