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));
+ });
+ }
+ }
+}