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.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> 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);
|
||||
|
@ -71,33 +71,33 @@ final class MessageDetails {
|
|||
}
|
||||
}
|
||||
|
||||
@NonNull ConversationMessage getConversationMessage() {
|
||||
public @NonNull ConversationMessage getConversationMessage() {
|
||||
return conversationMessage;
|
||||
}
|
||||
|
||||
@NonNull Collection<RecipientDeliveryStatus> getPending() {
|
||||
public @NonNull Collection<RecipientDeliveryStatus> getPending() {
|
||||
return pending;
|
||||
}
|
||||
|
||||
@NonNull Collection<RecipientDeliveryStatus> getSent() {
|
||||
public @NonNull Collection<RecipientDeliveryStatus> getSent() {
|
||||
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;
|
||||
}
|
||||
|
||||
@NonNull Collection<RecipientDeliveryStatus> getRead() {
|
||||
public @NonNull Collection<RecipientDeliveryStatus> getRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
@NonNull Collection<RecipientDeliveryStatus> getNotSent() {
|
||||
public @NonNull Collection<RecipientDeliveryStatus> getNotSent() {
|
||||
return notSent;
|
||||
}
|
||||
|
||||
@NonNull Collection<RecipientDeliveryStatus> getViewed() {
|
||||
public @NonNull Collection<RecipientDeliveryStatus> getViewed() {
|
||||
return viewed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ import androidx.lifecycle.MutableLiveData;
|
|||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
|
@ -24,7 +26,10 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import java.util.LinkedList;
|
||||
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();
|
||||
|
||||
|
@ -44,11 +49,33 @@ final class MessageDetailsRepository {
|
|||
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
|
||||
private @NonNull MessageDetails getRecipientDeliveryStatusesInternal(@NonNull MessageRecord messageRecord) {
|
||||
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
|
||||
|
||||
if (!messageRecord.getRecipient().isGroup()) {
|
||||
if (!messageRecord.getRecipient().isGroup() && !messageRecord.getRecipient().isDistributionList()) {
|
||||
recipients.add(new RecipientDeliveryStatus(messageRecord,
|
||||
messageRecord.getRecipient(),
|
||||
getStatusFor(messageRecord),
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
|||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
final class RecipientDeliveryStatus {
|
||||
public final class RecipientDeliveryStatus {
|
||||
|
||||
enum Status {
|
||||
UNKNOWN, PENDING, SENT, DELIVERED, READ, VIEWED, SKIPPED,
|
||||
|
@ -32,31 +32,31 @@ final class RecipientDeliveryStatus {
|
|||
this.keyMismatchFailure = keyMismatchFailure;
|
||||
}
|
||||
|
||||
@NonNull MessageRecord getMessageRecord() {
|
||||
public @NonNull MessageRecord getMessageRecord() {
|
||||
return messageRecord;
|
||||
}
|
||||
|
||||
@NonNull Status getDeliveryStatus() {
|
||||
public @NonNull Status getDeliveryStatus() {
|
||||
return deliveryStatus;
|
||||
}
|
||||
|
||||
boolean isUnidentified() {
|
||||
public boolean isUnidentified() {
|
||||
return isUnidentified;
|
||||
}
|
||||
|
||||
long getTimestamp() {
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@NonNull Recipient getRecipient() {
|
||||
public @NonNull Recipient getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
@Nullable NetworkFailure getNetworkFailure() {
|
||||
public @Nullable NetworkFailure getNetworkFailure() {
|
||||
return networkFailure;
|
||||
}
|
||||
|
||||
@Nullable IdentityKeyMismatch getKeyMismatchFailure() {
|
||||
public @Nullable IdentityKeyMismatch getKeyMismatchFailure() {
|
||||
return keyMismatchFailure;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package org.thoughtcrime.securesms.stories.viewer.info
|
||||
|
||||
import android.content.DialogInterface
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
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.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.fragments.findListener
|
||||
|
@ -58,23 +60,61 @@ class StoryInfoBottomSheetDialogFragment : DSLSettingsBottomSheetFragment() {
|
|||
)
|
||||
)
|
||||
|
||||
state.sections.map { (section, recipients) ->
|
||||
renderSection(section, recipients)
|
||||
val details = state.messageDetails!!
|
||||
|
||||
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>) {
|
||||
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
|
||||
}
|
||||
)
|
||||
private fun DSLConfiguration.renderSection(@StringRes title: Int, recipients: List<StoryInfoRecipientRow.Model>) {
|
||||
if (recipients.isNotEmpty()) {
|
||||
sectionHeaderPref(
|
||||
title = DSLSettingsText.from(title)
|
||||
)
|
||||
|
||||
recipients.forEach {
|
||||
customPref(it)
|
||||
recipients.forEach {
|
||||
customPref(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.view.View
|
|||
import android.widget.TextView
|
||||
import org.thoughtcrime.securesms.R
|
||||
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.adapter.mapping.LayoutFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
@ -21,17 +21,15 @@ object StoryInfoRecipientRow {
|
|||
}
|
||||
|
||||
class Model(
|
||||
val recipient: Recipient,
|
||||
val date: Long,
|
||||
val status: Int,
|
||||
val isFailed: Boolean
|
||||
val recipientDeliveryStatus: RecipientDeliveryStatus
|
||||
) : MappingModel<Model> {
|
||||
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 {
|
||||
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)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
avatarView.setRecipient(model.recipient)
|
||||
nameView.text = model.recipient.getDisplayName(context)
|
||||
timestampView.text = DateUtils.getTimeString(context, Locale.getDefault(), model.date)
|
||||
avatarView.setRecipient(model.recipientDeliveryStatus.recipient)
|
||||
nameView.text = model.recipientDeliveryStatus.recipient.getDisplayName(context)
|
||||
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
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
import org.thoughtcrime.securesms.messagedetails.MessageDetails
|
||||
|
||||
/**
|
||||
* Contains the needed information to render the story info sheet.
|
||||
*/
|
||||
data class StoryInfoState(
|
||||
val sentMillis: Long = -1L,
|
||||
val receivedMillis: Long = -1L,
|
||||
val size: Long = -1L,
|
||||
val isOutgoing: Boolean = false,
|
||||
val sections: Map<SectionKey, List<StoryInfoRecipientRow.Model>> = emptyMap(),
|
||||
val isLoaded: Boolean = false
|
||||
val messageDetails: MessageDetails? = null
|
||||
) {
|
||||
enum class SectionKey {
|
||||
FAILED,
|
||||
SENT_TO,
|
||||
SENT_FROM
|
||||
}
|
||||
private val mediaMessage = messageDetails?.conversationMessage?.messageRecord as? MediaMmsMessageRecord
|
||||
|
||||
val sentMillis: Long = mediaMessage?.dateSent ?: -1L
|
||||
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.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.database.model.MessageId
|
||||
import org.thoughtcrime.securesms.messagedetails.MessageDetailsRepository
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
|
||||
/**
|
||||
* 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 disposables = CompositeDisposable()
|
||||
|
@ -25,54 +22,13 @@ class StoryInfoViewModel(storyId: Long, repository: StoryInfoRepository = StoryI
|
|||
val state: Flowable<StoryInfoState> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||
|
||||
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(
|
||||
isLoaded = true,
|
||||
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)
|
||||
messageDetails = messageDetails
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
disposables.clear()
|
||||
store.dispose()
|
||||
|
|
Loading…
Add table
Reference in a new issue