diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4f5e6d02ad..df33d2b0e3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -406,7 +406,14 @@
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
- android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" />
+ android:launchMode="singleTask"
+ android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
+ android:parentActivityName=".MainActivity">
+
+
+
CREATOR = new Creator() {
+ @Override
+ public BlurHash createFromParcel(Parcel in) {
+ return new BlurHash(in);
+ }
+
+ @Override
+ public BlurHash[] newArray(int size) {
+ return new BlurHash[size];
+ }
+ };
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt
index e582a1d536..45a625d5d8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.emoji.EmojiFiles
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob
import org.thoughtcrime.securesms.jobs.CreateReleaseChannelJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread
+import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
@@ -54,7 +54,7 @@ class InternalSettingsRepository(context: Context) {
SignalDatabase.attachments.getAttachmentsForMessage(insertResult.messageId)
.forEach { ApplicationDependencies.getJobManager().add(AttachmentDownloadJob(insertResult.messageId, it.attachmentId, false)) }
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.threadId))
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.threadId))
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt
index 5f42ec3123..9e9b56b143 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt
@@ -76,6 +76,7 @@ import org.thoughtcrime.securesms.recipients.RecipientExporter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment
import org.thoughtcrime.securesms.stories.Stories
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.CommunicationActions
@@ -275,7 +276,13 @@ class ConversationSettingsFragment : DSLSettingsFragment(
val viewAvatarTransitionBundle = AvatarPreviewActivity.createTransitionBundle(requireActivity(), avatar)
if (Stories.isFeatureEnabled() && avatar.hasStory()) {
- val viewStoryIntent = StoryViewerActivity.createIntent(requireContext(), state.recipient.id)
+ val viewStoryIntent = StoryViewerActivity.createIntent(
+ requireContext(),
+ StoryViewerArgs(
+ recipientId = state.recipient.id,
+ isInHiddenStoryMode = state.recipient.shouldHideStory()
+ )
+ )
StoryDialogs.displayStoryOrProfileImage(
context = requireContext(),
onViewStory = { startActivity(viewStoryIntent) },
diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt
index 2b27db7f21..8354ecd5d5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread
+import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
@@ -215,7 +215,7 @@ object ContactDiscovery {
.forEach { result ->
val hour = Calendar.getInstance()[Calendar.HOUR_OF_DAY]
if (hour in 9..22) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(result.threadId), true)
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(result.threadId), true)
} else {
Log.i(TAG, "Not notifying of a new user due to the time of day. (Hour: $hour)")
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
index e58463b5e4..b0216278ff 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java
@@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.providers.BlobProvider;
@@ -157,6 +158,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
+import org.thoughtcrime.securesms.stories.StoryViewerArgs;
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity;
import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions;
@@ -670,7 +672,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
this.recipient = Recipient.live(conversationViewModel.getArgs().getRecipientId());
this.threadId = conversationViewModel.getArgs().getThreadId();
- this.markReadHelper = new MarkReadHelper(threadId, requireContext(), getViewLifecycleOwner());
+ this.markReadHelper = new MarkReadHelper(ConversationId.forConversation(threadId), requireContext(), getViewLifecycleOwner());
conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, startingPosition);
messageCountsViewModel.setThreadId(threadId);
@@ -917,7 +919,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
snapToTopDataObserver.requestScrollPosition(0);
conversationViewModel.onConversationDataAvailable(recipient.getId(), threadId, -1);
messageCountsViewModel.setThreadId(threadId);
- markReadHelper = new MarkReadHelper(threadId, requireContext(), getViewLifecycleOwner());
+ markReadHelper = new MarkReadHelper(ConversationId.forConversation(threadId), requireContext(), getViewLifecycleOwner());
initializeListAdapter();
initializeTypingObserver();
}
@@ -1628,15 +1630,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
if (messageRecord.getParentStoryId() != null) {
startActivity(StoryViewerActivity.createIntent(
requireContext(),
- messageRecord.getQuote().getAuthor(),
- messageRecord.getParentStoryId().asMessageId().getId(),
- Recipient.resolved(messageRecord.getQuote().getAuthor()).shouldHideStory(),
- null,
- null,
- null,
- Collections.emptyList()
- ));
-
+ new StoryViewerArgs.Builder(messageRecord.getQuote().getAuthor(), Recipient.resolved(messageRecord.getQuote().getAuthor()).shouldHideStory())
+ .withStoryId(messageRecord.getParentStoryId().asMessageId().getId())
+ .build()));
return;
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java
index 7f59666e86..373c557bc8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java
@@ -240,7 +240,7 @@ import org.thoughtcrime.securesms.mms.SlideFactory.MediaType;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView;
@@ -267,6 +267,7 @@ import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.stickers.StickerManagementActivity;
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
+import org.thoughtcrime.securesms.stories.StoryViewerArgs;
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import org.thoughtcrime.securesms.util.Base64;
@@ -885,7 +886,7 @@ public class ConversationParentFragment extends Fragment
private void setVisibleThread(long threadId) {
if (!isInBubble()) {
// TODO [alex] LargeScreenSupport -- Inform MainActivityViewModel that the conversation was opened.
- ApplicationDependencies.getMessageNotifier().setVisibleThread(NotificationThread.forConversation(threadId));
+ ApplicationDependencies.getMessageNotifier().setVisibleThread(ConversationId.forConversation(threadId));
}
}
@@ -1249,7 +1250,10 @@ public class ConversationParentFragment extends Fragment
}
private void handleStoryRingClick() {
- startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.getId(), -1L, recipient.get().shouldHideStory(), null, null, null, Collections.emptyList()));
+ startActivity(StoryViewerActivity.createIntent(
+ requireContext(),
+ new StoryViewerArgs.Builder(recipient.getId(), recipient.get().shouldHideStory())
+ .build()));
}
private void handleConversationSettings() {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/MarkReadHelper.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/MarkReadHelper.java
index 2b8fb6b4d1..e316215843 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/MarkReadHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/MarkReadHelper.java
@@ -13,26 +13,27 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.util.Debouncer;
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor;
import java.util.List;
import java.util.concurrent.Executor;
-class MarkReadHelper {
+public class MarkReadHelper {
private static final String TAG = Log.tag(MarkReadHelper.class);
private static final long DEBOUNCE_TIMEOUT = 100;
private static final Executor EXECUTOR = new SerialMonoLifoExecutor(SignalExecutors.BOUNDED);
- private final long threadId;
+ private final ConversationId conversationId;
private final Context context;
- private final LifecycleOwner lifecycleOwner;
- private final Debouncer debouncer = new Debouncer(DEBOUNCE_TIMEOUT);
- private long latestTimestamp;
+ private final LifecycleOwner lifecycleOwner;
+ private final Debouncer debouncer = new Debouncer(DEBOUNCE_TIMEOUT);
+ private long latestTimestamp;
- MarkReadHelper(long threadId, @NonNull Context context, @NonNull LifecycleOwner lifecycleOwner) {
- this.threadId = threadId;
+ public MarkReadHelper(@NonNull ConversationId conversationId, @NonNull Context context, @NonNull LifecycleOwner lifecycleOwner) {
+ this.conversationId = conversationId;
this.context = context.getApplicationContext();
this.lifecycleOwner = lifecycleOwner;
}
@@ -47,7 +48,7 @@ class MarkReadHelper {
debouncer.publish(() -> {
EXECUTOR.execute(() -> {
ThreadDatabase threadDatabase = SignalDatabase.threads();
- List infos = threadDatabase.setReadSince(threadId, false, timestamp);
+ List infos = threadDatabase.setReadSince(conversationId, false, timestamp);
Log.d(TAG, "Marking " + infos.size() + " messages as read.");
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java
index c2476f3392..bf1ef65c76 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java
@@ -197,6 +197,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract @NonNull List getUnreadStoryThreadRecipientIds();
public abstract boolean containsStories(long threadId);
public abstract boolean hasSelfReplyInStory(long parentStoryId);
+ public abstract boolean hasSelfReplyInGroupStory(long parentStoryId);
public abstract @NonNull Cursor getStoryReplies(long parentStoryId);
public abstract @Nullable Long getOldestStorySendTimestamp();
public abstract int deleteStoriesOlderThan(long timestamp);
@@ -204,6 +205,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
public abstract @Nullable ParentStoryId.GroupReply getParentStoryIdForGroupReply(long messageId);
public abstract void deleteGroupStoryReplies(long parentStoryId);
public abstract boolean isOutgoingStoryAlreadyInDatabase(@NonNull RecipientId recipientId, long sentTimestamp);
+ public abstract @NonNull List setGroupStoryMessagesReadSince(long threadId, long groupStoryId, long sinceTimestamp);
public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId);
public abstract void updateViewedStories(@NonNull Set syncMessageIds);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
index ff41ed0795..b0705c5661 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -801,7 +801,7 @@ public class MmsDatabase extends MessageDatabase {
String where = PARENT_STORY_ID + " = ?";
String[] whereArgs = SqlUtil.buildArgs(parentStoryId);
- return rawQuery(where, whereArgs, true, 0);
+ return rawQuery(where, whereArgs, false, 0);
}
@Override
@@ -840,6 +840,18 @@ public class MmsDatabase extends MessageDatabase {
}
}
+ @Override
+ public boolean hasSelfReplyInGroupStory(long parentStoryId) {
+ SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
+ String[] columns = new String[]{"COUNT(*)"};
+ String where = PARENT_STORY_ID + " = ? AND (" + getOutgoingTypeClause() + ")";
+ String[] whereArgs = SqlUtil.buildArgs(parentStoryId);
+
+ try (Cursor cursor = db.query(TABLE_NAME, columns, where, whereArgs, null, null, null, null)) {
+ return cursor != null && cursor.moveToNext() && cursor.getInt(0) > 0;
+ }
+ }
+
@Override
public @Nullable Long getOldestStorySendTimestamp() {
SQLiteDatabase db = databaseHelper.getSignalReadableDatabase();
@@ -1381,6 +1393,15 @@ public class MmsDatabase extends MessageDatabase {
}
}
+ @Override
+ public @NonNull List setGroupStoryMessagesReadSince(long threadId, long groupStoryId, long sinceTimestamp) {
+ if (sinceTimestamp == -1) {
+ return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " = ? AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND (" + getOutgoingTypeClause() + ")))", SqlUtil.buildArgs(threadId, groupStoryId));
+ } else {
+ return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " = ? AND (" + READ + " = 0 OR (" + REACTIONS_UNREAD + " = 1 AND ( " + getOutgoingTypeClause() + " ))) AND " + DATE_RECEIVED + " <= ?", SqlUtil.buildArgs(threadId, groupStoryId, sinceTimestamp));
+ }
+ }
+
@Override
public List setEntireThreadRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0", new String[] { String.valueOf(threadId)});
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index 6100af9d0f..537e8dfd73 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -32,7 +32,6 @@ import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.database.MessageDatabase.MessageUpdate;
import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.database.model.StoryViewState;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -259,12 +258,12 @@ public class MmsSmsDatabase extends Database {
}
stickyQuery.append("(")
.append(MmsSmsColumns.THREAD_ID + " = ")
- .append(stickyThread.getNotificationThread().getThreadId())
+ .append(stickyThread.getConversationId().getThreadId())
.append(" AND ")
.append(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)
.append(" >= ")
.append(stickyThread.getEarliestTimestamp())
- .append(getStickyWherePartForParentStoryId(stickyThread.getNotificationThread().getGroupStoryId()))
+ .append(getStickyWherePartForParentStoryId(stickyThread.getConversationId().getGroupStoryId()))
.append(")");
}
@@ -654,6 +653,11 @@ public class MmsSmsDatabase extends Database {
return SignalDatabase.sms().hasReceivedAnyCallsSince(threadId, timestamp);
}
+
+ public int getMessagePositionInConversation(long threadId, long receivedTimestamp) {
+ return getMessagePositionInConversation(threadId, 0, receivedTimestamp);
+ }
+
/**
* Retrieves the position of the message with the provided timestamp in the query results you'd
* get from calling {@link #getConversation(long)}.
@@ -661,12 +665,24 @@ public class MmsSmsDatabase extends Database {
* Note: This could give back incorrect results in the situation where multiple messages have the
* same received timestamp. However, because this was designed to determine where to scroll to,
* you'll still wind up in about the right spot.
+ *
+ * @param groupStoryId Ignored if passed value is <= 0
*/
- public int getMessagePositionInConversation(long threadId, long receivedTimestamp) {
- String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
- String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " +
- MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " > " + receivedTimestamp + " AND " +
- MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
+ public int getMessagePositionInConversation(long threadId, long groupStoryId, long receivedTimestamp) {
+ final String order;
+ final String selection;
+
+ if (groupStoryId > 0) {
+ order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
+ selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " +
+ MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " < " + receivedTimestamp + " AND " +
+ MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " = " + groupStoryId;
+ } else {
+ order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
+ selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " +
+ MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " > " + receivedTimestamp + " AND " +
+ MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0";
+ }
try (Cursor cursor = queryTables(new String[]{ "COUNT(*)" }, selection, order, null, false)) {
if (cursor != null && cursor.moveToFirst()) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
index bf513ffeca..4d2519078d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -1446,6 +1446,11 @@ public class SmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
+ @Override
+ public boolean hasSelfReplyInGroupStory(long parentStoryId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public @NonNull Cursor getStoryReplies(long parentStoryId) {
throw new UnsupportedOperationException();
@@ -1481,6 +1486,11 @@ public class SmsDatabase extends MessageDatabase {
throw new UnsupportedOperationException();
}
+ @Override
+ public @NonNull List setGroupStoryMessagesReadSince(long threadId, long groupStoryId, long sinceTimestamp) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void deleteGroupStoryReplies(long parentStoryId) {
throw new UnsupportedOperationException();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
index 8f6cd644ed..017121daa3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java
@@ -31,6 +31,8 @@ import com.annimon.stream.Stream;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jsoup.helper.StringUtil;
+import org.signal.core.util.CursorUtil;
+import org.signal.core.util.SqlUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
@@ -46,15 +48,14 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.StickerSlide;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientDetails;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.ConversationUtil;
-import org.signal.core.util.CursorUtil;
import org.thoughtcrime.securesms.util.JsonUtils;
-import org.signal.core.util.SqlUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.push.ServiceId;
@@ -402,6 +403,22 @@ public class ThreadDatabase extends Database {
return setReadSince(Collections.singletonMap(threadId, -1L), lastSeen);
}
+ public List setRead(@NonNull ConversationId conversationId, boolean lastSeen) {
+ if (conversationId.getGroupStoryId() == null) {
+ return setRead(conversationId.getThreadId(), lastSeen);
+ } else {
+ return setGroupStoryReadSince(conversationId.getThreadId(), conversationId.getGroupStoryId(), System.currentTimeMillis());
+ }
+ }
+
+ public List setReadSince(@NonNull ConversationId conversationId, boolean lastSeen, long sinceTimestamp) {
+ if (conversationId.getGroupStoryId() != null) {
+ return setGroupStoryReadSince(conversationId.getThreadId(), conversationId.getGroupStoryId(), sinceTimestamp);
+ } else {
+ return setReadSince(conversationId.getThreadId(), lastSeen, sinceTimestamp);
+ }
+ }
+
public List setReadSince(long threadId, boolean lastSeen, long sinceTimestamp) {
return setReadSince(Collections.singletonMap(threadId, sinceTimestamp), lastSeen);
}
@@ -410,6 +427,10 @@ public class ThreadDatabase extends Database {
return setReadSince(Stream.of(threadIds).collect(Collectors.toMap(t -> t, t -> -1L)), lastSeen);
}
+ public List setGroupStoryReadSince(long threadId, long groupStoryId, long sinceTimestamp) {
+ return SignalDatabase.mms().setGroupStoryMessagesReadSince(threadId, groupStoryId, sinceTimestamp);
+ }
+
public List setReadSince(Map threadIdToSinceTimestamp, boolean lastSeen) {
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
@@ -1252,7 +1273,7 @@ public class ThreadDatabase extends Database {
pinnedPosition++;
}
-
+
db.setTransactionSuccessful();
} finally {
db.endTransaction();
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java
index 7e6b95b30e..b2a898dd02 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.jobs.AvatarGroupsV1DownloadJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage;
@@ -255,7 +255,7 @@ public final class GroupV1MessageProcessor {
Optional insertResult = smsDatabase.insertMessageInbox(groupMessage);
if (insertResult.isPresent()) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
return insertResult.get().getThreadId();
} else {
return null;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
index 3ba675445d..f81465fec6 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobLogger;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.mms.MmsException;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel;
import org.thoughtcrime.securesms.s3.S3;
import org.thoughtcrime.securesms.transport.RetryLaterException;
@@ -120,7 +120,7 @@ public final class AttachmentDownloadJob extends BaseJob {
doWork();
if (!SignalDatabase.mms().isStory(messageId)) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(0));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(0));
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java
index dee161789a..81382da3d4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@@ -122,7 +122,7 @@ public class AutomaticSessionResetJob extends BaseJob {
private void insertLocalMessage() {
MessageDatabase.InsertResult result = SignalDatabase.sms().insertChatSessionRefreshedMessage(recipientId, deviceId, sentTimestamp);
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(result.getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(result.getThreadId()));
}
private void sendNullMessage() throws IOException {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
index 8c62ee7f0b..62b4bf8462 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java
@@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.MmsRadioException;
import org.thoughtcrime.securesms.mms.PartParser;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -175,7 +175,7 @@ public class MmsDownloadJob extends BaseJob {
if (automatic) {
database.markIncomingNotificationReceived(threadId);
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(threadId));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(threadId));
}
}
@@ -255,7 +255,7 @@ public class MmsDownloadJob extends BaseJob {
if (insertResult.isPresent()) {
database.deleteMessage(messageId);
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
}
@@ -267,7 +267,7 @@ public class MmsDownloadJob extends BaseJob {
if (automatic) {
db.markIncomingNotificationReceived(threadId);
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(threadId));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(threadId));
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java
index 3736cda932..e05f237223 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java
@@ -45,7 +45,7 @@ import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
@@ -348,7 +348,7 @@ public final class MmsSendJob extends SendJob {
Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId);
if (recipient != null) {
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.forConversation(threadId));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, ConversationId.forConversation(threadId));
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java
index 3a96efeb7b..d145b429ae 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java
@@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.QuoteModel;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@@ -296,7 +296,7 @@ public abstract class PushSendJob extends SendJob {
ParentStoryId.GroupReply groupReplyStoryId = SignalDatabase.mms().getParentStoryIdForGroupReply(messageId);
if (threadId != -1 && recipient != null) {
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.fromThreadAndReply(threadId, groupReplyStoryId));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, ConversationId.fromThreadAndReply(threadId, groupReplyStoryId));
}
}
@@ -515,7 +515,7 @@ public abstract class PushSendJob extends SendJob {
if (recipient != null) {
ParentStoryId.GroupReply groupReply = SignalDatabase.mms().getParentStoryIdForGroupReply(messageId);
- ApplicationDependencies.getMessageNotifier().notifyProofRequired(context, recipient, NotificationThread.fromThreadAndReply(threadId, groupReply));
+ ApplicationDependencies.getMessageNotifier().notifyProofRequired(context, recipient, ConversationId.fromThreadAndReply(threadId, groupReply));
} else {
Log.w(TAG, "[Proof Required] No recipient! Couldn't notify.");
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
index 562c2bf0f9..44c85a9dd3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java
@@ -15,7 +15,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@@ -127,7 +127,7 @@ public class PushTextSendJob extends PushSendJob {
} catch (InsecureFallbackApprovalException e) {
warn(TAG, String.valueOf(record.getDateSent()), "Failure", e);
database.markAsPendingInsecureSmsFallback(record.getId());
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.forConversation(record.getThreadId()));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), ConversationId.forConversation(record.getThreadId()));
ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false));
} catch (UntrustedIdentityException e) {
warn(TAG, String.valueOf(record.getDateSent()), "Failure", e);
@@ -157,7 +157,7 @@ public class PushTextSendJob extends PushSendJob {
Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId);
if (threadId != -1 && recipient != null) {
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.forConversation(threadId));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, ConversationId.forConversation(threadId));
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt
index 822acf6f65..4b0c3c7fd8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveRemoteAnnouncementsJob.kt
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.jobmanager.Data
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread
+import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
@@ -214,7 +214,7 @@ class RetrieveRemoteAnnouncementsJob private constructor(private val force: Bool
SignalDatabase.attachments.getAttachmentsForMessage(insertResult.messageId)
.forEach { ApplicationDependencies.getJobManager().add(AttachmentDownloadJob(insertResult.messageId, it.attachmentId, false)) }
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.threadId))
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.threadId))
TrimThreadJob.enqueueAsync(insertResult.threadId)
highestVersion = max(highestVersion, note.releaseNote.androidMinVersion!!.toInt())
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java
index 8e636fe419..e1ab737601 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java
@@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.NotificationIds;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.VerificationCodeParser;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
@@ -124,7 +124,7 @@ public class SmsReceiveJob extends BaseJob {
Optional insertResult = storeMessage(message.get());
if (insertResult.isPresent()) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
} else if (message.isPresent()) {
Log.w(TAG, "Received an SMS from a blocked user. Ignoring.");
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java
index 20826f3486..fa3305452b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
@@ -98,7 +98,7 @@ public class SmsSendJob extends SendJob {
} catch (UndeliverableMessageException ude) {
warn(TAG, ude);
SignalDatabase.sms().markAsSentFailed(record.getId());
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.fromMessageRecord(record));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), ConversationId.fromMessageRecord(record));
}
}
@@ -116,7 +116,7 @@ public class SmsSendJob extends SendJob {
SignalDatabase.sms().markAsSentFailed(messageId);
if (threadId != -1 && recipient != null) {
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.forConversation(threadId));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, ConversationId.forConversation(threadId));
} else {
Log.w(TAG, "Could not find message! threadId: " + threadId + ", recipient: " + (recipient != null ? recipient.getId().toString() : "null"));
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java
index 23dcc2a2d3..334bd56797 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java
@@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
public class SmsSentJob extends BaseJob {
@@ -109,7 +109,7 @@ public class SmsSentJob extends BaseJob {
if (isMultipart) {
Log.w(TAG, "Service connectivity problem, but not retrying due to multipart");
database.markAsSentFailed(messageId);
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.forConversation(record.getThreadId()));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), ConversationId.forConversation(record.getThreadId()));
} else {
Log.w(TAG, "Service connectivity problem, requeuing...");
ApplicationDependencies.getJobManager().add(new SmsSendJob(messageId, record.getIndividualRecipient(), runAttempt + 1));
@@ -117,7 +117,7 @@ public class SmsSentJob extends BaseJob {
break;
default:
database.markAsSentFailed(messageId);
- ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.forConversation(record.getThreadId()));
+ ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), ConversationId.forConversation(record.getThreadId()));
}
} catch (NoSuchMessageException e) {
Log.w(TAG, e);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java
index 26262911c8..dd568c3179 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java
@@ -112,7 +112,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.StickerSlide;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress;
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -411,7 +411,7 @@ public final class MessageContentProcessor {
if (threadId != null) {
ThreadDatabase.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId);
- long visibleThread = ApplicationDependencies.getMessageNotifier().getVisibleThread().map(NotificationThread::getThreadId).orElse(-1L);
+ long visibleThread = ApplicationDependencies.getMessageNotifier().getVisibleThread().map(ConversationId::getThreadId).orElse(-1L);
if (threadId != visibleThread && metadata.getLastSeen() > 0 && metadata.getLastSeen() < pending.getReceivedTimestamp()) {
receivedTime = pending.getReceivedTimestamp();
@@ -754,7 +754,7 @@ public final class MessageContentProcessor {
ApplicationDependencies.getProtocolStore().aci().deleteAllSessions(content.getSender().getIdentifier());
SecurityEvent.broadcastSecurityUpdateEvent(context);
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
return new MessageId(insertResult.get().getMessageId(), true);
} else {
@@ -982,7 +982,7 @@ public final class MessageContentProcessor {
} else {
ReactionRecord reactionRecord = new ReactionRecord(reaction.getEmoji(), senderRecipient.getId(), message.getTimestamp(), System.currentTimeMillis());
SignalDatabase.reactions().addReaction(targetMessageId, reactionRecord);
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.fromMessageRecord(targetMessage), false);
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.fromMessageRecord(targetMessage), false);
}
return new MessageId(targetMessage.getId(), targetMessage.isMms());
@@ -998,7 +998,7 @@ public final class MessageContentProcessor {
if (targetMessage != null && RemoteDeleteUtil.isValidReceive(targetMessage, senderRecipient, content.getServerReceivedTimestamp())) {
MessageDatabase db = targetMessage.isMms() ? SignalDatabase.mms() : SignalDatabase.sms();
db.markAsRemoteDelete(targetMessage.getId());
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.fromMessageRecord(targetMessage), false);
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.fromMessageRecord(targetMessage), false);
return new MessageId(targetMessage.getId(), targetMessage.isMms());
} else if (targetMessage == null) {
warn(String.valueOf(content.getTimestamp()), "[handleRemoteDelete] Could not find matching message! timestamp: " + delete.getTargetSentTimestamp() + " author: " + senderRecipient.getId());
@@ -1613,6 +1613,14 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
database.setTransactionSuccessful();
+
+ if (parentStoryId.isGroupReply()) {
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.fromThreadAndReply(insertResult.get().getThreadId(), (ParentStoryId.GroupReply) parentStoryId));
+ } else {
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
+ TrimThreadJob.enqueueAsync(insertResult.get().getThreadId());
+ }
+
if (parentStoryId.isDirectReply()) {
return MessageId.fromNullable(insertResult.get().getMessageId(), true);
} else {
@@ -1704,6 +1712,14 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
database.setTransactionSuccessful();
+
+ if (parentStoryId.isGroupReply()) {
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.fromThreadAndReply(insertResult.get().getThreadId(), (ParentStoryId.GroupReply) parentStoryId));
+ } else {
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
+ TrimThreadJob.enqueueAsync(insertResult.get().getThreadId());
+ }
+
if (parentStoryId.isDirectReply()) {
return MessageId.fromNullable(insertResult.get().getMessageId(), true);
} else {
@@ -1776,7 +1792,7 @@ public final class MessageContentProcessor {
}
if (insertResult.isPresent()) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
TrimThreadJob.enqueueAsync(insertResult.get().getThreadId());
return new MessageId(insertResult.get().getMessageId(), true);
@@ -1860,7 +1876,7 @@ public final class MessageContentProcessor {
ApplicationDependencies.getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false));
}
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
TrimThreadJob.enqueueAsync(insertResult.get().getThreadId());
if (message.isViewOnce()) {
@@ -2329,7 +2345,7 @@ public final class MessageContentProcessor {
}
if (insertResult.isPresent()) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
return new MessageId(insertResult.get().getMessageId(), false);
} else {
return null;
@@ -2416,7 +2432,7 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId());
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
} else {
smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get());
@@ -2435,7 +2451,7 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId());
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
} else {
smsDatabase.markAsDecryptFailed(smsMessageId.get());
@@ -2457,7 +2473,7 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
smsDatabase.markAsUnsupportedProtocolVersion(insertResult.get().getMessageId());
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
} else {
smsDatabase.markAsNoSession(smsMessageId.get());
@@ -2479,7 +2495,7 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
smsDatabase.markAsInvalidMessage(insertResult.get().getMessageId());
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
} else {
smsDatabase.markAsNoSession(smsMessageId.get());
@@ -2498,7 +2514,7 @@ public final class MessageContentProcessor {
if (insertResult.isPresent()) {
smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId());
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
} else {
smsDatabase.markAsLegacyVersion(smsMessageId.get());
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java
index c0f9c6eef6..d2522f486a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java
@@ -8,7 +8,7 @@ import android.content.Intent;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import java.util.ArrayList;
@@ -27,11 +27,11 @@ public class DeleteNotificationReceiver extends BroadcastReceiver {
notifier.clearReminder(context);
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
- final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
- final ArrayList threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
+ final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
+ final ArrayList threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
if (threads != null) {
- for (NotificationThread thread : threads) {
+ for (ConversationId thread : threads) {
notifier.removeStickyThread(thread);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java
index c8789f18b9..8239eec74b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
@@ -41,11 +41,11 @@ public class MarkReadReceiver extends BroadcastReceiver {
if (!CLEAR_ACTION.equals(intent.getAction()))
return;
- final ArrayList threads = intent.getParcelableArrayListExtra(THREADS_EXTRA);
+ final ArrayList threads = intent.getParcelableArrayListExtra(THREADS_EXTRA);
if (threads != null) {
MessageNotifier notifier = ApplicationDependencies.getMessageNotifier();
- for (NotificationThread thread : threads) {
+ for (ConversationId thread : threads) {
notifier.removeStickyThread(thread);
}
@@ -55,9 +55,9 @@ public class MarkReadReceiver extends BroadcastReceiver {
SignalExecutors.BOUNDED.execute(() -> {
List messageIdsCollection = new LinkedList<>();
- for (NotificationThread thread : threads) {
+ for (ConversationId thread : threads) {
Log.i(TAG, "Marking as read: " + thread);
- List messageIds = SignalDatabase.threads().setRead(thread.getThreadId(), true);
+ List messageIds = SignalDatabase.threads().setRead(thread, true);
messageIdsCollection.addAll(messageIds);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java
index 5e1b4a10a6..ffa98a5aaf 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java
@@ -9,28 +9,28 @@ import androidx.annotation.Nullable;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
import java.util.Optional;
public interface MessageNotifier {
- void setVisibleThread(@Nullable NotificationThread notificationThread);
- @NonNull Optional getVisibleThread();
+ void setVisibleThread(@Nullable ConversationId conversationId);
+ @NonNull Optional getVisibleThread();
void clearVisibleThread();
void setLastDesktopActivityTimestamp(long timestamp);
- void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread);
- void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread);
+ void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId);
+ void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId);
void cancelDelayedNotifications();
void updateNotification(@NonNull Context context);
- void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread);
- void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, @NonNull BubbleUtil.BubbleState defaultBubbleState);
- void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, boolean signal);
- void updateNotification(@NonNull Context context, @Nullable NotificationThread notificationThread, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
+ void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId);
+ void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, @NonNull BubbleUtil.BubbleState defaultBubbleState);
+ void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, boolean signal);
+ void updateNotification(@NonNull Context context, @Nullable ConversationId conversationId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void clearReminder(@NonNull Context context);
- void addStickyThread(@NonNull NotificationThread notificationThread, long earliestTimestamp);
- void removeStickyThread(@NonNull NotificationThread notificationThread);
+ void addStickyThread(@NonNull ConversationId conversationId, long earliestTimestamp);
+ void removeStickyThread(@NonNull ConversationId conversationId);
class ReminderReceiver extends BroadcastReceiver {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java
index db7db3452e..f63aa021c5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java
@@ -15,7 +15,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
@@ -185,10 +185,10 @@ public final class NotificationCancellationHelper {
return true;
}
- Long threadId = SignalDatabase.threads().getThreadIdFor(recipientId);
- Optional focusedThread = ApplicationDependencies.getMessageNotifier().getVisibleThread();
- Long focusedThreadId = focusedThread.map(NotificationThread::getThreadId).orElse(null);
- Long focusedGroupStoryId = focusedThread.map(NotificationThread::getGroupStoryId).orElse(null);
+ Long threadId = SignalDatabase.threads().getThreadIdFor(recipientId);
+ Optional focusedThread = ApplicationDependencies.getMessageNotifier().getVisibleThread();
+ Long focusedThreadId = focusedThread.map(ConversationId::getThreadId).orElse(null);
+ Long focusedGroupStoryId = focusedThread.map(ConversationId::getGroupStoryId).orElse(null);
if (Objects.equals(threadId, focusedThreadId) && focusedGroupStoryId == null) {
Log.d(TAG, "isCancellable: user entered full screen thread.");
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java
index 07bd9a454d..703e649c86 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.notifications;
import androidx.annotation.NonNull;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
public final class NotificationIds {
@@ -25,19 +25,19 @@ public final class NotificationIds {
private NotificationIds() { }
- public static int getNotificationIdForThread(@NonNull NotificationThread notificationThread) {
- if (notificationThread.getGroupStoryId() != null) {
- return STORY_THREAD + notificationThread.getGroupStoryId().intValue();
+ public static int getNotificationIdForThread(@NonNull ConversationId conversationId) {
+ if (conversationId.getGroupStoryId() != null) {
+ return STORY_THREAD + conversationId.getGroupStoryId().intValue();
} else {
- return THREAD + (int) notificationThread.getThreadId();
+ return THREAD + (int) conversationId.getThreadId();
}
}
- public static int getNotificationIdForMessageDeliveryFailed(@NonNull NotificationThread notificationThread) {
- if (notificationThread.getGroupStoryId() != null) {
- return STORY_MESSAGE_DELIVERY_FAILURE + notificationThread.getGroupStoryId().intValue();
+ public static int getNotificationIdForMessageDeliveryFailed(@NonNull ConversationId conversationId) {
+ if (conversationId.getGroupStoryId() != null) {
+ return STORY_MESSAGE_DELIVERY_FAILURE + conversationId.getGroupStoryId().intValue();
} else {
- return MESSAGE_DELIVERY_FAILURE + (int) notificationThread.getThreadId();
+ return MESSAGE_DELIVERY_FAILURE + (int) conversationId.getThreadId();
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
index e77558f583..1d4b185517 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
@@ -11,7 +11,7 @@ import androidx.annotation.Nullable;
import org.signal.core.util.ExceptionUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.LeakyBucketLimiter;
@@ -33,12 +33,12 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
- public void setVisibleThread(@Nullable NotificationThread notificationThread) {
- getNotifier().setVisibleThread(notificationThread);
+ public void setVisibleThread(@Nullable ConversationId conversationId) {
+ getNotifier().setVisibleThread(conversationId);
}
@Override
- public @NonNull Optional getVisibleThread() {
+ public @NonNull Optional getVisibleThread() {
return getNotifier().getVisibleThread();
}
@@ -53,13 +53,13 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
- public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread) {
- getNotifier().notifyMessageDeliveryFailed(context, recipient, notificationThread);
+ public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId) {
+ getNotifier().notifyMessageDeliveryFailed(context, recipient, conversationId);
}
@Override
- public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread) {
- getNotifier().notifyProofRequired(context, recipient, notificationThread);
+ public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId) {
+ getNotifier().notifyProofRequired(context, recipient, conversationId);
}
@Override
@@ -73,23 +73,23 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
- public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread) {
- runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread));
+ public void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId) {
+ runOnLimiter(() -> getNotifier().updateNotification(context, conversationId));
}
@Override
- public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
- runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, defaultBubbleState));
+ public void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
+ runOnLimiter(() -> getNotifier().updateNotification(context, conversationId, defaultBubbleState));
}
@Override
- public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, boolean signal) {
- runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, signal));
+ public void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, boolean signal) {
+ runOnLimiter(() -> getNotifier().updateNotification(context, conversationId, signal));
}
@Override
- public void updateNotification(@NonNull Context context, @Nullable NotificationThread notificationThread, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
- runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, signal, reminderCount, defaultBubbleState));
+ public void updateNotification(@NonNull Context context, @Nullable ConversationId conversationId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
+ runOnLimiter(() -> getNotifier().updateNotification(context, conversationId, signal, reminderCount, defaultBubbleState));
}
@Override
@@ -98,13 +98,13 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
- public void addStickyThread(@NonNull NotificationThread notificationThread, long earliestTimestamp) {
- getNotifier().addStickyThread(notificationThread, earliestTimestamp);
+ public void addStickyThread(@NonNull ConversationId conversationId, long earliestTimestamp) {
+ getNotifier().addStickyThread(conversationId, earliestTimestamp);
}
@Override
- public void removeStickyThread(@NonNull NotificationThread notificationThread) {
- getNotifier().removeStickyThread(notificationThread);
+ public void removeStickyThread(@NonNull ConversationId conversationId) {
+ getNotifier().removeStickyThread(conversationId);
}
private void runOnLimiter(@NonNull Runnable runnable) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
index 78ec008d19..4908192508 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
@@ -33,7 +33,7 @@ import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
@@ -120,7 +120,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
}
ApplicationDependencies.getMessageNotifier()
- .addStickyThread(new NotificationThread(threadId, groupStoryId != Long.MIN_VALUE ? groupStoryId : null),
+ .addStickyThread(new ConversationId(threadId, groupStoryId != Long.MIN_VALUE ? groupStoryId : null),
intent.getLongExtra(EARLIEST_TIMESTAMP, System.currentTimeMillis()));
List messageIds = SignalDatabase.threads().setRead(threadId, true);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThread.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/ConversationId.kt
similarity index 60%
rename from app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThread.kt
rename to app/src/main/java/org/thoughtcrime/securesms/notifications/v2/ConversationId.kt
index edc92dfddc..2806d1fe54 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThread.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/ConversationId.kt
@@ -10,27 +10,27 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
* Represents a "thread" that a notification can belong to.
*/
@Parcelize
-data class NotificationThread(
+data class ConversationId(
val threadId: Long,
val groupStoryId: Long?
) : Parcelable {
companion object {
@JvmStatic
- fun forConversation(threadId: Long): NotificationThread {
- return NotificationThread(
+ fun forConversation(threadId: Long): ConversationId {
+ return ConversationId(
threadId = threadId,
groupStoryId = null
)
}
@JvmStatic
- fun fromMessageRecord(record: MessageRecord): NotificationThread {
- return NotificationThread(record.threadId, ((record as? MmsMessageRecord)?.parentStoryId as? ParentStoryId.GroupReply)?.serialize())
+ fun fromMessageRecord(record: MessageRecord): ConversationId {
+ return ConversationId(record.threadId, ((record as? MmsMessageRecord)?.parentStoryId as? ParentStoryId.GroupReply)?.serialize())
}
@JvmStatic
- fun fromThreadAndReply(threadId: Long, groupReply: ParentStoryId.GroupReply?): NotificationThread {
- return NotificationThread(threadId, groupReply?.serialize())
+ fun fromThreadAndReply(threadId: Long, groupReply: ParentStoryId.GroupReply?): ConversationId {
+ return ConversationId(threadId, groupReply?.serialize())
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt
index 7d45a091f5..5cf090e59a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt
@@ -45,7 +45,7 @@ import kotlin.math.max
* MessageNotifier implementation using the new system for creating and showing notifications.
*/
class MessageNotifierV2(context: Application) : MessageNotifier {
- @Volatile private var visibleThread: NotificationThread? = null
+ @Volatile private var visibleThread: ConversationId? = null
@Volatile private var lastDesktopActivityTimestamp: Long = -1
@Volatile private var lastAudibleNotification: Long = -1
@Volatile private var lastScheduledReminder: Long = 0
@@ -53,17 +53,17 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
@Volatile private var previousState: NotificationStateV2 = NotificationStateV2.EMPTY
- private val threadReminders: MutableMap = ConcurrentHashMap()
- private val stickyThreads: MutableMap = mutableMapOf()
+ private val threadReminders: MutableMap = ConcurrentHashMap()
+ private val stickyThreads: MutableMap = mutableMapOf()
private val executor = CancelableExecutor()
- override fun setVisibleThread(notificationThread: NotificationThread?) {
- visibleThread = notificationThread
- stickyThreads.remove(notificationThread)
+ override fun setVisibleThread(conversationId: ConversationId?) {
+ visibleThread = conversationId
+ stickyThreads.remove(conversationId)
}
- override fun getVisibleThread(): Optional {
+ override fun getVisibleThread(): Optional {
return Optional.ofNullable(visibleThread)
}
@@ -75,12 +75,12 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
lastDesktopActivityTimestamp = timestamp
}
- override fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, notificationThread: NotificationThread) {
- NotificationFactory.notifyMessageDeliveryFailed(context, recipient, notificationThread, visibleThread)
+ override fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, conversationId: ConversationId) {
+ NotificationFactory.notifyMessageDeliveryFailed(context, recipient, conversationId, visibleThread)
}
- override fun notifyProofRequired(context: Context, recipient: Recipient, notificationThread: NotificationThread) {
- NotificationFactory.notifyProofRequired(context, recipient, notificationThread, visibleThread)
+ override fun notifyProofRequired(context: Context, recipient: Recipient, conversationId: ConversationId) {
+ NotificationFactory.notifyProofRequired(context, recipient, conversationId, visibleThread)
}
override fun cancelDelayedNotifications() {
@@ -91,21 +91,21 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
updateNotification(context, null, false, 0, BubbleState.HIDDEN)
}
- override fun updateNotification(context: Context, notificationThread: NotificationThread) {
+ override fun updateNotification(context: Context, conversationId: ConversationId) {
if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
Log.i(TAG, "Scheduling delayed notification...")
- executor.enqueue(context, notificationThread)
+ executor.enqueue(context, conversationId)
} else {
- updateNotification(context, notificationThread, true)
+ updateNotification(context, conversationId, true)
}
}
- override fun updateNotification(context: Context, notificationThread: NotificationThread, defaultBubbleState: BubbleState) {
- updateNotification(context, notificationThread, false, 0, defaultBubbleState)
+ override fun updateNotification(context: Context, conversationId: ConversationId, defaultBubbleState: BubbleState) {
+ updateNotification(context, conversationId, false, 0, defaultBubbleState)
}
- override fun updateNotification(context: Context, notificationThread: NotificationThread, signal: Boolean) {
- updateNotification(context, notificationThread, signal, 0, BubbleState.HIDDEN)
+ override fun updateNotification(context: Context, conversationId: ConversationId, signal: Boolean) {
+ updateNotification(context, conversationId, signal, 0, BubbleState.HIDDEN)
}
/**
@@ -114,7 +114,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
*/
override fun updateNotification(
context: Context,
- notificationThread: NotificationThread?,
+ conversationId: ConversationId?,
signal: Boolean,
reminderCount: Int,
defaultBubbleState: BubbleState
@@ -164,7 +164,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
val displayedNotifications: Set? = ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrNull()
if (displayedNotifications != null) {
- val cleanedUpThreads: MutableSet = mutableSetOf()
+ val cleanedUpThreads: MutableSet = mutableSetOf()
state.conversations.filterNot { it.hasNewNotifications() || displayedNotifications.contains(it.notificationId) }
.forEach { conversation ->
cleanedUpThreads += conversation.thread
@@ -179,7 +179,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
- val retainStickyThreadIds: Set = state.getThreadsWithMostRecentNotificationFromSelf()
+ val retainStickyThreadIds: Set = state.getThreadsWithMostRecentNotificationFromSelf()
stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) }
if (state.isEmpty) {
@@ -190,13 +190,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
return
}
- val alertOverrides: Set = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
+ val alertOverrides: Set = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
- val threadsThatAlerted: Set = NotificationFactory.notify(
+ val threadsThatAlerted: Set = NotificationFactory.notify(
context = ContextThemeWrapper(context, R.style.TextSecure_LightTheme),
state = state,
visibleThread = visibleThread,
- targetThread = notificationThread,
+ targetThread = conversationId,
defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification,
notificationConfigurationChanged = notificationConfigurationChanged,
@@ -238,23 +238,23 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
// Intentionally left blank
}
- override fun addStickyThread(notificationThread: NotificationThread, earliestTimestamp: Long) {
- stickyThreads[notificationThread] = StickyThread(notificationThread, NotificationIds.getNotificationIdForThread(notificationThread), earliestTimestamp)
+ override fun addStickyThread(conversationId: ConversationId, earliestTimestamp: Long) {
+ stickyThreads[conversationId] = StickyThread(conversationId, NotificationIds.getNotificationIdForThread(conversationId), earliestTimestamp)
}
- override fun removeStickyThread(notificationThread: NotificationThread) {
- stickyThreads.remove(notificationThread)
+ override fun removeStickyThread(conversationId: ConversationId) {
+ stickyThreads.remove(conversationId)
}
- private fun updateReminderTimestamps(context: Context, alertOverrides: Set, threadsThatAlerted: Set) {
+ private fun updateReminderTimestamps(context: Context, alertOverrides: Set, threadsThatAlerted: Set) {
if (SignalStore.settings().messageNotificationsRepeatAlerts == 0) {
return
}
- val iterator: MutableIterator> = threadReminders.iterator()
+ val iterator: MutableIterator> = threadReminders.iterator()
while (iterator.hasNext()) {
- val entry: MutableEntry = iterator.next()
- val (id: NotificationThread, reminder: Reminder) = entry
+ val entry: MutableEntry = iterator.next()
+ val (id: ConversationId, reminder: Reminder) = entry
if (alertOverrides.contains(id)) {
val notifyCount: Int = reminder.count + 1
if (notifyCount >= SignalStore.settings().messageNotificationsRepeatAlerts) {
@@ -265,7 +265,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
- for (alertedThreadId: NotificationThread in threadsThatAlerted) {
+ for (alertedThreadId: ConversationId in threadsThatAlerted) {
threadReminders[alertedThreadId] = Reminder(lastAudibleNotification)
}
@@ -319,7 +319,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
- data class StickyThread(val notificationThread: NotificationThread, val notificationId: Int, val earliestTimestamp: Long)
+ data class StickyThread(val conversationId: ConversationId, val notificationId: Int, val earliestTimestamp: Long)
private data class Reminder(val lastNotified: Long, val count: Int = 0)
}
@@ -368,8 +368,8 @@ private class CancelableExecutor {
private val executor: Executor = Executors.newSingleThreadExecutor()
private val tasks: MutableSet = mutableSetOf()
- fun enqueue(context: Context, notificationThread: NotificationThread) {
- execute(DelayedNotification(context, notificationThread))
+ fun enqueue(context: Context, conversationId: ConversationId) {
+ execute(DelayedNotification(context, conversationId))
}
private fun execute(runnable: DelayedNotification) {
@@ -389,7 +389,7 @@ private class CancelableExecutor {
}
}
- private class DelayedNotification constructor(private val context: Context, private val thread: NotificationThread) : Runnable {
+ private class DelayedNotification constructor(private val context: Context, private val thread: ConversationId) : Runnable {
private val canceled = AtomicBoolean(false)
private val delayUntil: Long = System.currentTimeMillis() + DELAY
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt
index 16dd4398fd..779cb0eec7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt
@@ -7,6 +7,7 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.SpannableStringBuilder
import androidx.core.app.TaskStackBuilder
+import org.signal.core.util.PendingIntentFlags
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.contacts.TurnOffContactJoinedNotificationsActivity
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
@@ -22,6 +23,7 @@ import org.thoughtcrime.securesms.notifications.ReplyMethod
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.KeyCachingService
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.Util
@@ -31,7 +33,7 @@ import org.thoughtcrime.securesms.util.Util
*/
data class NotificationConversation(
val recipient: Recipient,
- val thread: NotificationThread,
+ val thread: ConversationId,
val notificationItems: List
) {
val mostRecentNotification: NotificationItemV2 = notificationItems.last()
@@ -43,7 +45,7 @@ data class NotificationConversation(
fun getContentTitle(context: Context): CharSequence {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
- recipient.getDisplayName(context)
+ getDisplayName(context)
} else {
context.getString(R.string.SingleRecipientNotificationBuilder_signal)
}
@@ -82,7 +84,7 @@ data class NotificationConversation(
fun getConversationTitle(context: Context): CharSequence? {
if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
- return if (isGroup) recipient.getDisplayName(context) else null
+ return if (isGroup) getDisplayName(context) else null
}
return context.getString(R.string.SingleRecipientNotificationBuilder_signal)
}
@@ -113,13 +115,21 @@ data class NotificationConversation(
fun getPendingIntent(context: Context): PendingIntent {
val intent: Intent = if (thread.groupStoryId != null) {
- StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
+ StoryViewerActivity.createIntent(
+ context,
+ StoryViewerArgs(
+ recipientId = recipient.id,
+ storyId = thread.groupStoryId,
+ isInHiddenStoryMode = recipient.shouldHideStory(),
+ isFromNotification = true,
+ groupReplyStartPosition = mostRecentNotification.getStartingPosition(context)
+ )
+ )
} else {
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.withStartingPosition(mostRecentNotification.getStartingPosition(context))
.build()
- .makeUniqueToPreventMerging()
- }
+ }.makeUniqueToPreventMerging()
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(intent)
@@ -141,7 +151,7 @@ data class NotificationConversation(
.putParcelableArrayListExtra(DeleteNotificationReceiver.EXTRA_THREADS, arrayListOf(thread))
.makeUniqueToPreventMerging()
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.updateCurrent())
}
fun getMarkAsReadIntent(context: Context): PendingIntent {
@@ -151,7 +161,7 @@ data class NotificationConversation(
.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId)
.makeUniqueToPreventMerging()
- return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt(), intent, PendingIntentFlags.updateCurrent())
}
fun getQuickReplyIntent(context: Context): PendingIntent {
@@ -159,7 +169,7 @@ data class NotificationConversation(
.build()
.makeUniqueToPreventMerging()
- return PendingIntent.getActivity(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ return PendingIntent.getActivity(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntentFlags.updateCurrent())
}
fun getRemoteReplyIntent(context: Context, replyMethod: ReplyMethod): PendingIntent {
@@ -171,7 +181,7 @@ data class NotificationConversation(
.putExtra(RemoteReplyReceiver.GROUP_STORY_ID_EXTRA, notificationItems.first().thread.groupStoryId ?: Long.MIN_VALUE)
.makeUniqueToPreventMerging()
- return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntentFlags.updateCurrent())
}
fun getTurnOffJoinedNotificationsIntent(context: Context): PendingIntent {
@@ -179,10 +189,18 @@ data class NotificationConversation(
context,
0,
TurnOffContactJoinedNotificationsActivity.newIntent(context, thread.threadId),
- PendingIntent.FLAG_UPDATE_CURRENT
+ PendingIntentFlags.updateCurrent()
)
}
+ private fun getDisplayName(context: Context): String {
+ return if (thread.groupStoryId != null) {
+ context.getString(R.string.SingleRecipientNotificationBuilder__s_dot_story, recipient.getDisplayName(context))
+ } else {
+ recipient.getDisplayName(context)
+ }
+ }
+
override fun toString(): String {
return "NotificationConversation(thread=$thread, notificationItems=$notificationItems, messageCount=$messageCount, hasNewNotifications=${hasNewNotifications()})"
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt
index 7dc1e824f3..d4b961da58 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt
@@ -27,7 +27,6 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.my.MyStoriesActivity
-import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.BubbleUtil
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ServiceUtil
@@ -43,14 +42,14 @@ object NotificationFactory {
fun notify(
context: Context,
state: NotificationStateV2,
- visibleThread: NotificationThread?,
- targetThread: NotificationThread?,
+ visibleThread: ConversationId?,
+ targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
- alertOverrides: Set,
+ alertOverrides: Set,
previousState: NotificationStateV2
- ): Set {
+ ): Set {
if (state.isEmpty) {
Log.d(TAG, "State is empty, bailing")
return emptySet()
@@ -87,14 +86,14 @@ object NotificationFactory {
private fun notify19(
context: Context,
state: NotificationStateV2,
- visibleThread: NotificationThread?,
- targetThread: NotificationThread?,
+ visibleThread: ConversationId?,
+ targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
- alertOverrides: Set,
+ alertOverrides: Set,
nonVisibleThreadCount: Int
- ): Set {
- val threadsThatNewlyAlerted: MutableSet = mutableSetOf()
+ ): Set {
+ val threadsThatNewlyAlerted: MutableSet = mutableSetOf()
state.conversations.find { it.thread == visibleThread }?.let { conversation ->
if (conversation.hasNewNotifications()) {
@@ -129,16 +128,16 @@ object NotificationFactory {
private fun notify24(
context: Context,
state: NotificationStateV2,
- visibleThread: NotificationThread?,
- targetThread: NotificationThread?,
+ visibleThread: ConversationId?,
+ targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
- alertOverrides: Set,
+ alertOverrides: Set,
nonVisibleThreadCount: Int,
previousState: NotificationStateV2
- ): Set {
- val threadsThatNewlyAlerted: MutableSet = mutableSetOf()
+ ): Set {
+ val threadsThatNewlyAlerted: MutableSet = mutableSetOf()
state.conversations.forEach { conversation ->
if (conversation.thread == visibleThread && conversation.hasNewNotifications()) {
@@ -169,7 +168,7 @@ object NotificationFactory {
private fun notifyForConversation(
context: Context,
conversation: NotificationConversation,
- targetThread: NotificationThread?,
+ targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
shouldAlert: Boolean
) {
@@ -189,8 +188,12 @@ object NotificationFactory {
setContentTitle(conversation.getContentTitle(context))
setLargeIcon(conversation.getContactLargeIcon(context).toLargeBitmap(context))
addPerson(conversation.recipient)
- setShortcutId(ConversationUtil.getShortcutId(conversation.recipient))
- setLocusId(ConversationUtil.getShortcutId(conversation.recipient))
+
+ if (conversation.thread.groupStoryId == null) {
+ setShortcutId(ConversationUtil.getShortcutId(conversation.recipient))
+ setLocusId(ConversationUtil.getShortcutId(conversation.recipient))
+ }
+
setContentInfo(conversation.messageCount.toString())
setNumber(conversation.messageCount)
setContentText(conversation.getContentText(context))
@@ -292,22 +295,18 @@ object NotificationFactory {
ringtone.play()
}
- fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, thread: NotificationThread, visibleThread: NotificationThread?) {
+ fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, thread: ConversationId, visibleThread: ConversationId?) {
if (thread == visibleThread) {
notifyInThread(context, recipient, 0)
return
}
- val intent: Intent = if (recipient.isDistributionList) {
+ val intent: Intent = if (recipient.isDistributionList || thread.groupStoryId != null) {
Intent(context, MyStoriesActivity::class.java)
- .makeUniqueToPreventMerging()
- } else if (thread.groupStoryId != null) {
- StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
} else {
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.build()
- .makeUniqueToPreventMerging()
- }
+ }.makeUniqueToPreventMerging()
val builder: NotificationBuilder = NotificationBuilder.create(context)
@@ -326,22 +325,18 @@ object NotificationFactory {
NotificationManagerCompat.from(context).safelyNotify(context, recipient, NotificationIds.getNotificationIdForMessageDeliveryFailed(thread), builder.build())
}
- fun notifyProofRequired(context: Context, recipient: Recipient, thread: NotificationThread, visibleThread: NotificationThread?) {
+ fun notifyProofRequired(context: Context, recipient: Recipient, thread: ConversationId, visibleThread: ConversationId?) {
if (thread == visibleThread) {
notifyInThread(context, recipient, 0)
return
}
- val intent: Intent = if (recipient.isDistributionList) {
+ val intent: Intent = if (recipient.isDistributionList || thread.groupStoryId != null) {
Intent(context, MyStoriesActivity::class.java)
- .makeUniqueToPreventMerging()
- } else if (thread.groupStoryId != null) {
- StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
} else {
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.build()
- .makeUniqueToPreventMerging()
- }
+ }.makeUniqueToPreventMerging()
val builder: NotificationBuilder = NotificationBuilder.create(context)
@@ -366,7 +361,7 @@ object NotificationFactory {
val conversation = NotificationConversation(
recipient = recipient,
- thread = NotificationThread.forConversation(threadId),
+ thread = ConversationId.forConversation(threadId),
notificationItems = listOf(
MessageNotification(
threadRecipient = recipient,
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt
index 32e93cae58..92ce7cc5e3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt
@@ -40,7 +40,7 @@ private const val MAX_DISPLAY_LENGTH = 500
sealed class NotificationItemV2(val threadRecipient: Recipient, protected val record: MessageRecord) : Comparable {
val id: Long = record.id
- val thread = NotificationThread.fromMessageRecord(record)
+ val thread = ConversationId.fromMessageRecord(record)
val isMms: Boolean = record.isMms
val slideDeck: SlideDeck? = if (record.isViewOnce) null else (record as? MmsMessageRecord)?.slideDeck
val isJoined: Boolean = record.isJoined
@@ -201,7 +201,11 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
}
override fun getStartingPosition(context: Context): Int {
- return -1
+ return if (thread.groupStoryId != null) {
+ SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, thread.groupStoryId, record.dateReceived)
+ } else {
+ -1
+ }
}
override fun getLargeIconUri(): Uri? {
@@ -304,7 +308,7 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va
}
override fun getStartingPosition(context: Context): Int {
- return SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, record.dateReceived)
+ return SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, thread.groupStoryId ?: 0L, record.dateReceived)
}
override fun getLargeIconUri(): Uri? = null
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt
index e60100bcf5..20db05068d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt
@@ -21,7 +21,7 @@ object NotificationStateProvider {
private val TAG = Log.tag(NotificationStateProvider::class.java)
@WorkerThread
- fun constructNotificationState(stickyThreads: Map, notificationProfile: NotificationProfile?): NotificationStateV2 {
+ fun constructNotificationState(stickyThreads: Map, notificationProfile: NotificationProfile?): NotificationStateV2 {
val messages: MutableList = mutableListOf()
SignalDatabase.mmsSms.getMessagesForNotificationState(stickyThreads.values).use { unreadMessages ->
@@ -35,16 +35,27 @@ object NotificationStateProvider {
val threadRecipient: Recipient? = SignalDatabase.threads.getRecipientForThreadId(record.threadId)
if (threadRecipient != null) {
val hasUnreadReactions = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.REACTIONS_UNREAD) == 1
+ val conversationId = ConversationId.fromMessageRecord(record)
+
+ val parentRecord = conversationId.groupStoryId?.let {
+ SignalDatabase.mms.getMessageRecord(it)
+ }
+
+ val hasSelfRepliedToGroupStory = conversationId.groupStoryId?.let {
+ SignalDatabase.mms.hasSelfReplyInGroupStory(it)
+ }
messages += NotificationMessage(
messageRecord = record,
reactions = if (hasUnreadReactions) SignalDatabase.reactions.getReactions(MessageId(record.id, record.isMms)) else emptyList(),
threadRecipient = threadRecipient,
- thread = NotificationThread.fromMessageRecord(record),
- stickyThread = stickyThreads.containsKey(NotificationThread.fromMessageRecord(record)),
+ thread = conversationId,
+ stickyThread = stickyThreads.containsKey(conversationId),
isUnreadMessage = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.READ) == 0,
hasUnreadReactions = hasUnreadReactions,
- lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN)
+ lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN),
+ isParentStorySentBySelf = parentRecord?.isOutgoing ?: false,
+ hasSelfRepliedToStory = hasSelfRepliedToGroupStory ?: false
)
}
try {
@@ -104,16 +115,20 @@ object NotificationStateProvider {
val messageRecord: MessageRecord,
val reactions: List,
val threadRecipient: Recipient,
- val thread: NotificationThread,
+ val thread: ConversationId,
val stickyThread: Boolean,
val isUnreadMessage: Boolean,
val hasUnreadReactions: Boolean,
- val lastReactionRead: Long
+ val lastReactionRead: Long,
+ val isParentStorySentBySelf: Boolean,
+ val hasSelfRepliedToStory: Boolean
) {
- private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing
+ private val isGroupStoryReply: Boolean = thread.groupStoryId != null
+ private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing && !isGroupStoryReply
+ private val isNotifiableGroupStoryMessage: Boolean = isUnreadMessage && !messageRecord.isOutgoing && isGroupStoryReply && (isParentStorySentBySelf || hasSelfRepliedToStory)
fun includeMessage(notificationProfile: NotificationProfile?): MessageInclusion {
- return if (isUnreadIncoming || stickyThread) {
+ return if (isUnreadIncoming || stickyThread || isNotifiableGroupStoryMessage) {
if (threadRecipient.isMuted && (threadRecipient.isDoNotNotifyMentions || !messageRecord.hasSelfMention())) {
MessageInclusion.MUTE_FILTERED
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id) && !(notificationProfile.allowAllMentions && messageRecord.hasSelfMention())) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt
index 1dd9f89a96..6749f4edc8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt
@@ -39,18 +39,18 @@ data class NotificationStateV2(val conversations: List
val mostRecentSender: Recipient?
get() = mostRecentNotification?.individualRecipient
- fun getNonVisibleConversation(visibleThread: NotificationThread?): List {
+ fun getNonVisibleConversation(visibleThread: ConversationId?): List {
return conversations.filterNot { it.thread == visibleThread }
}
- fun getConversation(notificationThread: NotificationThread): NotificationConversation? {
- return conversations.firstOrNull { it.thread == notificationThread }
+ fun getConversation(conversationId: ConversationId): NotificationConversation? {
+ return conversations.firstOrNull { it.thread == conversationId }
}
fun getDeleteIntent(context: Context): PendingIntent? {
val ids = LongArray(messageCount)
val mms = BooleanArray(ids.size)
- val threads: MutableList = mutableListOf()
+ val threads: MutableList = mutableListOf()
conversations.forEach { conversation ->
threads += conversation.thread
@@ -79,7 +79,7 @@ data class NotificationStateV2(val conversations: List
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
- fun getThreadsWithMostRecentNotificationFromSelf(): Set {
+ fun getThreadsWithMostRecentNotificationFromSelf(): Set {
return conversations.filter { it.mostRecentNotification.individualRecipient.isSelf }
.map { it.thread }
.toSet()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
index 5aafd18b0e..2062130510 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientDialogViewModel.java
@@ -32,12 +32,12 @@ import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupsActivity;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
+import org.thoughtcrime.securesms.stories.StoryViewerArgs;
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
-import java.util.Collections;
import java.util.Objects;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@@ -141,7 +141,10 @@ final class RecipientDialogViewModel extends ViewModel {
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
onMessageClicked(activity);
} else {
- activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null, Collections.emptyList()));
+ activity.startActivity(StoryViewerActivity.createIntent(
+ activity,
+ new StoryViewerArgs.Builder(recipientDialogRepository.getRecipientId(), recipient.getValue().shouldHideStory())
+ .build()));
}
}
@@ -177,7 +180,10 @@ final class RecipientDialogViewModel extends ViewModel {
if (storyViewState.getValue() == null || storyViewState.getValue() == StoryViewState.NONE) {
activity.startActivity(ConversationSettingsActivity.forRecipient(activity, recipientDialogRepository.getRecipientId()));
} else {
- activity.startActivity(StoryViewerActivity.createIntent(activity, recipientDialogRepository.getRecipientId(), -1L, recipient.getValue().shouldHideStory(), null, null, null, Collections.emptyList()));
+ activity.startActivity(StoryViewerActivity.createIntent(
+ activity,
+ new StoryViewerArgs.Builder(recipientDialogRepository.getRecipientId(), recipient.getValue().shouldHideStory())
+ .build()));
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java
index 9feac0037d..0b1c729cb9 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java
@@ -40,7 +40,7 @@ import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.messages.GroupSendUtil;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
@@ -351,7 +351,7 @@ private void processStateless(@NonNull Function1 messageAndThreadId = SignalDatabase.sms().insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer);
ApplicationDependencies.getMessageNotifier()
- .updateNotification(context, NotificationThread.forConversation(messageAndThreadId.second()), signal);
+ .updateNotification(context, ConversationId.forConversation(messageAndThreadId.second()), signal);
}
public void insertReceivedCall(@NonNull RemotePeer remotePeer, boolean signal, boolean isVideoOffer) {
Pair messageAndThreadId = SignalDatabase.sms().insertReceivedCall(remotePeer.getId(), isVideoOffer);
ApplicationDependencies.getMessageNotifier()
- .updateNotification(context, NotificationThread.forConversation(messageAndThreadId.second()), signal);
+ .updateNotification(context, ConversationId.forConversation(messageAndThreadId.second()), signal);
}
public void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/StoryViewerArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryViewerArgs.kt
new file mode 100644
index 0000000000..1c2ccf0e46
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/StoryViewerArgs.kt
@@ -0,0 +1,84 @@
+package org.thoughtcrime.securesms.stories
+
+import android.net.Uri
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+import org.thoughtcrime.securesms.blurhash.BlurHash
+import org.thoughtcrime.securesms.recipients.RecipientId
+
+/**
+ * Arguments for launching the story viewer, prefilled with sensible defaults.
+ */
+@Parcelize
+data class StoryViewerArgs(
+ val recipientId: RecipientId,
+ val isInHiddenStoryMode: Boolean,
+ val storyId: Long = -1L,
+ val storyThumbTextModel: StoryTextPostModel? = null,
+ val storyThumbUri: Uri? = null,
+ val storyThumbBlur: BlurHash? = null,
+ val recipientIds: List = emptyList(),
+ val isFromNotification: Boolean = false,
+ val groupReplyStartPosition: Int = -1
+) : Parcelable {
+
+ class Builder(private val recipientId: RecipientId, private val isInHiddenStoryMode: Boolean) {
+
+ private var storyId: Long = -1L
+ private var storyThumbTextModel: StoryTextPostModel? = null
+ private var storyThumbUri: Uri? = null
+ private var storyThumbBlur: BlurHash? = null
+ private var recipientIds: List = emptyList()
+ private var isFromNotification: Boolean = false
+ private var groupReplyStartPosition: Int = -1
+
+ fun withStoryId(storyId: Long): Builder {
+ this.storyId = storyId
+ return this
+ }
+
+ fun withStoryThumbTextModel(storyThumbTextModel: StoryTextPostModel?): Builder {
+ this.storyThumbTextModel = storyThumbTextModel
+ return this
+ }
+
+ fun withStoryThumbUri(storyThumbUri: Uri?): Builder {
+ this.storyThumbUri = storyThumbUri
+ return this
+ }
+
+ fun withStoryThumbBlur(storyThumbBlur: BlurHash?): Builder {
+ this.storyThumbBlur = storyThumbBlur
+ return this
+ }
+
+ fun withRecipientIds(recipientIds: List): Builder {
+ this.recipientIds = recipientIds
+ return this
+ }
+
+ fun isFromNotification(isFromNotification: Boolean): Builder {
+ this.isFromNotification = isFromNotification
+ return this
+ }
+
+ fun withGroupReplyStartPosition(groupReplyStartPosition: Int): Builder {
+ this.groupReplyStartPosition = groupReplyStartPosition
+ return this
+ }
+
+ fun build(): StoryViewerArgs {
+ return StoryViewerArgs(
+ recipientId = recipientId,
+ isInHiddenStoryMode = isInHiddenStoryMode,
+ storyId = storyId,
+ storyThumbTextModel = storyThumbTextModel,
+ storyThumbUri = storyThumbUri,
+ storyThumbBlur = storyThumbBlur,
+ recipientIds = recipientIds,
+ isFromNotification = isFromNotification,
+ groupReplyStartPosition = groupReplyStartPosition
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt
index 86950e8dc2..2c7db44d5d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.stories.StoryTextPostModel
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
import org.thoughtcrime.securesms.stories.my.MyStoriesActivity
@@ -209,13 +210,15 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
startActivityIfAble(
StoryViewerActivity.createIntent(
context = requireContext(),
- recipientId = model.data.storyRecipient.id,
- storyId = -1L,
- onlyIncludeHiddenStories = model.data.isHidden,
- storyThumbTextModel = text,
- storyThumbUri = image,
- storyThumbBlur = blur,
- recipientIds = viewModel.getRecipientIds(model.data.isHidden)
+ storyViewerArgs = StoryViewerArgs(
+ recipientId = model.data.storyRecipient.id,
+ storyId = -1L,
+ isInHiddenStoryMode = model.data.isHidden,
+ storyThumbTextModel = text,
+ storyThumbUri = image,
+ storyThumbBlur = blur,
+ recipientIds = viewModel.getRecipientIds(model.data.isHidden)
+ )
),
options.toBundle()
)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt
index 6f13e248bc..6a48367780 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesFragment.kt
@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.StoryTextPostModel
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
@@ -101,7 +102,20 @@ class MyStoriesFragment : DSLSettingsFragment(
}
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), preview, ViewCompat.getTransitionName(preview) ?: "")
- startActivity(StoryViewerActivity.createIntent(requireContext(), recipient.id, conversationMessage.messageRecord.id, recipient.shouldHideStory(), text, image, blur), options.toBundle())
+ startActivity(
+ StoryViewerActivity.createIntent(
+ context = requireContext(),
+ storyViewerArgs = StoryViewerArgs(
+ recipientId = recipient.id,
+ storyId = conversationMessage.messageRecord.id,
+ isInHiddenStoryMode = recipient.shouldHideStory(),
+ storyThumbTextModel = text,
+ storyThumbUri = image,
+ storyThumbBlur = blur
+ )
+ ),
+ options.toBundle()
+ )
}
},
onLongClick = {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt
index 124cffc200..898e4bef79 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt
@@ -2,17 +2,14 @@ package org.thoughtcrime.securesms.stories.viewer
import android.content.Context
import android.content.Intent
-import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
-import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
-import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.stories.StoryTextPostModel
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner {
@@ -32,57 +29,40 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
voiceNoteMediaController = VoiceNoteMediaController(this)
if (savedInstanceState == null) {
- supportFragmentManager.beginTransaction()
- .replace(
- R.id.fragment_container,
- StoryViewerFragment.create(
- intent.getParcelableExtra(ARG_START_RECIPIENT_ID)!!,
- intent.getLongExtra(ARG_START_STORY_ID, -1L),
- intent.getBooleanExtra(ARG_HIDDEN_STORIES, false),
- intent.getParcelableExtra(ARG_CROSSFADE_TEXT_MODEL),
- intent.getParcelableExtra(ARG_CROSSFADE_IMAGE_URI),
- intent.getStringExtra(ARG_CROSSFADE_IMAGE_BLUR),
- intent.getParcelableArrayListExtra(ARG_RECIPIENT_IDS)!!
- )
- )
- .commit()
+ replaceStoryViewerFragment()
}
}
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ setIntent(intent)
+ replaceStoryViewerFragment()
+ }
+
override fun onEnterAnimationComplete() {
if (Build.VERSION.SDK_INT >= 21) {
window.transitionBackgroundFadeDuration = 100
}
}
+ private fun replaceStoryViewerFragment() {
+ supportFragmentManager.beginTransaction()
+ .replace(
+ R.id.fragment_container,
+ StoryViewerFragment.create(intent.getParcelableExtra(ARGS)!!)
+ )
+ .commit()
+ }
+
companion object {
- private const val ARG_START_RECIPIENT_ID = "start.recipient.id"
- private const val ARG_START_STORY_ID = "start.story.id"
- private const val ARG_HIDDEN_STORIES = "hidden_stories"
- private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model"
- private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri"
- private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur"
- private const val ARG_RECIPIENT_IDS = "recipient_ids"
+ private const val ARGS = "story.viewer.args"
@JvmStatic
fun createIntent(
context: Context,
- recipientId: RecipientId,
- storyId: Long = -1L,
- onlyIncludeHiddenStories: Boolean = false,
- storyThumbTextModel: StoryTextPostModel? = null,
- storyThumbUri: Uri? = null,
- storyThumbBlur: BlurHash? = null,
- recipientIds: List = emptyList()
+ storyViewerArgs: StoryViewerArgs
): Intent {
- return Intent(context, StoryViewerActivity::class.java)
- .putExtra(ARG_START_RECIPIENT_ID, recipientId)
- .putExtra(ARG_START_STORY_ID, storyId)
- .putExtra(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
- .putExtra(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
- .putExtra(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
- .putExtra(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur?.hash)
- .putParcelableArrayListExtra(ARG_RECIPIENT_IDS, ArrayList(recipientIds))
+ return Intent(context, StoryViewerActivity::class.java).putExtra(ARGS, storyViewerArgs)
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt
index 560c757d58..07f0b85184 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerFragment.kt
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.stories.viewer
-import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
@@ -8,9 +7,8 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveDataReactiveStreams
import androidx.viewpager2.widget.ViewPager2
import org.thoughtcrime.securesms.R
-import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.stories.StoryTextPostModel
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
/**
@@ -24,35 +22,16 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
private val viewModel: StoryViewerViewModel by viewModels(
factoryProducer = {
- StoryViewerViewModel.Factory(storyRecipientId, onlyIncludeHiddenStories, storyThumbTextModel, storyThumbUri, storuThumbBlur, recipientIds, StoryViewerRepository())
+ StoryViewerViewModel.Factory(storyViewerArgs, StoryViewerRepository())
}
)
- private val storyRecipientId: RecipientId
- get() = requireArguments().getParcelable(ARG_START_RECIPIENT_ID)!!
-
- private val storyId: Long
- get() = requireArguments().getLong(ARG_START_STORY_ID, -1L)
-
- private val onlyIncludeHiddenStories: Boolean
- get() = requireArguments().getBoolean(ARG_HIDDEN_STORIES)
-
- private val storyThumbTextModel: StoryTextPostModel?
- get() = requireArguments().getParcelable(ARG_CROSSFADE_TEXT_MODEL)
-
- private val storyThumbUri: Uri?
- get() = requireArguments().getParcelable(ARG_CROSSFADE_IMAGE_URI)
-
- private val storuThumbBlur: BlurHash?
- get() = requireArguments().getString(ARG_CROSSFADE_IMAGE_BLUR)?.let { BlurHash.parseOrNull(it) }
-
- private val recipientIds: List
- get() = requireArguments().getParcelableArrayList(ARG_RECIPIENT_IDS)!!
+ private val storyViewerArgs: StoryViewerArgs by lazy { requireArguments().getParcelable(ARGS)!! }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
storyPager = view.findViewById(R.id.story_item_pager)
- val adapter = StoryViewerPagerAdapter(this, storyId)
+ val adapter = StoryViewerPagerAdapter(this, storyViewerArgs.storyId, storyViewerArgs.isFromNotification, storyViewerArgs.groupReplyStartPosition)
storyPager.adapter = adapter
viewModel.isChildScrolling.observe(viewLifecycleOwner) {
@@ -110,32 +89,12 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
}
companion object {
- private const val ARG_START_RECIPIENT_ID = "start.recipient.id"
- private const val ARG_START_STORY_ID = "start.story.id"
- private const val ARG_HIDDEN_STORIES = "hidden_stories"
- private const val ARG_CROSSFADE_TEXT_MODEL = "crossfade.text.model"
- private const val ARG_CROSSFADE_IMAGE_URI = "crossfade.image.uri"
- private const val ARG_CROSSFADE_IMAGE_BLUR = "crossfade.image.blur"
- private const val ARG_RECIPIENT_IDS = "start.recipient.ids"
+ private const val ARGS = "args"
- fun create(
- storyRecipientId: RecipientId,
- storyId: Long,
- onlyIncludeHiddenStories: Boolean,
- storyThumbTextModel: StoryTextPostModel? = null,
- storyThumbUri: Uri? = null,
- storyThumbBlur: String? = null,
- recipientIds: List = emptyList()
- ): Fragment {
+ fun create(storyViewerArgs: StoryViewerArgs): Fragment {
return StoryViewerFragment().apply {
arguments = Bundle().apply {
- putParcelable(ARG_START_RECIPIENT_ID, storyRecipientId)
- putLong(ARG_START_STORY_ID, storyId)
- putBoolean(ARG_HIDDEN_STORIES, onlyIncludeHiddenStories)
- putParcelable(ARG_CROSSFADE_TEXT_MODEL, storyThumbTextModel)
- putParcelable(ARG_CROSSFADE_IMAGE_URI, storyThumbUri)
- putString(ARG_CROSSFADE_IMAGE_BLUR, storyThumbBlur)
- putParcelableArrayList(ARG_RECIPIENT_IDS, ArrayList(recipientIds))
+ putParcelable(ARGS, storyViewerArgs)
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt
index bd8883a6e6..d93c313286 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerPagerAdapter.kt
@@ -6,7 +6,12 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
-class StoryViewerPagerAdapter(fragment: Fragment, private val initialStoryId: Long) : FragmentStateAdapter(fragment) {
+class StoryViewerPagerAdapter(
+ fragment: Fragment,
+ private val initialStoryId: Long,
+ private val isFromNotification: Boolean,
+ private val groupReplyStartPosition: Int
+) : FragmentStateAdapter(fragment) {
private var pages: List = emptyList()
@@ -21,7 +26,7 @@ class StoryViewerPagerAdapter(fragment: Fragment, private val initialStoryId: Lo
override fun getItemCount(): Int = pages.size
override fun createFragment(position: Int): Fragment {
- return StoryViewerPageFragment.create(pages[position], initialStoryId)
+ return StoryViewerPageFragment.create(pages[position], initialStoryId, isFromNotification, groupReplyStartPosition)
}
private class Callback(
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt
index 977d891712..268f61fbc5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModel.kt
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.stories.viewer
-import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -9,27 +8,21 @@ import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
-import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.recipients.RecipientId
-import org.thoughtcrime.securesms.stories.StoryTextPostModel
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.util.rx.RxStore
import kotlin.math.max
class StoryViewerViewModel(
- private val startRecipientId: RecipientId,
- private val onlyIncludeHiddenStories: Boolean,
- storyThumbTextModel: StoryTextPostModel?,
- storyThumbUri: Uri?,
- storyThumbBlur: BlurHash?,
- private val recipientIds: List,
+ private val storyViewerArgs: StoryViewerArgs,
private val repository: StoryViewerRepository,
) : ViewModel() {
private val store = RxStore(
StoryViewerState(
crossfadeSource = when {
- storyThumbTextModel != null -> StoryViewerState.CrossfadeSource.TextModel(storyThumbTextModel)
- storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyThumbUri, storyThumbBlur)
+ storyViewerArgs.storyThumbTextModel != null -> StoryViewerState.CrossfadeSource.TextModel(storyViewerArgs.storyThumbTextModel)
+ storyViewerArgs.storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyViewerArgs.storyThumbUri, storyViewerArgs.storyThumbBlur)
else -> StoryViewerState.CrossfadeSource.None
}
)
@@ -46,10 +39,17 @@ class StoryViewerViewModel(
private val childScrollStatePublisher: MutableLiveData = MutableLiveData(false)
val isChildScrolling: LiveData = childScrollStatePublisher
+ var hasConsumedInitialState = false
+ private set
+
init {
refresh()
}
+ fun consumeInitialState() {
+ hasConsumedInitialState = true
+ }
+
fun setContentIsReady() {
store.update {
it.copy(loadState = it.loadState.copy(isContentReady = true))
@@ -67,10 +67,10 @@ class StoryViewerViewModel(
}
private fun getStories(): Single> {
- return if (recipientIds.isNotEmpty()) {
- Single.just(recipientIds)
+ return if (storyViewerArgs.recipientIds.isNotEmpty()) {
+ Single.just(storyViewerArgs.recipientIds)
} else {
- repository.getStories(onlyIncludeHiddenStories)
+ repository.getStories(storyViewerArgs.isInHiddenStoryMode)
}
}
@@ -148,7 +148,7 @@ class StoryViewerViewModel(
return if (page > -1) {
page
} else {
- val indexOfStartRecipient = recipientIds.indexOf(startRecipientId)
+ val indexOfStartRecipient = recipientIds.indexOf(storyViewerArgs.recipientId)
if (indexOfStartRecipient == -1) {
0
} else {
@@ -162,23 +162,13 @@ class StoryViewerViewModel(
}
class Factory(
- private val startRecipientId: RecipientId,
- private val onlyIncludeHiddenStories: Boolean,
- private val storyThumbTextModel: StoryTextPostModel?,
- private val storyThumbUri: Uri?,
- private val storyThumbBlur: BlurHash?,
- private val recipientIds: List,
+ private val storyViewerArgs: StoryViewerArgs,
private val repository: StoryViewerRepository
) : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
return modelClass.cast(
StoryViewerViewModel(
- startRecipientId,
- onlyIncludeHiddenStories,
- storyThumbTextModel,
- storyThumbUri,
- storyThumbBlur,
- recipientIds,
+ storyViewerArgs,
repository
)
) as T
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt
index 6ff3f28c1c..c7bad611f3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt
@@ -23,7 +23,6 @@ import androidx.core.view.doOnNextLayout
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
-import androidx.lifecycle.LiveDataReactiveStreams
import com.google.android.material.button.MaterialButton
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
@@ -115,6 +114,12 @@ class StoryViewerPageFragment :
private val initialStoryId: Long
get() = requireArguments().getLong(ARG_STORY_ID, -1L)
+ private val isFromNotification: Boolean
+ get() = requireArguments().getBoolean(ARG_IS_FROM_NOTIFICATION, false)
+
+ private val groupReplyStartPosition: Int
+ get() = requireArguments().getInt(ARG_GROUP_REPLY_START_POSITION, -1)
+
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
callback = requireListener()
@@ -249,7 +254,7 @@ class StoryViewerPageFragment :
viewModel.setIsUserScrollingParent(isScrolling)
}
- LiveDataReactiveStreams.fromPublisher(sharedViewModel.state.distinctUntilChanged()).observe(viewLifecycleOwner) { parentState ->
+ lifecycleDisposable += sharedViewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { parentState ->
if (parentState.pages.size <= parentState.page) {
viewModel.setIsSelectedPage(false)
} else if (storyRecipientId == parentState.pages[parentState.page]) {
@@ -270,51 +275,54 @@ class StoryViewerPageFragment :
}
}
- LiveDataReactiveStreams
- .fromPublisher(viewModel.state.observeOn(AndroidSchedulers.mainThread()))
- .observe(viewLifecycleOwner) { state ->
- if (state.posts.isNotEmpty() && state.selectedPostIndex in state.posts.indices) {
- val post = state.posts[state.selectedPostIndex]
+ lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state ->
+ if (state.posts.isNotEmpty() && state.selectedPostIndex in state.posts.indices) {
+ val post = state.posts[state.selectedPostIndex]
- presentViewsAndReplies(post, state.replyState)
- presentSenderAvatar(senderAvatar, post)
- presentGroupAvatar(groupAvatar, post)
- presentFrom(from, post)
- presentDate(date, post)
- presentDistributionList(distributionList, post)
- presentCaption(caption, largeCaption, largeCaptionOverlay, post)
- presentBlur(blurContainer, post)
+ presentViewsAndReplies(post, state.replyState)
+ presentSenderAvatar(senderAvatar, post)
+ presentGroupAvatar(groupAvatar, post)
+ presentFrom(from, post)
+ presentDate(date, post)
+ presentDistributionList(distributionList, post)
+ presentCaption(caption, largeCaption, largeCaptionOverlay, post)
+ presentBlur(blurContainer, post)
- val durations: Map = state.posts
- .mapIndexed { index, storyPost ->
- index to when {
- storyPost.content.isVideo() -> -1L
- storyPost.content is StoryPost.Content.TextContent -> calculateDurationForText(storyPost.content)
- else -> DEFAULT_DURATION
- }
+ val durations: Map = state.posts
+ .mapIndexed { index, storyPost ->
+ index to when {
+ storyPost.content.isVideo() -> -1L
+ storyPost.content is StoryPost.Content.TextContent -> calculateDurationForText(storyPost.content)
+ else -> DEFAULT_DURATION
}
- .toMap()
-
- if (progressBar.segmentCount != state.posts.size || progressBar.segmentDurations != durations) {
- progressBar.segmentCount = state.posts.size
- progressBar.segmentDurations = durations
}
+ .toMap()
- presentStory(post, state.selectedPostIndex)
- presentSlate(post)
-
- if (!storyCrossfader.setTargetView(post.conversationMessage.messageRecord as MmsMessageRecord)) {
- onReadyToAnimate()
- }
-
- viewModel.setAreSegmentsInitialized(true)
- } else if (state.selectedPostIndex >= state.posts.size) {
- callback.onFinishedPosts(storyRecipientId)
- } else if (state.selectedPostIndex < 0) {
- callback.onGoToPreviousStory(storyRecipientId)
+ if (progressBar.segmentCount != state.posts.size || progressBar.segmentDurations != durations) {
+ progressBar.segmentCount = state.posts.size
+ progressBar.segmentDurations = durations
}
+
+ presentStory(post, state.selectedPostIndex)
+ presentSlate(post)
+
+ if (!storyCrossfader.setTargetView(post.conversationMessage.messageRecord as MmsMessageRecord)) {
+ onReadyToAnimate()
+ }
+
+ viewModel.setAreSegmentsInitialized(true)
+ } else if (state.selectedPostIndex >= state.posts.size) {
+ callback.onFinishedPosts(storyRecipientId)
+ } else if (state.selectedPostIndex < 0) {
+ callback.onGoToPreviousStory(storyRecipientId)
}
+ if (state.isDisplayingInitialState && isFromNotification && !sharedViewModel.hasConsumedInitialState) {
+ sharedViewModel.consumeInitialState()
+ startReply(isFromNotification = true, groupReplyStartPosition = groupReplyStartPosition)
+ }
+ }
+
viewModel.storyViewerPlaybackState.observe(viewLifecycleOwner) { state ->
if (state.isPaused) {
pauseProgress()
@@ -484,13 +492,25 @@ class StoryViewerPageFragment :
videoControlsDelegate.pause()
}
- private fun startReply() {
+ private fun startReply(isFromNotification: Boolean = false, groupReplyStartPosition: Int = -1) {
+ val storyPostId: Long = viewModel.getPost().id
val replyFragment: DialogFragment = when (viewModel.getSwipeToReplyState()) {
StoryViewerPageState.ReplyState.NONE -> return
- StoryViewerPageState.ReplyState.SELF -> StoryViewsBottomSheetDialogFragment.create(viewModel.getPost().id)
- StoryViewerPageState.ReplyState.GROUP -> StoryGroupReplyBottomSheetDialogFragment.create(viewModel.getPost().id, viewModel.getPost().group!!.id)
- StoryViewerPageState.ReplyState.PRIVATE -> StoryDirectReplyDialogFragment.create(viewModel.getPost().id)
- StoryViewerPageState.ReplyState.GROUP_SELF -> StoryViewsAndRepliesDialogFragment.create(viewModel.getPost().id, viewModel.getPost().group!!.id, getViewsAndRepliesDialogStartPage())
+ StoryViewerPageState.ReplyState.SELF -> StoryViewsBottomSheetDialogFragment.create(storyPostId)
+ StoryViewerPageState.ReplyState.GROUP -> StoryGroupReplyBottomSheetDialogFragment.create(
+ storyPostId,
+ viewModel.getPost().group!!.id,
+ isFromNotification,
+ groupReplyStartPosition
+ )
+ StoryViewerPageState.ReplyState.PRIVATE -> StoryDirectReplyDialogFragment.create(storyPostId)
+ StoryViewerPageState.ReplyState.GROUP_SELF -> StoryViewsAndRepliesDialogFragment.create(
+ storyPostId,
+ viewModel.getPost().group!!.id,
+ if (isFromNotification) StoryViewsAndRepliesDialogFragment.StartPage.REPLIES else getViewsAndRepliesDialogStartPage(),
+ isFromNotification,
+ groupReplyStartPosition
+ )
}
if (viewModel.getSwipeToReplyState() == StoryViewerPageState.ReplyState.PRIVATE) {
@@ -805,12 +825,16 @@ class StoryViewerPageFragment :
private const val ARG_STORY_RECIPIENT_ID = "arg.story.recipient.id"
private const val ARG_STORY_ID = "arg.story.id"
+ private const val ARG_IS_FROM_NOTIFICATION = "is_from_notification"
+ private const val ARG_GROUP_REPLY_START_POSITION = "group_reply_start_position"
- fun create(recipientId: RecipientId, initialStoryId: Long): Fragment {
+ fun create(recipientId: RecipientId, initialStoryId: Long, isFromNotification: Boolean, groupReplyStartPosition: Int): Fragment {
return StoryViewerPageFragment().apply {
arguments = Bundle().apply {
putParcelable(ARG_STORY_RECIPIENT_ID, recipientId)
putLong(ARG_STORY_ID, initialStoryId)
+ putBoolean(ARG_IS_FROM_NOTIFICATION, isFromNotification)
+ putInt(ARG_GROUP_REPLY_START_POSITION, groupReplyStartPosition)
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt
index d3c1fe6034..52e8f47c69 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageState.kt
@@ -4,7 +4,8 @@ data class StoryViewerPageState(
val posts: List = emptyList(),
val selectedPostIndex: Int = 0,
val replyState: ReplyState = ReplyState.NONE,
- val isFirstPage: Boolean = false
+ val isFirstPage: Boolean = false,
+ val isDisplayingInitialState: Boolean = false
) {
/**
* Indicates which Reply method is available when the user swipes on the dialog
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt
index f9ebe2132e..ffd0162f8e 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt
@@ -49,8 +49,10 @@ class StoryViewerPageViewModel(
disposables.clear()
disposables += repository.getStoryPostsFor(recipientId).subscribe { posts ->
store.update { state ->
+ var isDisplayingInitialState = false
val startIndex = if (state.posts.isEmpty() && initialStoryId > 0) {
val initialIndex = posts.indexOfFirst { it.id == initialStoryId }
+ isDisplayingInitialState = initialIndex > -1
initialIndex.takeIf { it > -1 } ?: state.selectedPostIndex
} else if (state.posts.isEmpty()) {
val initialPost = getNextUnreadPost(posts)
@@ -63,7 +65,8 @@ class StoryViewerPageViewModel(
state.copy(
posts = posts,
replyState = resolveSwipeToReplyState(state, startIndex),
- selectedPostIndex = startIndex
+ selectedPostIndex = startIndex,
+ isDisplayingInitialState = isDisplayingInitialState
)
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt
index 793fe132bc..d403adffb1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyBottomSheetDialogFragment.kt
@@ -34,6 +34,12 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi
private val groupRecipientId: RecipientId
get() = requireArguments().getParcelable(ARG_GROUP_RECIPIENT_ID)!!
+ private val isFromNotification: Boolean
+ get() = requireArguments().getBoolean(ARG_IS_FROM_NOTIFICATION, false)
+
+ private val groupReplyStartPosition: Int
+ get() = requireArguments().getInt(ARG_GROUP_REPLY_START_POSITION, -1)
+
override val peekHeightPercentage: Float = 1f
private val lifecycleDisposable = LifecycleDisposable()
@@ -52,7 +58,7 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi
lifecycleDisposable.bindTo(viewLifecycleOwner)
if (savedInstanceState == null) {
childFragmentManager.beginTransaction()
- .replace(R.id.fragment_container, StoryGroupReplyFragment.create(storyId, groupRecipientId))
+ .replace(R.id.fragment_container, StoryGroupReplyFragment.create(storyId, groupRecipientId, isFromNotification, groupReplyStartPosition))
.commitAllowingStateLoss()
}
@@ -109,12 +115,16 @@ class StoryGroupReplyBottomSheetDialogFragment : FixedRoundedCornerBottomSheetDi
companion object {
private const val ARG_STORY_ID = "arg.story.id"
private const val ARG_GROUP_RECIPIENT_ID = "arg.group.recipient.id"
+ private const val ARG_IS_FROM_NOTIFICATION = "is_from_notification"
+ private const val ARG_GROUP_REPLY_START_POSITION = "group_reply_start_position"
- fun create(storyId: Long, groupRecipientId: RecipientId): DialogFragment {
+ fun create(storyId: Long, groupRecipientId: RecipientId, isFromNotification: Boolean, groupReplyStartPosition: Int): DialogFragment {
return StoryGroupReplyBottomSheetDialogFragment().apply {
arguments = Bundle().apply {
putLong(ARG_STORY_ID, storyId)
putParcelable(ARG_GROUP_RECIPIENT_ID, groupRecipientId)
+ putBoolean(ARG_IS_FROM_NOTIFICATION, isFromNotification)
+ putInt(ARG_GROUP_REPLY_START_POSITION, groupReplyStartPosition)
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt
index 939fc68f7d..48d8c5e5b3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt
@@ -22,16 +22,19 @@ import org.thoughtcrime.securesms.components.emoji.MediaKeyboard
import org.thoughtcrime.securesms.components.mention.MentionAnnotation
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.configure
+import org.thoughtcrime.securesms.conversation.MarkReadHelper
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerFragment
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel
import org.thoughtcrime.securesms.database.model.Mention
+import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob
import org.thoughtcrime.securesms.keyboard.KeyboardPage
import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardCallback
import org.thoughtcrime.securesms.mediasend.v2.UntrustedRecords
+import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -44,7 +47,7 @@ import org.thoughtcrime.securesms.util.DeleteDialog
import org.thoughtcrime.securesms.util.FragmentDialogs.displayInDialogAboveAnchor
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.ServiceUtil
-import org.thoughtcrime.securesms.util.SnapToTopDataObserver
+import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter
import org.thoughtcrime.securesms.util.fragments.findListener
import org.thoughtcrime.securesms.util.fragments.requireListener
@@ -97,10 +100,19 @@ class StoryGroupReplyFragment :
private val groupRecipientId: RecipientId
get() = requireArguments().getParcelable(ARG_GROUP_RECIPIENT_ID)!!
+ private val isFromNotification: Boolean
+ get() = requireArguments().getBoolean(ARG_IS_FROM_NOTIFICATION, false)
+
+ private val groupReplyStartPosition: Int
+ get() = requireArguments().getInt(ARG_GROUP_REPLY_START_POSITION, -1)
+
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: PagingMappingAdapter
- private lateinit var snapToTopDataObserver: SnapToTopDataObserver
+ private lateinit var dataObserver: RecyclerView.AdapterDataObserver
private lateinit var composer: StoryReplyComposer
+
+ private var markReadHelper: MarkReadHelper? = null
+
private var currentChild: StoryViewsAndRepliesPagerParent.Child? = null
private var resendBody: CharSequence? = null
@@ -120,7 +132,7 @@ class StoryGroupReplyFragment :
val emptyNotice: View = requireView().findViewById(R.id.empty_notice)
adapter = PagingMappingAdapter()
- val layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, true)
+ val layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
recyclerView.itemAnimator = null
@@ -131,6 +143,18 @@ class StoryGroupReplyFragment :
onPageSelected(findListener()?.selectedChild ?: StoryViewsAndRepliesPagerParent.Child.REPLIES)
viewModel.state.observe(viewLifecycleOwner) { state ->
+ if (markReadHelper == null && state.threadId > 0L) {
+ if (isResumed) {
+ ApplicationDependencies.getMessageNotifier().setVisibleThread(ConversationId(state.threadId, storyId))
+ }
+
+ markReadHelper = MarkReadHelper(ConversationId(state.threadId, storyId), requireContext(), viewLifecycleOwner)
+
+ if (isFromNotification) {
+ markReadHelper?.onViewsRevealed(System.currentTimeMillis())
+ }
+ }
+
emptyNotice.visible = state.noReplies && state.loadState == StoryGroupReplyState.LoadState.READY
colorizer.onNameColorsChanged(state.nameColors)
}
@@ -139,26 +163,39 @@ class StoryGroupReplyFragment :
adapter.setPagingController(controller)
}
+ var consumed = false
viewModel.pageData.observe(viewLifecycleOwner) { pageData ->
- adapter.submitList(getConfiguration(pageData).toMappingModelList())
+ adapter.submitList(getConfiguration(pageData).toMappingModelList()) {
+ if (!consumed && groupReplyStartPosition >= 0 && adapter.hasItem(groupReplyStartPosition)) {
+ consumed = true
+ recyclerView.post { recyclerView.scrollToPosition(groupReplyStartPosition) }
+ }
+ }
}
- snapToTopDataObserver = SnapToTopDataObserver(
- recyclerView,
- object : SnapToTopDataObserver.ScrollRequestValidator {
- override fun isPositionStillValid(position: Int): Boolean {
- return position >= 0 && position < adapter.itemCount
- }
-
- override fun isItemAtPositionLoaded(position: Int): Boolean {
- return adapter.hasItem(position)
- }
- },
- null
- )
- adapter.registerAdapterDataObserver(snapToTopDataObserver)
+ dataObserver = GroupDataObserver()
+ adapter.registerAdapterDataObserver(dataObserver)
initializeMentions()
+
+ if (savedInstanceState == null && isFromNotification) {
+ ViewUtil.focusAndShowKeyboard(composer)
+ }
+
+ recyclerView.addOnScrollListener(GroupReplyScrollObserver())
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val threadId = viewModel.stateSnapshot.threadId
+ if (threadId != 0L) {
+ ApplicationDependencies.getMessageNotifier().setVisibleThread(ConversationId(threadId, storyId))
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ ApplicationDependencies.getMessageNotifier().setVisibleThread(null)
}
override fun onDestroyView() {
@@ -168,9 +205,23 @@ class StoryGroupReplyFragment :
composer.input.setMentionValidator(null)
}
+ private fun postMarkAsReadRequest() {
+ if (adapter.itemCount == 0 || markReadHelper == null) {
+ return
+ }
+
+ val lastVisibleItem = (recyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
+ val adapterItem = adapter.getItem(lastVisibleItem)
+ if (adapterItem == null || adapterItem !is StoryGroupReplyItem.DataWrapper) {
+ return
+ }
+
+ markReadHelper?.onViewsRevealed(adapterItem.storyGroupReplyItemData.sentAtMillis)
+ }
+
private fun getConfiguration(pageData: List): DSLConfiguration {
return configure {
- pageData.filterNotNull().forEach {
+ pageData.forEach {
when (it.replyBody) {
is StoryGroupReplyItemData.ReplyBody.Text -> {
customPref(
@@ -294,9 +345,6 @@ class StoryGroupReplyFragment :
Toast.makeText(context, R.string.message_details_recipient__failed_to_send, Toast.LENGTH_SHORT).show()
}
}
- },
- onComplete = {
- snapToTopDataObserver.requestScrollPosition(0)
}
)
}
@@ -379,17 +427,47 @@ class StoryGroupReplyFragment :
}
}
+ private inner class GroupReplyScrollObserver : RecyclerView.OnScrollListener() {
+ override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+ postMarkAsReadRequest()
+ }
+
+ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+ postMarkAsReadRequest()
+ }
+ }
+
+ private inner class GroupDataObserver : RecyclerView.AdapterDataObserver() {
+ override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+ if (itemCount == 0) {
+ return
+ }
+
+ val item = adapter.getItem(positionStart)
+ if (positionStart == adapter.itemCount - 1 && item is StoryGroupReplyItem.DataWrapper) {
+ val isOutgoing = item.storyGroupReplyItemData.sender == Recipient.self()
+ if (isOutgoing || (!isOutgoing && !recyclerView.canScrollVertically(1))) {
+ recyclerView.post { recyclerView.scrollToPosition(positionStart) }
+ }
+ }
+ }
+ }
+
companion object {
private val TAG = Log.tag(StoryGroupReplyFragment::class.java)
private const val ARG_STORY_ID = "arg.story.id"
private const val ARG_GROUP_RECIPIENT_ID = "arg.group.recipient.id"
+ private const val ARG_IS_FROM_NOTIFICATION = "is_from_notification"
+ private const val ARG_GROUP_REPLY_START_POSITION = "group_reply_start_position"
- fun create(storyId: Long, groupRecipientId: RecipientId): Fragment {
+ fun create(storyId: Long, groupRecipientId: RecipientId, isFromNotification: Boolean, groupReplyStartPosition: Int): Fragment {
return StoryGroupReplyFragment().apply {
arguments = Bundle().apply {
putLong(ARG_STORY_ID, storyId)
putParcelable(ARG_GROUP_RECIPIENT_ID, groupRecipientId)
+ putBoolean(ARG_IS_FROM_NOTIFICATION, isFromNotification)
+ putInt(ARG_GROUP_REPLY_START_POSITION, groupReplyStartPosition)
}
}
}
@@ -412,9 +490,6 @@ class StoryGroupReplyFragment :
Toast.makeText(context, R.string.message_details_recipient__failed_to_send, Toast.LENGTH_SHORT).show()
}
}
- },
- onComplete = {
- snapToTopDataObserver.requestScrollPosition(0)
}
)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt
index 0460903104..9fd0eddeae 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyItem.kt
@@ -43,13 +43,13 @@ object StoryGroupReplyItem {
}
class TextModel(
- val storyGroupReplyItemData: StoryGroupReplyItemData,
+ override val storyGroupReplyItemData: StoryGroupReplyItemData,
val text: StoryGroupReplyItemData.ReplyBody.Text,
@ColorInt val nameColor: Int,
val onCopyClick: (TextModel) -> Unit,
val onDeleteClick: (TextModel) -> Unit,
val onMentionClick: (RecipientId) -> Unit
- ) : PreferenceModel() {
+ ) : PreferenceModel(), DataWrapper {
override fun areItemsTheSame(newItem: TextModel): Boolean {
return storyGroupReplyItemData.sender == newItem.storyGroupReplyItemData.sender &&
storyGroupReplyItemData.sentAtMillis == newItem.storyGroupReplyItemData.sentAtMillis
@@ -76,11 +76,11 @@ object StoryGroupReplyItem {
}
class RemoteDeleteModel(
- val storyGroupReplyItemData: StoryGroupReplyItemData,
+ override val storyGroupReplyItemData: StoryGroupReplyItemData,
val remoteDelete: StoryGroupReplyItemData.ReplyBody.RemoteDelete,
val onDeleteClick: (RemoteDeleteModel) -> Unit,
@ColorInt val nameColor: Int
- ) : MappingModel {
+ ) : MappingModel, DataWrapper {
override fun areItemsTheSame(newItem: RemoteDeleteModel): Boolean {
return storyGroupReplyItemData.sender == newItem.storyGroupReplyItemData.sender &&
storyGroupReplyItemData.sentAtMillis == newItem.storyGroupReplyItemData.sentAtMillis
@@ -105,10 +105,10 @@ object StoryGroupReplyItem {
}
class ReactionModel(
- val storyGroupReplyItemData: StoryGroupReplyItemData,
+ override val storyGroupReplyItemData: StoryGroupReplyItemData,
val reaction: StoryGroupReplyItemData.ReplyBody.Reaction,
@ColorInt val nameColor: Int
- ) : PreferenceModel() {
+ ) : PreferenceModel(), DataWrapper {
override fun areItemsTheSame(newItem: ReactionModel): Boolean {
return storyGroupReplyItemData.sender == newItem.storyGroupReplyItemData.sender &&
storyGroupReplyItemData.sentAtMillis == newItem.storyGroupReplyItemData.sentAtMillis
@@ -134,6 +134,10 @@ object StoryGroupReplyItem {
}
}
+ interface DataWrapper {
+ val storyGroupReplyItemData: StoryGroupReplyItemData
+ }
+
private abstract class BaseViewHolder(itemView: View) : MappingViewHolder(itemView) {
protected val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
protected val name: FromTextView = itemView.findViewById(R.id.name)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt
index 85c1024904..aca01125a7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyRepository.kt
@@ -13,6 +13,12 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class StoryGroupReplyRepository {
+ fun getThreadId(storyId: Long): Single {
+ return Single.fromCallable {
+ SignalDatabase.mms.getThreadIdForMessage(storyId)
+ }.subscribeOn(Schedulers.io())
+ }
+
fun getPagedReplies(parentStoryId: Long): Observable> {
return Observable.create> { emitter ->
fun refresh() {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyState.kt
index cd7cd1ded5..ef66710fed 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyState.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyState.kt
@@ -4,6 +4,7 @@ import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.recipients.RecipientId
data class StoryGroupReplyState(
+ val threadId: Long = 0L,
val noReplies: Boolean = true,
val nameColors: Map = emptyMap(),
val loadState: LoadState = LoadState.INIT
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyViewModel.kt
index 553f8e6064..49f366d9b1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyViewModel.kt
@@ -21,6 +21,7 @@ class StoryGroupReplyViewModel(storyId: Long, repository: StoryGroupReplyReposit
private val store = Store(StoryGroupReplyState())
private val disposables = CompositeDisposable()
+ val stateSnapshot: StoryGroupReplyState = store.state
val state: LiveData = store.stateLiveData
private val pagedData: MutableLiveData> = MutableLiveData()
@@ -29,6 +30,10 @@ class StoryGroupReplyViewModel(storyId: Long, repository: StoryGroupReplyReposit
val pageData: LiveData>
init {
+ disposables += repository.getThreadId(storyId).subscribe { threadId ->
+ store.update { it.copy(threadId = threadId) }
+ }
+
disposables += repository.getPagedReplies(storyId).subscribe {
pagedData.postValue(it)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt
index cc09411f54..480d3c58c1 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesDialogFragment.kt
@@ -44,6 +44,12 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr
private val startPageIndex: Int
get() = requireArguments().getInt(ARG_START_PAGE)
+ private val isFromNotification: Boolean
+ get() = requireArguments().getBoolean(ARG_IS_FROM_NOTIFICATION, false)
+
+ private val groupReplyStartPosition: Int
+ get() = requireArguments().getInt(ARG_GROUP_REPLY_START_POSITION, -1)
+
override val peekHeightPercentage: Float = 1f
private lateinit var pager: ViewPager2
@@ -84,7 +90,7 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr
val tabs: TabLayout = view.findViewById(R.id.tab_layout)
ViewCompat.setNestedScrollingEnabled(tabs, false)
- pager.adapter = StoryViewsAndRepliesPagerAdapter(this, storyId, groupRecipientId)
+ pager.adapter = StoryViewsAndRepliesPagerAdapter(this, storyId, groupRecipientId, isFromNotification, groupReplyStartPosition)
pager.setCurrentItem(startPageIndex, false)
TabLayoutMediator(tabs, pager) { tab, position ->
@@ -168,13 +174,17 @@ class StoryViewsAndRepliesDialogFragment : FixedRoundedCornerBottomSheetDialogFr
private const val ARG_STORY_ID = "arg.story.id"
private const val ARG_START_PAGE = "arg.start.page"
private const val ARG_GROUP_RECIPIENT_ID = "arg.group.recipient.id"
+ private const val ARG_IS_FROM_NOTIFICATION = "is_from_notification"
+ private const val ARG_GROUP_REPLY_START_POSITION = "group_reply_start_position"
- fun create(storyId: Long, groupRecipientId: RecipientId, startPage: StartPage): DialogFragment {
+ fun create(storyId: Long, groupRecipientId: RecipientId, startPage: StartPage, isFromNotification: Boolean, groupReplyStartPosition: Int): DialogFragment {
return StoryViewsAndRepliesDialogFragment().apply {
arguments = Bundle().apply {
putLong(ARG_STORY_ID, storyId)
putInt(ARG_START_PAGE, startPage.index)
putParcelable(ARG_GROUP_RECIPIENT_ID, groupRecipientId)
+ putBoolean(ARG_IS_FROM_NOTIFICATION, isFromNotification)
+ putInt(ARG_GROUP_REPLY_START_POSITION, groupReplyStartPosition)
}
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesPagerAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesPagerAdapter.kt
index 0001eebf9d..f04634a0b3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesPagerAdapter.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/tabs/StoryViewsAndRepliesPagerAdapter.kt
@@ -10,7 +10,9 @@ import org.thoughtcrime.securesms.stories.viewer.views.StoryViewsFragment
class StoryViewsAndRepliesPagerAdapter(
fragment: Fragment,
private val storyId: Long,
- private val groupRecipientId: RecipientId
+ private val groupRecipientId: RecipientId,
+ private val isFromNotification: Boolean,
+ private val groupReplyStartPosition: Int
) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 2
@@ -22,7 +24,7 @@ class StoryViewsAndRepliesPagerAdapter(
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> StoryViewsFragment.create(storyId)
- 1 -> StoryGroupReplyFragment.create(storyId, groupRecipientId)
+ 1 -> StoryGroupReplyFragment.create(storyId, groupRecipientId, isFromNotification, groupReplyStartPosition)
else -> throw IndexOutOfBoundsException()
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java
index 012c6ca39b..4e4c392547 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java
@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationIds;
import org.thoughtcrime.securesms.notifications.v2.NotificationFactory;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -76,12 +76,12 @@ public final class BubbleUtil {
*/
public static void displayAsBubble(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) {
if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION) {
- NotificationThread notificationThread = NotificationThread.forConversation(threadId);
+ ConversationId conversationId = ConversationId.forConversation(threadId);
SignalExecutors.BOUNDED.execute(() -> {
if (canBubble(context, recipientId, threadId)) {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
- int threadNotificationId = NotificationIds.getNotificationIdForThread(notificationThread);
+ int threadNotificationId = NotificationIds.getNotificationIdForThread(conversationId);
Notification activeThreadNotification = Stream.of(notifications)
.filter(n -> n.getId() == threadNotificationId)
.findFirst()
@@ -89,7 +89,7 @@ public final class BubbleUtil {
.orElse(null);
if (activeThreadNotification != null && activeThreadNotification.deleteIntent != null) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, notificationThread, BubbleState.SHOWN);
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, conversationId, BubbleState.SHOWN);
} else {
Recipient recipient = Recipient.resolved(recipientId);
NotificationFactory.notifyToBubbleConversation(context, recipient, threadId);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java
index 7c396365ba..bfe554e567 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
+import org.thoughtcrime.securesms.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage;
@@ -141,7 +141,7 @@ public final class IdentityUtil {
Optional insertResult = smsDatabase.insertMessageInbox(individualUpdate);
if (insertResult.isPresent()) {
- ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId()));
+ ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(insertResult.get().getThreadId()));
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java
index 3b624dabee..f4dd63b666 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java
@@ -33,7 +33,7 @@ public class PagingMappingAdapter extends MappingAdapter {
}
@Override
- protected @Nullable MappingModel> getItem(int position) {
+ public @Nullable MappingModel> getItem(int position) {
if (pagingController != null) {
pagingController.onDataNeededAroundIndex(position);
}
diff --git a/app/src/main/res/layout/stories_group_replies_fragment.xml b/app/src/main/res/layout/stories_group_replies_fragment.xml
index 13cf9644f6..949c32e2a5 100644
--- a/app/src/main/res/layout/stories_group_replies_fragment.xml
+++ b/app/src/main/res/layout/stories_group_replies_fragment.xml
@@ -22,6 +22,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
+ android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/composer"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/stories_views_fragment.xml b/app/src/main/res/layout/stories_views_fragment.xml
index f9b036a969..479da1b1b3 100644
--- a/app/src/main/res/layout/stories_views_fragment.xml
+++ b/app/src/main/res/layout/stories_views_fragment.xml
@@ -22,6 +22,7 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
+ android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 83ae54ba9f..136723428e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1952,6 +1952,8 @@
New message
Message request
You
+
+ %1$s • Story
Play video
diff --git a/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt
index 8585510b16..a2ef1ceea3 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerViewModelTest.kt
@@ -14,6 +14,7 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.thoughtcrime.securesms.recipients.RecipientId
+import org.thoughtcrime.securesms.stories.StoryViewerArgs
class StoryViewerViewModelTest {
private val testScheduler = TestScheduler()
@@ -39,7 +40,14 @@ class StoryViewerViewModelTest {
val injectedStories: List = (6L..10L).map(RecipientId::from)
// WHEN
- val testSubject = StoryViewerViewModel(injectedStories.first(), false, null, null, null, injectedStories, repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = injectedStories.first(),
+ isInHiddenStoryMode = false,
+ recipientIds = injectedStories
+ ),
+ repository
+ )
testScheduler.triggerActions()
// THEN
@@ -55,7 +63,13 @@ class StoryViewerViewModelTest {
whenever(repository.getStories(any())).doReturn(Single.just(stories))
// WHEN
- val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = startStory,
+ isInHiddenStoryMode = false
+ ),
+ repository
+ )
testScheduler.triggerActions()
// THEN
@@ -71,7 +85,13 @@ class StoryViewerViewModelTest {
val stories: List = (1L..5L).map(RecipientId::from)
val startStory = RecipientId.from(1L)
whenever(repository.getStories(any())).doReturn(Single.just(stories))
- val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = startStory,
+ isInHiddenStoryMode = false,
+ ),
+ repository
+ )
testScheduler.triggerActions()
// WHEN
@@ -91,7 +111,13 @@ class StoryViewerViewModelTest {
val stories: List = (1L..5L).map(RecipientId::from)
val startStory = stories.last()
whenever(repository.getStories(any())).doReturn(Single.just(stories))
- val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = startStory,
+ isInHiddenStoryMode = false,
+ ),
+ repository
+ )
testScheduler.triggerActions()
// WHEN
@@ -111,7 +137,13 @@ class StoryViewerViewModelTest {
val stories: List = (1L..5L).map(RecipientId::from)
val startStory = stories.last()
whenever(repository.getStories(any())).doReturn(Single.just(stories))
- val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = startStory,
+ isInHiddenStoryMode = false,
+ ),
+ repository
+ )
testScheduler.triggerActions()
// WHEN
@@ -131,7 +163,13 @@ class StoryViewerViewModelTest {
val stories: List = (1L..5L).map(RecipientId::from)
val startStory = stories.first()
whenever(repository.getStories(any())).doReturn(Single.just(stories))
- val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = startStory,
+ isInHiddenStoryMode = false,
+ ),
+ repository
+ )
testScheduler.triggerActions()
// WHEN
@@ -151,7 +189,13 @@ class StoryViewerViewModelTest {
val stories: List = (1L..5L).map(RecipientId::from)
val startStory = stories.first()
whenever(repository.getStories(any())).doReturn(Single.just(stories))
- val testSubject = StoryViewerViewModel(startStory, false, null, null, null, emptyList(), repository)
+ val testSubject = StoryViewerViewModel(
+ StoryViewerArgs(
+ recipientId = startStory,
+ isInHiddenStoryMode = false,
+ ),
+ repository
+ )
testScheduler.triggerActions()
// WHEN