Implement group story notifications.
This commit is contained in:
parent
01543dd52b
commit
a03c49e12c
66 changed files with 865 additions and 473 deletions
|
@ -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">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.thoughtcrime.securesms.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
|
||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.thoughtcrime.securesms.blurhash;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
@ -9,7 +12,7 @@ import java.util.Objects;
|
|||
* A BlurHash is a compact string representation of a blurred image that we can use to show fast
|
||||
* image previews.
|
||||
*/
|
||||
public class BlurHash {
|
||||
public class BlurHash implements Parcelable {
|
||||
|
||||
private final String hash;
|
||||
|
||||
|
@ -17,6 +20,20 @@ public class BlurHash {
|
|||
this.hash = hash;
|
||||
}
|
||||
|
||||
protected BlurHash(Parcel in) {
|
||||
hash = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static @Nullable BlurHash parseOrNull(@Nullable String hash) {
|
||||
if (Base83.isValid(hash)) {
|
||||
return new BlurHash(hash);
|
||||
|
@ -40,4 +57,16 @@ public class BlurHash {
|
|||
public int hashCode() {
|
||||
return Objects.hash(hash);
|
||||
}
|
||||
|
||||
public static final Creator<BlurHash> CREATOR = new Creator<BlurHash>() {
|
||||
@Override
|
||||
public BlurHash createFromParcel(Parcel in) {
|
||||
return new BlurHash(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlurHash[] newArray(int size) {
|
||||
return new BlurHash[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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)")
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<MessageDatabase.MarkedMessageInfo> infos = threadDatabase.setReadSince(threadId, false, timestamp);
|
||||
List<MessageDatabase.MarkedMessageInfo> infos = threadDatabase.setReadSince(conversationId, false, timestamp);
|
||||
|
||||
Log.d(TAG, "Marking " + infos.size() + " messages as read.");
|
||||
|
||||
|
|
|
@ -197,6 +197,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
|||
public abstract @NonNull List<RecipientId> 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<MarkedMessageInfo> setGroupStoryMessagesReadSince(long threadId, long groupStoryId, long sinceTimestamp);
|
||||
|
||||
public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId);
|
||||
public abstract void updateViewedStories(@NonNull Set<SyncMessageId> syncMessageIds);
|
||||
|
|
|
@ -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<MarkedMessageInfo> 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<MarkedMessageInfo> setEntireThreadRead(long threadId) {
|
||||
return setMessagesRead(THREAD_ID + " = ? AND " + STORY_TYPE + " = 0 AND " + PARENT_STORY_ID + " <= 0", new String[] { String.valueOf(threadId)});
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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<MarkedMessageInfo> setGroupStoryMessagesReadSince(long threadId, long groupStoryId, long sinceTimestamp) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteGroupStoryReplies(long parentStoryId) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -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<MarkedMessageInfo> 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<MarkedMessageInfo> 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<MarkedMessageInfo> 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<MarkedMessageInfo> setGroupStoryReadSince(long threadId, long groupStoryId, long sinceTimestamp) {
|
||||
return SignalDatabase.mms().setGroupStoryMessagesReadSince(threadId, groupStoryId, sinceTimestamp);
|
||||
}
|
||||
|
||||
public List<MarkedMessageInfo> setReadSince(Map<Long, Long> threadIdToSinceTimestamp, boolean lastSeen) {
|
||||
SQLiteDatabase db = databaseHelper.getSignalWritableDatabase();
|
||||
|
||||
|
@ -1252,7 +1273,7 @@ public class ThreadDatabase extends Database {
|
|||
|
||||
pinnedPosition++;
|
||||
}
|
||||
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
|
|
@ -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> 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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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> 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.");
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<NotificationThread> threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
|
||||
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
|
||||
final ArrayList<ConversationId> threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
|
||||
|
||||
if (threads != null) {
|
||||
for (NotificationThread thread : threads) {
|
||||
for (ConversationId thread : threads) {
|
||||
notifier.removeStickyThread(thread);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<NotificationThread> threads = intent.getParcelableArrayListExtra(THREADS_EXTRA);
|
||||
final ArrayList<ConversationId> 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<MarkedMessageInfo> messageIdsCollection = new LinkedList<>();
|
||||
|
||||
for (NotificationThread thread : threads) {
|
||||
for (ConversationId thread : threads) {
|
||||
Log.i(TAG, "Marking as read: " + thread);
|
||||
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread.getThreadId(), true);
|
||||
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread, true);
|
||||
messageIdsCollection.addAll(messageIds);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<NotificationThread> getVisibleThread();
|
||||
void setVisibleThread(@Nullable ConversationId conversationId);
|
||||
@NonNull Optional<ConversationId> 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 {
|
||||
|
|
|
@ -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<NotificationThread> 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<ConversationId> 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.");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<NotificationThread> getVisibleThread() {
|
||||
public @NonNull Optional<ConversationId> 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) {
|
||||
|
|
|
@ -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<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(threadId, true);
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<NotificationThread, Reminder> = ConcurrentHashMap()
|
||||
private val stickyThreads: MutableMap<NotificationThread, StickyThread> = mutableMapOf()
|
||||
private val threadReminders: MutableMap<ConversationId, Reminder> = ConcurrentHashMap()
|
||||
private val stickyThreads: MutableMap<ConversationId, StickyThread> = 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<NotificationThread> {
|
||||
override fun getVisibleThread(): Optional<ConversationId> {
|
||||
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<Int>? = ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrNull()
|
||||
if (displayedNotifications != null) {
|
||||
val cleanedUpThreads: MutableSet<NotificationThread> = mutableSetOf()
|
||||
val cleanedUpThreads: MutableSet<ConversationId> = 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<NotificationThread> = state.getThreadsWithMostRecentNotificationFromSelf()
|
||||
val retainStickyThreadIds: Set<ConversationId> = state.getThreadsWithMostRecentNotificationFromSelf()
|
||||
stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) }
|
||||
|
||||
if (state.isEmpty) {
|
||||
|
@ -190,13 +190,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
|||
return
|
||||
}
|
||||
|
||||
val alertOverrides: Set<NotificationThread> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
|
||||
val alertOverrides: Set<ConversationId> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
|
||||
|
||||
val threadsThatAlerted: Set<NotificationThread> = NotificationFactory.notify(
|
||||
val threadsThatAlerted: Set<ConversationId> = 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<NotificationThread>, threadsThatAlerted: Set<NotificationThread>) {
|
||||
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<ConversationId>, threadsThatAlerted: Set<ConversationId>) {
|
||||
if (SignalStore.settings().messageNotificationsRepeatAlerts == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val iterator: MutableIterator<MutableEntry<NotificationThread, Reminder>> = threadReminders.iterator()
|
||||
val iterator: MutableIterator<MutableEntry<ConversationId, Reminder>> = threadReminders.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val entry: MutableEntry<NotificationThread, Reminder> = iterator.next()
|
||||
val (id: NotificationThread, reminder: Reminder) = entry
|
||||
val entry: MutableEntry<ConversationId, Reminder> = 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<DelayedNotification> = 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
|
||||
|
||||
|
|
|
@ -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<NotificationItemV2>
|
||||
) {
|
||||
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()})"
|
||||
}
|
||||
|
|
|
@ -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<NotificationThread>,
|
||||
alertOverrides: Set<ConversationId>,
|
||||
previousState: NotificationStateV2
|
||||
): Set<NotificationThread> {
|
||||
): Set<ConversationId> {
|
||||
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<NotificationThread>,
|
||||
alertOverrides: Set<ConversationId>,
|
||||
nonVisibleThreadCount: Int
|
||||
): Set<NotificationThread> {
|
||||
val threadsThatNewlyAlerted: MutableSet<NotificationThread> = mutableSetOf()
|
||||
): Set<ConversationId> {
|
||||
val threadsThatNewlyAlerted: MutableSet<ConversationId> = 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<NotificationThread>,
|
||||
alertOverrides: Set<ConversationId>,
|
||||
nonVisibleThreadCount: Int,
|
||||
previousState: NotificationStateV2
|
||||
): Set<NotificationThread> {
|
||||
val threadsThatNewlyAlerted: MutableSet<NotificationThread> = mutableSetOf()
|
||||
): Set<ConversationId> {
|
||||
val threadsThatNewlyAlerted: MutableSet<ConversationId> = 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,
|
||||
|
|
|
@ -40,7 +40,7 @@ private const val MAX_DISPLAY_LENGTH = 500
|
|||
sealed class NotificationItemV2(val threadRecipient: Recipient, protected val record: MessageRecord) : Comparable<NotificationItemV2> {
|
||||
|
||||
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
|
||||
|
|
|
@ -21,7 +21,7 @@ object NotificationStateProvider {
|
|||
private val TAG = Log.tag(NotificationStateProvider::class.java)
|
||||
|
||||
@WorkerThread
|
||||
fun constructNotificationState(stickyThreads: Map<NotificationThread, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
|
||||
fun constructNotificationState(stickyThreads: Map<ConversationId, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
|
||||
val messages: MutableList<NotificationMessage> = 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<ReactionRecord>,
|
||||
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())) {
|
||||
|
|
|
@ -39,18 +39,18 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
|||
val mostRecentSender: Recipient?
|
||||
get() = mostRecentNotification?.individualRecipient
|
||||
|
||||
fun getNonVisibleConversation(visibleThread: NotificationThread?): List<NotificationConversation> {
|
||||
fun getNonVisibleConversation(visibleThread: ConversationId?): List<NotificationConversation> {
|
||||
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<NotificationThread> = mutableListOf()
|
||||
val threads: MutableList<ConversationId> = mutableListOf()
|
||||
|
||||
conversations.forEach { conversation ->
|
||||
threads += conversation.thread
|
||||
|
@ -79,7 +79,7 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
|||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
fun getThreadsWithMostRecentNotificationFromSelf(): Set<NotificationThread> {
|
||||
fun getThreadsWithMostRecentNotificationFromSelf(): Set<ConversationId> {
|
||||
return conversations.filter { it.mostRecentNotification.individualRecipient.isSelf }
|
||||
.map { it.thread }
|
||||
.toSet()
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<WebRtcEphemeralState, WebRtcEph
|
|||
peekInfo.getJoinedMembers(),
|
||||
WebRtcUtil.isCallFull(peekInfo));
|
||||
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(threadId), true, 0, BubbleUtil.BubbleState.HIDDEN);
|
||||
ApplicationDependencies.getMessageNotifier().updateNotification(context, ConversationId.forConversation(threadId), true, 0, BubbleUtil.BubbleState.HIDDEN);
|
||||
|
||||
EventBus.getDefault().postSticky(new GroupCallPeekEvent(id, peekInfo.getEraId(), peekInfo.getDeviceCount(), peekInfo.getMaxDevices()));
|
||||
}
|
||||
|
@ -836,14 +836,14 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
|
|||
Pair<Long, Long> 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<Long, Long> 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) {
|
||||
|
|
|
@ -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<RecipientId> = 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<RecipientId> = 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<RecipientId>): 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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<RecipientId> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RecipientId>
|
||||
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<RecipientId> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RecipientId> = 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(
|
||||
|
|
|
@ -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<RecipientId>,
|
||||
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<Boolean> = MutableLiveData(false)
|
||||
val isChildScrolling: LiveData<Boolean> = 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<List<RecipientId>> {
|
||||
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<RecipientId>,
|
||||
private val storyViewerArgs: StoryViewerArgs,
|
||||
private val repository: StoryViewerRepository
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(
|
||||
StoryViewerViewModel(
|
||||
startRecipientId,
|
||||
onlyIncludeHiddenStories,
|
||||
storyThumbTextModel,
|
||||
storyThumbUri,
|
||||
storyThumbBlur,
|
||||
recipientIds,
|
||||
storyViewerArgs,
|
||||
repository
|
||||
)
|
||||
) as T
|
||||
|
|
|
@ -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<Int, Long> = 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<Int, Long> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ data class StoryViewerPageState(
|
|||
val posts: List<StoryPost> = 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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<StoryGroupReplyItemData.Key>
|
||||
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<StoryGroupReplyItemData.Key>()
|
||||
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<StoryViewsAndRepliesPagerParent>()?.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<StoryGroupReplyItemData>): 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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<TextModel>() {
|
||||
) : PreferenceModel<TextModel>(), 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<RemoteDeleteModel> {
|
||||
) : MappingModel<RemoteDeleteModel>, 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<ReactionModel>() {
|
||||
) : PreferenceModel<ReactionModel>(), 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<T>(itemView: View) : MappingViewHolder<T>(itemView) {
|
||||
protected val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||
protected val name: FromTextView = itemView.findViewById(R.id.name)
|
||||
|
|
|
@ -13,6 +13,12 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
|||
|
||||
class StoryGroupReplyRepository {
|
||||
|
||||
fun getThreadId(storyId: Long): Single<Long> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.mms.getThreadIdForMessage(storyId)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun getPagedReplies(parentStoryId: Long): Observable<LivePagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> {
|
||||
return Observable.create<LivePagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> { emitter ->
|
||||
fun refresh() {
|
||||
|
|
|
@ -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<RecipientId, NameColor> = emptyMap(),
|
||||
val loadState: LoadState = LoadState.INIT
|
||||
|
|
|
@ -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<StoryGroupReplyState> = store.stateLiveData
|
||||
|
||||
private val pagedData: MutableLiveData<LivePagedData<StoryGroupReplyItemData.Key, StoryGroupReplyItemData>> = MutableLiveData()
|
||||
|
@ -29,6 +30,10 @@ class StoryGroupReplyViewModel(storyId: Long, repository: StoryGroupReplyReposit
|
|||
val pageData: LiveData<List<StoryGroupReplyItemData>>
|
||||
|
||||
init {
|
||||
disposables += repository.getThreadId(storyId).subscribe { threadId ->
|
||||
store.update { it.copy(threadId = threadId) }
|
||||
}
|
||||
|
||||
disposables += repository.getPagedReplies(storyId).subscribe {
|
||||
pagedData.postValue(it)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ public class PagingMappingAdapter<Key> extends MappingAdapter {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable MappingModel<?> getItem(int position) {
|
||||
public @Nullable MappingModel<?> getItem(int position) {
|
||||
if (pagingController != null) {
|
||||
pagingController.onDataNeededAroundIndex(position);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1952,6 +1952,8 @@
|
|||
<string name="SingleRecipientNotificationBuilder_new_message">New message</string>
|
||||
<string name="SingleRecipientNotificationBuilder_message_request">Message request</string>
|
||||
<string name="SingleRecipientNotificationBuilder_you">You</string>
|
||||
<!-- Notification subtext for group stories -->
|
||||
<string name="SingleRecipientNotificationBuilder__s_dot_story">%1$s • Story</string>
|
||||
|
||||
<!-- ThumbnailView -->
|
||||
<string name="ThumbnailView_Play_video_description">Play video</string>
|
||||
|
|
|
@ -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<RecipientId> = (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<RecipientId> = (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<RecipientId> = (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<RecipientId> = (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<RecipientId> = (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<RecipientId> = (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
|
||||
|
|
Loading…
Add table
Reference in a new issue