Story info page should mirror message details.
This commit is contained in:
parent
742d1bece0
commit
2041756513
8 changed files with 122 additions and 174 deletions
|
@ -12,7 +12,7 @@ import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
final class MessageDetails {
|
public final class MessageDetails {
|
||||||
private static final Comparator<RecipientDeliveryStatus> HAS_DISPLAY_NAME = (r1, r2) -> Boolean.compare(r2.getRecipient().hasAUserSetDisplayName(ApplicationDependencies.getApplication()), r1.getRecipient().hasAUserSetDisplayName(ApplicationDependencies.getApplication()));
|
private static final Comparator<RecipientDeliveryStatus> HAS_DISPLAY_NAME = (r1, r2) -> Boolean.compare(r2.getRecipient().hasAUserSetDisplayName(ApplicationDependencies.getApplication()), r1.getRecipient().hasAUserSetDisplayName(ApplicationDependencies.getApplication()));
|
||||||
private static final Comparator<RecipientDeliveryStatus> ALPHABETICAL = (r1, r2) -> r1.getRecipient().getDisplayName(ApplicationDependencies.getApplication()).compareToIgnoreCase(r2.getRecipient().getDisplayName(ApplicationDependencies.getApplication()));
|
private static final Comparator<RecipientDeliveryStatus> ALPHABETICAL = (r1, r2) -> r1.getRecipient().getDisplayName(ApplicationDependencies.getApplication()).compareToIgnoreCase(r2.getRecipient().getDisplayName(ApplicationDependencies.getApplication()));
|
||||||
private static final Comparator<RecipientDeliveryStatus> RECIPIENT_COMPARATOR = ComparatorCompat.chain(HAS_DISPLAY_NAME).thenComparing(ALPHABETICAL);
|
private static final Comparator<RecipientDeliveryStatus> RECIPIENT_COMPARATOR = ComparatorCompat.chain(HAS_DISPLAY_NAME).thenComparing(ALPHABETICAL);
|
||||||
|
@ -71,33 +71,33 @@ final class MessageDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull ConversationMessage getConversationMessage() {
|
public @NonNull ConversationMessage getConversationMessage() {
|
||||||
return conversationMessage;
|
return conversationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getPending() {
|
public @NonNull Collection<RecipientDeliveryStatus> getPending() {
|
||||||
return pending;
|
return pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getSent() {
|
public @NonNull Collection<RecipientDeliveryStatus> getSent() {
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getSkipped() {return skipped;}
|
public @NonNull Collection<RecipientDeliveryStatus> getSkipped() {return skipped;}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getDelivered() {
|
public @NonNull Collection<RecipientDeliveryStatus> getDelivered() {
|
||||||
return delivered;
|
return delivered;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getRead() {
|
public @NonNull Collection<RecipientDeliveryStatus> getRead() {
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getNotSent() {
|
public @NonNull Collection<RecipientDeliveryStatus> getNotSent() {
|
||||||
return notSent;
|
return notSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Collection<RecipientDeliveryStatus> getViewed() {
|
public @NonNull Collection<RecipientDeliveryStatus> getViewed() {
|
||||||
return viewed;
|
return viewed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,11 @@ import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
|
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||||
|
@ -24,7 +26,10 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
final class MessageDetailsRepository {
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public final class MessageDetailsRepository {
|
||||||
|
|
||||||
private final Context context = ApplicationDependencies.getApplication();
|
private final Context context = ApplicationDependencies.getApplication();
|
||||||
|
|
||||||
|
@ -44,11 +49,33 @@ final class MessageDetailsRepository {
|
||||||
return liveData;
|
return liveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @NonNull Observable<MessageDetails> getMessageDetails(@NonNull MessageId messageId) {
|
||||||
|
return Observable.<MessageDetails>create(emitter -> {
|
||||||
|
DatabaseObserver.MessageObserver messageObserver = mId -> {
|
||||||
|
try {
|
||||||
|
MessageRecord messageRecord = messageId.isMms() ? SignalDatabase.mms().getMessageRecord(messageId.getId())
|
||||||
|
: SignalDatabase.sms().getMessageRecord(messageId.getId());
|
||||||
|
|
||||||
|
MessageDetails messageDetails = getRecipientDeliveryStatusesInternal(messageRecord);
|
||||||
|
|
||||||
|
emitter.onNext(messageDetails);
|
||||||
|
} catch (NoSuchMessageException e) {
|
||||||
|
emitter.onError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(messageObserver);
|
||||||
|
emitter.setCancellable(() -> ApplicationDependencies.getDatabaseObserver().unregisterObserver(messageObserver));
|
||||||
|
|
||||||
|
messageObserver.onMessageChanged(messageId);
|
||||||
|
}).observeOn(Schedulers.io());
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private @NonNull MessageDetails getRecipientDeliveryStatusesInternal(@NonNull MessageRecord messageRecord) {
|
private @NonNull MessageDetails getRecipientDeliveryStatusesInternal(@NonNull MessageRecord messageRecord) {
|
||||||
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
|
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
|
||||||
|
|
||||||
if (!messageRecord.getRecipient().isGroup()) {
|
if (!messageRecord.getRecipient().isGroup() && !messageRecord.getRecipient().isDistributionList()) {
|
||||||
recipients.add(new RecipientDeliveryStatus(messageRecord,
|
recipients.add(new RecipientDeliveryStatus(messageRecord,
|
||||||
messageRecord.getRecipient(),
|
messageRecord.getRecipient(),
|
||||||
getStatusFor(messageRecord),
|
getStatusFor(messageRecord),
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
|
||||||
final class RecipientDeliveryStatus {
|
public final class RecipientDeliveryStatus {
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
UNKNOWN, PENDING, SENT, DELIVERED, READ, VIEWED, SKIPPED,
|
UNKNOWN, PENDING, SENT, DELIVERED, READ, VIEWED, SKIPPED,
|
||||||
|
@ -32,31 +32,31 @@ final class RecipientDeliveryStatus {
|
||||||
this.keyMismatchFailure = keyMismatchFailure;
|
this.keyMismatchFailure = keyMismatchFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull MessageRecord getMessageRecord() {
|
public @NonNull MessageRecord getMessageRecord() {
|
||||||
return messageRecord;
|
return messageRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Status getDeliveryStatus() {
|
public @NonNull Status getDeliveryStatus() {
|
||||||
return deliveryStatus;
|
return deliveryStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isUnidentified() {
|
public boolean isUnidentified() {
|
||||||
return isUnidentified;
|
return isUnidentified;
|
||||||
}
|
}
|
||||||
|
|
||||||
long getTimestamp() {
|
public long getTimestamp() {
|
||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Recipient getRecipient() {
|
public @NonNull Recipient getRecipient() {
|
||||||
return recipient;
|
return recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable NetworkFailure getNetworkFailure() {
|
public @Nullable NetworkFailure getNetworkFailure() {
|
||||||
return networkFailure;
|
return networkFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable IdentityKeyMismatch getKeyMismatchFailure() {
|
public @Nullable IdentityKeyMismatch getKeyMismatchFailure() {
|
||||||
return keyMismatchFailure;
|
return keyMismatchFailure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package org.thoughtcrime.securesms.stories.viewer.info
|
package org.thoughtcrime.securesms.stories.viewer.info
|
||||||
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
||||||
|
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||||
import org.thoughtcrime.securesms.components.settings.configure
|
import org.thoughtcrime.securesms.components.settings.configure
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.fragments.findListener
|
import org.thoughtcrime.securesms.util.fragments.findListener
|
||||||
|
@ -58,23 +60,61 @@ class StoryInfoBottomSheetDialogFragment : DSLSettingsBottomSheetFragment() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
state.sections.map { (section, recipients) ->
|
val details = state.messageDetails!!
|
||||||
renderSection(section, recipients)
|
|
||||||
|
if (state.isOutgoing) {
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__not_sent,
|
||||||
|
recipients = details.notSent.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__viewed,
|
||||||
|
recipients = details.viewed.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__read_by,
|
||||||
|
recipients = details.read.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__delivered_to,
|
||||||
|
recipients = details.delivered.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__sent_to,
|
||||||
|
recipients = details.sent.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__pending_send,
|
||||||
|
recipients = details.pending.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__skipped,
|
||||||
|
recipients = details.skipped.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
renderSection(
|
||||||
|
title = R.string.message_details_recipient_header__sent_from,
|
||||||
|
recipients = details.sent.map { StoryInfoRecipientRow.Model(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DSLConfiguration.renderSection(sectionKey: StoryInfoState.SectionKey, recipients: List<StoryInfoRecipientRow.Model>) {
|
private fun DSLConfiguration.renderSection(@StringRes title: Int, recipients: List<StoryInfoRecipientRow.Model>) {
|
||||||
sectionHeaderPref(
|
if (recipients.isNotEmpty()) {
|
||||||
title = when (sectionKey) {
|
sectionHeaderPref(
|
||||||
StoryInfoState.SectionKey.FAILED -> R.string.StoryInfoBottomSheetDialogFragment__failed
|
title = DSLSettingsText.from(title)
|
||||||
StoryInfoState.SectionKey.SENT_TO -> R.string.StoryInfoBottomSheetDialogFragment__sent_to
|
)
|
||||||
StoryInfoState.SectionKey.SENT_FROM -> R.string.StoryInfoBottomSheetDialogFragment__sent_from
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
recipients.forEach {
|
recipients.forEach {
|
||||||
customPref(it)
|
customPref(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.messagedetails.RecipientDeliveryStatus
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
|
||||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||||
|
@ -21,17 +21,15 @@ object StoryInfoRecipientRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Model(
|
class Model(
|
||||||
val recipient: Recipient,
|
val recipientDeliveryStatus: RecipientDeliveryStatus
|
||||||
val date: Long,
|
|
||||||
val status: Int,
|
|
||||||
val isFailed: Boolean
|
|
||||||
) : MappingModel<Model> {
|
) : MappingModel<Model> {
|
||||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||||
return recipient.id == newItem.recipient.id
|
return recipientDeliveryStatus.recipient.id == newItem.recipientDeliveryStatus.recipient.id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||||
return recipient.hasSameContent(newItem.recipient) && date == newItem.date
|
return recipientDeliveryStatus.recipient.hasSameContent(newItem.recipientDeliveryStatus.recipient) &&
|
||||||
|
recipientDeliveryStatus.timestamp == newItem.recipientDeliveryStatus.timestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +40,9 @@ object StoryInfoRecipientRow {
|
||||||
private val timestampView: TextView = itemView.findViewById(R.id.story_info_timestamp)
|
private val timestampView: TextView = itemView.findViewById(R.id.story_info_timestamp)
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
override fun bind(model: Model) {
|
||||||
avatarView.setRecipient(model.recipient)
|
avatarView.setRecipient(model.recipientDeliveryStatus.recipient)
|
||||||
nameView.text = model.recipient.getDisplayName(context)
|
nameView.text = model.recipientDeliveryStatus.recipient.getDisplayName(context)
|
||||||
timestampView.text = DateUtils.getTimeString(context, Locale.getDefault(), model.date)
|
timestampView.text = DateUtils.getTimeString(context, Locale.getDefault(), model.recipientDeliveryStatus.timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.stories.viewer.info
|
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Observable
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
||||||
import org.signal.core.util.logging.Log
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
|
||||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.NoSuchMessageException
|
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gathers necessary message record and receipt data for a given story id.
|
|
||||||
*/
|
|
||||||
class StoryInfoRepository {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = Log.tag(StoryInfoRepository::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the StoryInfo for a given ID and emits a new item whenever the underlying
|
|
||||||
* message record changes.
|
|
||||||
*/
|
|
||||||
fun getStoryInfo(storyId: Long): Observable<StoryInfo> {
|
|
||||||
return observeMessageRecord(storyId)
|
|
||||||
.switchMap { record ->
|
|
||||||
getReceiptInfo(storyId).map { receiptInfo ->
|
|
||||||
StoryInfo(record, receiptInfo)
|
|
||||||
}.toObservable()
|
|
||||||
}
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeMessageRecord(storyId: Long): Observable<MessageRecord> {
|
|
||||||
return Observable.create { emitter ->
|
|
||||||
fun refresh() {
|
|
||||||
try {
|
|
||||||
emitter.onNext(SignalDatabase.mms.getMessageRecord(storyId))
|
|
||||||
} catch (e: NoSuchMessageException) {
|
|
||||||
Log.w(TAG, "The story message disappeared. Terminating emission.")
|
|
||||||
emitter.onComplete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val observer = DatabaseObserver.MessageObserver {
|
|
||||||
if (it.mms && it.id == storyId) {
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationDependencies.getDatabaseObserver().registerMessageUpdateObserver(observer)
|
|
||||||
emitter.setCancellable {
|
|
||||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer)
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getReceiptInfo(storyId: Long): Single<List<GroupReceiptDatabase.GroupReceiptInfo>> {
|
|
||||||
return Single.fromCallable {
|
|
||||||
SignalDatabase.groupReceipts.getGroupReceiptInfo(storyId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message record and receipt info for a given story id.
|
|
||||||
*/
|
|
||||||
data class StoryInfo(val messageRecord: MessageRecord, val receiptInfo: List<GroupReceiptDatabase.GroupReceiptInfo>)
|
|
||||||
}
|
|
|
@ -1,19 +1,19 @@
|
||||||
package org.thoughtcrime.securesms.stories.viewer.info
|
package org.thoughtcrime.securesms.stories.viewer.info
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||||
|
import org.thoughtcrime.securesms.messagedetails.MessageDetails
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the needed information to render the story info sheet.
|
* Contains the needed information to render the story info sheet.
|
||||||
*/
|
*/
|
||||||
data class StoryInfoState(
|
data class StoryInfoState(
|
||||||
val sentMillis: Long = -1L,
|
val messageDetails: MessageDetails? = null
|
||||||
val receivedMillis: Long = -1L,
|
|
||||||
val size: Long = -1L,
|
|
||||||
val isOutgoing: Boolean = false,
|
|
||||||
val sections: Map<SectionKey, List<StoryInfoRecipientRow.Model>> = emptyMap(),
|
|
||||||
val isLoaded: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
enum class SectionKey {
|
private val mediaMessage = messageDetails?.conversationMessage?.messageRecord as? MediaMmsMessageRecord
|
||||||
FAILED,
|
|
||||||
SENT_TO,
|
val sentMillis: Long = mediaMessage?.dateSent ?: -1L
|
||||||
SENT_FROM
|
val receivedMillis: Long = mediaMessage?.dateReceived ?: -1L
|
||||||
}
|
val size: Long = mediaMessage?.slideDeck?.thumbnailSlide?.fileSize ?: 0
|
||||||
|
val isOutgoing: Boolean = mediaMessage?.isOutgoing ?: false
|
||||||
|
val isLoaded: Boolean = mediaMessage != null
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,14 @@ import io.reactivex.rxjava3.core.BackpressureStrategy
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageId
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.messagedetails.MessageDetailsRepository
|
||||||
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
|
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gathers and stores the StoryInfoState which is used to render the story info sheet.
|
* Gathers and stores the StoryInfoState which is used to render the story info sheet.
|
||||||
*/
|
*/
|
||||||
class StoryInfoViewModel(storyId: Long, repository: StoryInfoRepository = StoryInfoRepository()) : ViewModel() {
|
class StoryInfoViewModel(storyId: Long, repository: MessageDetailsRepository = MessageDetailsRepository()) : ViewModel() {
|
||||||
|
|
||||||
private val store = RxStore(StoryInfoState())
|
private val store = RxStore(StoryInfoState())
|
||||||
private val disposables = CompositeDisposable()
|
private val disposables = CompositeDisposable()
|
||||||
|
@ -25,54 +22,13 @@ class StoryInfoViewModel(storyId: Long, repository: StoryInfoRepository = StoryI
|
||||||
val state: Flowable<StoryInfoState> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
val state: Flowable<StoryInfoState> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
disposables += store.update(repository.getStoryInfo(storyId).toFlowable(BackpressureStrategy.LATEST)) { storyInfo, storyInfoState ->
|
disposables += store.update(repository.getMessageDetails(MessageId(storyId, true)).toFlowable(BackpressureStrategy.LATEST)) { messageDetails, storyInfoState ->
|
||||||
storyInfoState.copy(
|
storyInfoState.copy(
|
||||||
isLoaded = true,
|
messageDetails = messageDetails
|
||||||
sentMillis = storyInfo.messageRecord.dateSent,
|
|
||||||
receivedMillis = storyInfo.messageRecord.dateReceived,
|
|
||||||
size = (storyInfo.messageRecord as? MmsMessageRecord)?.let { it.slideDeck.firstSlide?.fileSize } ?: -1L,
|
|
||||||
isOutgoing = storyInfo.messageRecord.isOutgoing,
|
|
||||||
sections = buildSections(storyInfo)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSections(storyInfo: StoryInfoRepository.StoryInfo): Map<StoryInfoState.SectionKey, List<StoryInfoRecipientRow.Model>> {
|
|
||||||
return if (storyInfo.messageRecord.isOutgoing) {
|
|
||||||
storyInfo.receiptInfo.map { groupReceiptInfo ->
|
|
||||||
StoryInfoRecipientRow.Model(
|
|
||||||
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 {
|
|
||||||
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() {
|
override fun onCleared() {
|
||||||
disposables.clear()
|
disposables.clear()
|
||||||
store.dispose()
|
store.dispose()
|
||||||
|
|
Loading…
Add table
Reference in a new issue