diff --git a/res/layout/media_overview_detail_text.xml b/res/layout/media_overview_detail_text.xml index 13de189849..9eab49b7d7 100644 --- a/res/layout/media_overview_detail_text.xml +++ b/res/layout/media_overview_detail_text.xml @@ -13,34 +13,28 @@ + tools:text="Sent voice note, 02:35" /> + tools:text="2.7 MB, 11.06.19 at 5:25 AM" /> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 8f8d9d0e8c..8b07688641 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -353,11 +353,7 @@ Today - Unknown file Unnamed file - Audio file - Image file - Video file Optimize for missing Play Services @@ -486,6 +482,9 @@ Media + Files + Audio + All Delete selected item? Delete selected items? @@ -496,11 +495,8 @@ Deleting Deleting messages… - Files Select all Collecting attachments… - Audio - All Sort by Newest Oldest @@ -510,6 +506,18 @@ List view Selected + File + Audio + Video + Image + %1$s · %2$s + %1$s · %2$s · %3$s + + Sent by %1$s + Sent by you + Sent by %1$s to %2$s + Sent by you to %1$s + Signal call in progress Establishing Signal call diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java index cfebb28569..52bd08efae 100644 --- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -52,7 +52,6 @@ import androidx.viewpager.widget.ViewPager; import org.thoughtcrime.securesms.animation.DepthPageTransformer; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener; -import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; @@ -722,13 +721,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv MediaRecord mediaRecord = MediaRecord.from(context, cursor); RecipientId recipientId = mediaRecord.getRecipientId(); - RecipientId threadRecipientId = DatabaseFactory.getThreadDatabase(context) - .getRecipientIdForThreadId(mediaRecord.getThreadId()); + RecipientId threadRecipientId = mediaRecord.getThreadRecipientId(); if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError(); return new MediaItem(Recipient.live(recipientId).get(), - threadRecipientId != null ? Recipient.live(threadRecipientId).get() : null, + Recipient.live(threadRecipientId).get(), mediaRecord.getAttachment(), mediaRecord.getAttachment().getDataUri(), mediaRecord.getContentType(), diff --git a/src/org/thoughtcrime/securesms/database/MediaDatabase.java b/src/org/thoughtcrime/securesms/database/MediaDatabase.java index 99db5ea4c1..1226404ab8 100644 --- a/src/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -5,6 +5,7 @@ import android.database.ContentObserver; import android.database.Cursor; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import net.sqlcipher.database.SQLiteDatabase; @@ -17,7 +18,8 @@ import java.util.List; public class MediaDatabase extends Database { - public static final int ALL_THREADS = -1; + public static final int ALL_THREADS = -1; + private static final String THREAD_RECIPIENT_ID = "THREAD_RECIPIENT_ID"; private static final String BASE_MEDIA_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ROW_ID + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " @@ -48,9 +50,12 @@ public class MediaDatabase extends Database { + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + ", " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.THREAD_ID + ", " - + MmsDatabase.TABLE_NAME + "." + MmsDatabase.RECIPIENT_ID + " " + + MmsDatabase.TABLE_NAME + "." + MmsDatabase.RECIPIENT_ID + ", " + + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " as " + THREAD_RECIPIENT_ID + " " + "FROM " + AttachmentDatabase.TABLE_NAME + " LEFT JOIN " + MmsDatabase.TABLE_NAME + " ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " + + "LEFT JOIN " + ThreadDatabase.TABLE_NAME + + " ON " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.THREAD_ID + " " + "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID + " FROM " + MmsDatabase.TABLE_NAME + " WHERE " + MmsDatabase.THREAD_ID + " __EQUALITY__ ?) AND (%s) AND " @@ -167,16 +172,24 @@ public class MediaDatabase extends Database { private final DatabaseAttachment attachment; private final RecipientId recipientId; + private final RecipientId threadRecipientId; private final long threadId; private final long date; private final boolean outgoing; - private MediaRecord(DatabaseAttachment attachment, @NonNull RecipientId recipientId, long threadId, long date, boolean outgoing) { - this.attachment = attachment; - this.recipientId = recipientId; - this.threadId = threadId; - this.date = date; - this.outgoing = outgoing; + private MediaRecord(@Nullable DatabaseAttachment attachment, + @NonNull RecipientId recipientId, + @NonNull RecipientId threadRecipientId, + long threadId, + long date, + boolean outgoing) + { + this.attachment = attachment; + this.recipientId = recipientId; + this.threadRecipientId = threadRecipientId; + this.threadId = threadId; + this.date = date; + this.outgoing = outgoing; } public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) { @@ -194,10 +207,17 @@ public class MediaDatabase extends Database { date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE_RECEIVED)); } - return new MediaRecord(attachments != null && attachments.size() > 0 ? attachments.get(0) : null, recipientId, threadId, date, outgoing); + RecipientId threadRecipient = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_RECIPIENT_ID))); + + return new MediaRecord(attachments != null && attachments.size() > 0 ? attachments.get(0) : null, + recipientId, + threadRecipient, + threadId, + date, + outgoing); } - public DatabaseAttachment getAttachment() { + public @Nullable DatabaseAttachment getAttachment() { return attachment; } @@ -209,6 +229,10 @@ public class MediaDatabase extends Database { return recipientId; } + public @NonNull RecipientId getThreadRecipientId() { + return threadRecipientId; + } + public long getThreadId() { return threadId; } diff --git a/src/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java b/src/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java index 15fc56dcc1..0fe4792169 100644 --- a/src/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java +++ b/src/org/thoughtcrime/securesms/mediaoverview/MediaGalleryAllAdapter.java @@ -17,12 +17,14 @@ package org.thoughtcrime.securesms.mediaoverview; import android.content.Context; +import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.lifecycle.Observer; import androidx.recyclerview.widget.RecyclerView; import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter; @@ -37,9 +39,14 @@ import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader.Grou import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; +import org.thoughtcrime.securesms.recipients.LiveRecipient; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.livedata.LiveDataPair; +import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.libsignal.util.guava.Optional; import java.util.Collection; import java.util.HashMap; @@ -50,6 +57,7 @@ import java.util.Map; final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { private final Context context; + private final boolean showThread; private final GlideRequests glideRequests; private final ItemClickListener itemClickListener; private final Map selected = new HashMap<>(); @@ -63,12 +71,18 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { private static final int GALLERY_DETAIL = 3; private static final int DOCUMENT_DETAIL = 4; - public void pause(RecyclerView.ViewHolder holder) { + void pause(RecyclerView.ViewHolder holder) { if (holder instanceof AudioDetailViewHolder) { ((AudioDetailViewHolder) holder).pause(); } } + void detach(RecyclerView.ViewHolder holder) { + if (holder instanceof SelectableViewHolder) { + ((SelectableViewHolder) holder).onDetached(); + } + } + private static class HeaderHolder extends HeaderViewHolder { TextView textView; @@ -82,13 +96,15 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { @NonNull GlideRequests glideRequests, GroupedThreadMedia media, ItemClickListener clickListener, - boolean showFileSizes) + boolean showFileSizes, + boolean showThread) { this.context = context; this.glideRequests = glideRequests; this.media = media; this.itemClickListener = clickListener; this.showFileSizes = showFileSizes; + this.showThread = showThread; } public void setMedia(GroupedThreadMedia media) { @@ -143,7 +159,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { public void onViewDetachedFromWindow(@NonNull ViewHolder holder) { super.onViewDetachedFromWindow(holder); if (holder instanceof SelectableViewHolder) { - ((SelectableViewHolder) holder).detached(); + ((SelectableViewHolder) holder).onDetached(); } } @@ -182,8 +198,10 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } void selectAllMedia() { - for (int section = 0; section < media.getSectionCount(); section++) { - for (int item = 0; item < media.getSectionItemCount(section); item++) { + int sectionCount = media.getSectionCount(); + for (int section = 0; section < sectionCount; section++) { + int sectionItemCount = media.getSectionItemCount(section); + for (int item = 0; item < sectionItemCount; item++) { MediaRecord mediaRecord = media.get(section, item); selected.put(mediaRecord.getAttachment().getAttachmentId(), mediaRecord); } @@ -203,6 +221,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { private final View selectedIndicator; private MediaDatabase.MediaRecord mediaRecord; + private boolean bound; SelectableViewHolder(@NonNull View itemView) { super(itemView); @@ -210,8 +229,16 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } public void bind(@NonNull Context context, @NonNull MediaDatabase.MediaRecord mediaRecord, @NonNull Slide slide) { + if (bound) { + unbind(); + } this.mediaRecord = mediaRecord; updateSelectedView(); + bound = true; + } + + void unbind() { + bound = false; } private void updateSelectedView() { @@ -226,7 +253,10 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { return true; } - void detached() { + void onDetached() { + if (bound) { + unbind(); + } } } @@ -259,16 +289,22 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } @Override - void detached() { + void unbind() { thumbnailView.clear(glideRequests); + super.unbind(); } } - private class DetailViewHolder extends SelectableViewHolder { + private abstract class DetailViewHolder extends SelectableViewHolder implements Observer> { - protected final View itemView; - private final TextView line1; - private final TextView line2; + protected final View itemView; + private final TextView line1; + private final TextView line2; + private LiveDataPair liveDataPair; + private Optional fileName; + private String fileTypeDescription; + private Handler handler; + private Runnable selectForMarque; DetailViewHolder(@NonNull View itemView) { super(itemView); @@ -281,27 +317,82 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { public void bind(@NonNull Context context, @NonNull MediaDatabase.MediaRecord mediaRecord, @NonNull Slide slide) { super.bind(context, mediaRecord, slide); - line1.setText(getLine1(context, slide)); - line2.setText(getLine2(mediaRecord, slide)); - line1.setVisibility(View.VISIBLE); - line2.setVisibility(View.VISIBLE); + fileName = slide.getFileName(); + fileTypeDescription = getFileTypeDescription(context, slide); + + line1.setText(fileName.or(fileTypeDescription)); + line2.setText(getLine2(context, mediaRecord, slide)); itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); itemView.setOnLongClickListener(view -> onLongClick()); + selectForMarque = () -> line1.setSelected(true); + handler = new Handler(); + handler.postDelayed(selectForMarque, 2500); + + LiveRecipient from = mediaRecord.isOutgoing() ? Recipient.self().live() : Recipient.live(mediaRecord.getRecipientId()); + LiveRecipient to = Recipient.live(mediaRecord.getThreadRecipientId()); + + liveDataPair = new LiveDataPair<>(from.getLiveData(), to.getLiveData(), Recipient.UNKNOWN, Recipient.UNKNOWN); + liveDataPair.observeForever(this); } - private String getLine1(@NonNull Context context, @NonNull Slide slide) { - return slide.getFileName() - .or(slide.getCaption()) - .or(() -> describeUnnamedFile(context, slide)); + @Override + void unbind() { + liveDataPair.removeObserver(this); + handler.removeCallbacks(selectForMarque); + line1.setSelected(false); + super.unbind(); } - private String getLine2(@NonNull MediaDatabase.MediaRecord mediaRecord, @NonNull Slide slide) { - String date = DateUtils.formatDate(Locale.getDefault(), mediaRecord.getDate()); - return Util.getPrettyFileSize(slide.getFileSize()) + " " + date; + private String getLine2(@NonNull Context context, @NonNull MediaDatabase.MediaRecord mediaRecord, @NonNull Slide slide) { + return context.getString(R.string.MediaOverviewActivity_detail_line_3_part, + Util.getPrettyFileSize(slide.getFileSize()), + getFileTypeDescription(context, slide), + DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(), mediaRecord.getDate())); } - protected String describeUnnamedFile(@NonNull Context context, @NonNull Slide slide) { - return context.getString(R.string.DocumentView_unnamed_file); + protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide){ + return context.getString(R.string.MediaOverviewActivity_file); + } + + @Override + public void onChanged(Pair fromToPair) { + line1.setText(describe(fromToPair.first(), fromToPair.second())); + } + + private String describe(@NonNull Recipient from, @NonNull Recipient thread) { + if (from == Recipient.UNKNOWN && thread == Recipient.UNKNOWN) { + return fileName.or(fileTypeDescription); + } + + String sentFromToString = getSentFromToString(from, thread); + + if (fileName.isPresent()) { + return context.getString(R.string.MediaOverviewActivity_detail_line_2_part, + fileName.get(), + sentFromToString); + } else { + return sentFromToString; + } + } + + private String getSentFromToString(@NonNull Recipient from, @NonNull Recipient thread) { + if (from.isLocalNumber() && from == thread) { + return context.getString(R.string.note_to_self); + } + + if (showThread && (from.isLocalNumber() || thread.isGroup())) { + if (from.isLocalNumber()) { + return context.getString(R.string.MediaOverviewActivity_sent_by_you_to_s, thread.toShortString(context)); + } else { + return context.getString(R.string.MediaOverviewActivity_sent_by_s_to_s, from.toShortString(context), thread.toShortString(context)); + } + } else { + if (from.isLocalNumber()) { + return context.getString(R.string.MediaOverviewActivity_sent_by_you); + } else { + return context.getString(R.string.MediaOverviewActivity_sent_by_s, from.toShortString(context)); + } + } } } @@ -345,13 +436,14 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } @Override - void detached() { + void unbind() { audioView.stopPlaybackAndReset(); + super.unbind(); } @Override - protected String describeUnnamedFile(@NonNull Context context, @NonNull Slide slide) { - return context.getString(R.string.DocumentView_audio_file); + protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { + return context.getString(R.string.MediaOverviewActivity_audio); } public void pause() { @@ -377,15 +469,16 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter { } @Override - protected String describeUnnamedFile(@NonNull Context context, @NonNull Slide slide) { - if (slide.hasVideo()) return context.getString(R.string.DocumentView_video_file); - if (slide.hasImage()) return context.getString(R.string.DocumentView_image_file); - return super.describeUnnamedFile(context, slide); + protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { + if (slide.hasVideo()) return context.getString(R.string.MediaOverviewActivity_video); + if (slide.hasImage()) return context.getString(R.string.MediaOverviewActivity_image); + return super.getFileTypeDescription(context, slide); } @Override - void detached() { + void unbind() { thumbnailView.clear(glideRequests); + super.unbind(); } } diff --git a/src/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java b/src/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java index 6585067502..042f61e28d 100644 --- a/src/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/src/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -103,7 +103,8 @@ public final class MediaOverviewPageFragment extends Fragment GlideApp.with(this), new GroupedThreadMediaLoader.EmptyGroupedThreadMedia(), this, - sorting.isRelatedToFileSize()); + sorting.isRelatedToFileSize(), + threadId == MediaDatabase.ALL_THREADS); this.recyclerView.setAdapter(adapter); this.recyclerView.setLayoutManager(gridManager); this.recyclerView.setHasFixedSize(true); @@ -182,7 +183,16 @@ public final class MediaOverviewPageFragment extends Fragment int childCount = recyclerView.getChildCount(); for (int i = 0; i < childCount; i++) { adapter.pause(recyclerView.getChildViewHolder(recyclerView.getChildAt(i))); - } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + int childCount = recyclerView.getChildCount(); + for (int i = 0; i < childCount; i++) { + adapter.detach(recyclerView.getChildViewHolder(recyclerView.getChildAt(i))); + } } private void handleMediaMultiSelectClick(@NonNull MediaDatabase.MediaRecord mediaRecord) { diff --git a/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java index abfa5eec39..d4d9b319b7 100644 --- a/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/src/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -6,6 +6,7 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Observer; @@ -168,6 +169,10 @@ public final class LiveRecipient { set(recipient); } + public @NonNull LiveData getLiveData() { + return liveData; + } + private @NonNull Recipient fetchRecipientFromDisk(RecipientId id) { RecipientSettings settings = recipientDatabase.getRecipientSettings(id); RecipientDetails details = settings.getGroupId() != null ? getGroupRecipientDetails(settings) diff --git a/src/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/src/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index db5513b44a..cd31a5ff35 100644 --- a/src/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/src/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -4,7 +4,6 @@ import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; -import androidx.lifecycle.MediatorLiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.Transformations; @@ -14,6 +13,7 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; +import org.thoughtcrime.securesms.util.livedata.LiveDataPair; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import org.whispersystems.signalservice.internal.util.JsonUtil; @@ -201,27 +201,4 @@ public final class RegistrationViewModel extends ViewModel { } }, this::setKeyBackupCurrentToken); } - - public static class LiveDataPair extends MediatorLiveData> { - private A a; - private B b; - - public LiveDataPair(LiveData ld1, LiveData ld2) { - setValue(new Pair<>(a, b)); - - addSource(ld1, (a) -> { - if(a != null) { - this.a = a; - } - setValue(new Pair<>(a, b)); - }); - - addSource(ld2, (b) -> { - if(b != null) { - this.b = b; - } - setValue(new Pair<>(a, b)); - }); - } -} } diff --git a/src/org/thoughtcrime/securesms/util/DateUtils.java b/src/org/thoughtcrime/securesms/util/DateUtils.java index 060c868042..8cf45c8289 100644 --- a/src/org/thoughtcrime/securesms/util/DateUtils.java +++ b/src/org/thoughtcrime/securesms/util/DateUtils.java @@ -24,6 +24,7 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.R; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -136,6 +137,10 @@ public class DateUtils extends android.text.format.DateUtils { return getFormattedDateTime(timestamp, "EEE, MMM d, yyyy", locale); } + public static String formatDateWithoutDayOfWeek(@NonNull Locale locale, long timestamp) { + return getFormattedDateTime(timestamp, "MMM d yyyy", locale); + } + public static boolean isSameDay(long t1, long t2) { return DATE_FORMAT.format(new Date(t1)).equals(DATE_FORMAT.format(new Date(t2))); } diff --git a/src/org/thoughtcrime/securesms/util/livedata/LiveDataPair.java b/src/org/thoughtcrime/securesms/util/livedata/LiveDataPair.java new file mode 100644 index 0000000000..2d28291da7 --- /dev/null +++ b/src/org/thoughtcrime/securesms/util/livedata/LiveDataPair.java @@ -0,0 +1,57 @@ +package org.thoughtcrime.securesms.util.livedata; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MediatorLiveData; + +import org.whispersystems.libsignal.util.Pair; + +public final class LiveDataPair extends MediatorLiveData> { + private A a; + private B b; + + public LiveDataPair(@NonNull LiveData liveDataA, + @NonNull LiveData liveDataB) + { + this(liveDataA, liveDataB, null, null); + } + + public LiveDataPair(@NonNull LiveData liveDataA, + @NonNull LiveData liveDataB, + @Nullable A initialA, + @Nullable B initialB) + { + a = initialA; + b = initialB; + setValue(new Pair<>(a, b)); + + if (liveDataA == liveDataB) { + + addSource(liveDataA, (a) -> { + if (a != null) { + this.a = a; + //noinspection unchecked: A is B if live datas are same instance + this.b = (B) a; + } + setValue(new Pair<>(a, b)); + }); + + } else { + + addSource(liveDataA, (a) -> { + if (a != null) { + this.a = a; + } + setValue(new Pair<>(a, b)); + }); + + addSource(liveDataB, (b) -> { + if (b != null) { + this.b = b; + } + setValue(new Pair<>(a, b)); + }); + } + } +}