From 30fc6d94c5aebbe48ed26113ee2e459d10f24a32 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 21 Apr 2023 12:57:56 -0300 Subject: [PATCH] Flesh out event listeners and add load sequencing to CFV2. --- .../securesms/BindableConversationItem.java | 2 +- .../components/ScrollToPositionDelegate.kt | 20 +- .../conversation/ConversationAdapter.java | 14 +- .../conversation/ConversationDataSource.java | 17 +- .../conversation/ConversationFragment.java | 57 +-- .../conversation/ConversationItem.java | 2 +- .../conversation/ConversationMessage.java | 37 +- .../ConversationParentFragment.java | 11 +- .../conversation/ConversationRepository.java | 2 +- .../ScheduledMessagesBottomSheet.kt | 25 +- .../ScheduledMessagesRepository.kt | 4 +- .../conversation/drafts/DraftRepository.kt | 8 +- .../forward/MultiselectForwardFragmentArgs.kt | 2 +- .../quotes/MessageQuotesBottomSheet.kt | 2 +- .../quotes/MessageQuotesRepository.kt | 5 +- .../ui/edit/EditMessageHistoryDialog.kt | 5 +- .../ui/edit/EditMessageHistoryRepository.kt | 9 +- .../conversation/v2/ConversationDialogs.kt | 92 +++++ .../conversation/v2/ConversationFragment.kt | 331 +++++++++++------- .../conversation/v2/ConversationRepository.kt | 8 +- .../conversation/v2/ConversationViewModel.kt | 9 +- .../longmessage/LongMessageResolveer.kt | 7 +- .../MessageDetailsRepository.java | 3 +- .../landing/StoriesLandingRepository.kt | 4 +- .../stories/my/MyStoriesRepository.kt | 2 +- .../viewer/page/StoryViewerPageRepository.kt | 2 +- .../reply/group/StoryGroupReplyDataSource.kt | 4 +- .../res/layout/conversation_input_panel.xml | 3 +- 28 files changed, 423 insertions(+), 264 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index 4bc690f879..e706c041a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -108,7 +108,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, void onInviteToSignalClicked(); void onActivatePaymentsClicked(); void onSendPaymentClicked(@NonNull RecipientId recipientId); - void onScheduledIndicatorClicked(@NonNull View view, @NonNull MessageRecord messageRecord); + void onScheduledIndicatorClicked(@NonNull View view, @NonNull ConversationMessage conversationMessage); /** @return true if handled, false if you want to let the normal url handling continue */ boolean onUrlClicked(@NonNull String url); void onViewGiftBadgeClicked(@NonNull MessageRecord messageRecord); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ScrollToPositionDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ScrollToPositionDelegate.kt index 7d9c60b79d..8cb202481a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ScrollToPositionDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ScrollToPositionDelegate.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.components import android.view.View +import androidx.annotation.AnyThread import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -36,7 +37,6 @@ class ScrollToPositionDelegate private constructor( private val EMPTY = ScrollToPositionRequest( position = NO_POSITION, smooth = true, - awaitLayout = true, scrollStrategy = DefaultScrollStrategy ) } @@ -57,14 +57,8 @@ class ScrollToPositionDelegate private constructor( .filter { it.position >= 0 && canJumpToPosition(it.position) } .map { it.copy(position = mapToTruePosition(it.position)) } .subscribeBy(onNext = { position -> - if (position.awaitLayout) { - recyclerView.doAfterNextLayout { - handleScrollPositionRequest(position, recyclerView) - } - } else { - recyclerView.post { - handleScrollPositionRequest(position, recyclerView) - } + recyclerView.doAfterNextLayout { + handleScrollPositionRequest(position, recyclerView) } if (!(recyclerView.isLayoutRequested || recyclerView.isInLayout)) { @@ -78,21 +72,21 @@ class ScrollToPositionDelegate private constructor( * * @param position The desired position to jump to. -1 to clear the current request. * @param smooth Whether a smooth scroll will be attempted. Only done if we are within a certain distance. - * @param awaitLayout Whether this scroll should await for the next layout to complete before being attempted. * @param scrollStrategy See [ScrollStrategy] */ + @AnyThread fun requestScrollPosition( position: Int, smooth: Boolean = true, - awaitLayout: Boolean = true, scrollStrategy: ScrollStrategy = DefaultScrollStrategy ) { - scrollPositionRequested.onNext(ScrollToPositionRequest(position, smooth, awaitLayout, scrollStrategy)) + scrollPositionRequested.onNext(ScrollToPositionRequest(position, smooth, scrollStrategy)) } /** * Reset the scroll position to 0 */ + @AnyThread fun resetScrollPosition() { requestScrollPosition(0, true) } @@ -100,6 +94,7 @@ class ScrollToPositionDelegate private constructor( /** * This should be called every time a list is submitted to the RecyclerView's adapter. */ + @AnyThread fun notifyListCommitted() { listCommitted.onNext(Unit) } @@ -135,7 +130,6 @@ class ScrollToPositionDelegate private constructor( private data class ScrollToPositionRequest( val position: Int, val smooth: Boolean, - val awaitLayout: Boolean, val scrollStrategy: ScrollStrategy ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index c73ca5afbd..d10572c124 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -105,8 +105,6 @@ public class ConversationAdapter private final LifecycleOwner lifecycleOwner; private final GlideRequests glideRequests; private final Locale locale; - private final Recipient recipient; - private final Set selected; private final Calendar calendar; @@ -129,7 +127,7 @@ public class ConversationAdapter @NonNull GlideRequests glideRequests, @NonNull Locale locale, @Nullable ItemClickListener clickListener, - @NonNull Recipient recipient, + boolean hasWallpaper, @NonNull Colorizer colorizer) { super(new DiffUtil.ItemCallback() { @@ -150,10 +148,9 @@ public class ConversationAdapter this.glideRequests = glideRequests; this.locale = locale; this.clickListener = clickListener; - this.recipient = recipient; this.selected = new HashSet<>(); this.calendar = Calendar.getInstance(); - this.hasWallpaper = recipient.hasWallpaper(); + this.hasWallpaper = hasWallpaper; this.isMessageRequestAccepted = true; this.colorizer = colorizer; } @@ -292,7 +289,7 @@ public class ConversationAdapter glideRequests, locale, selected, - recipient, + conversationMessage.getThreadRecipient(), searchQuery, conversationMessage == recordToPulse, hasWallpaper && displayMode.displayWallpaper(), @@ -440,7 +437,8 @@ public class ConversationAdapter } public boolean isForRecipientId(@NonNull RecipientId recipientId) { - return recipient.getId().equals(recipientId); + // TODO [alex] -- This should be fine, since we now have a 1:1 relationship between fragment and recipient. + return true; } void onBindLastSeenViewHolder(StickyHeaderViewHolder viewHolder, long unreadCount) { @@ -562,7 +560,7 @@ public class ConversationAdapter * Lets the adapter know that the wallpaper state has changed. * @return True if the internal wallpaper state changed, otherwise false. */ - boolean onHasWallpaperChanged(boolean hasWallpaper) { + public boolean onHasWallpaperChanged(boolean hasWallpaper) { if (this.hasWallpaper != hasWallpaper) { Log.d(TAG, "Resetting adapter due to wallpaper change."); this.hasWallpaper = hasWallpaper; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java index bc1f3cdb3c..d80956f190 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java @@ -39,6 +39,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -58,6 +59,8 @@ public class ConversationDataSource implements PagedDataSource messages = Stream.of(records) - .map(m -> ConversationMessageFactory.createWithUnresolvedData(context, m, m.getDisplayBody(context), mentionHelper.getMentions(m.getId()), quotedHelper.isQuoted(m.getId()))) + .map(m -> ConversationMessageFactory.createWithUnresolvedData(context, m, m.getDisplayBody(context), mentionHelper.getMentions(m.getId()), quotedHelper.isQuoted(m.getId()), threadRecipient)) .toList(); stopwatch.split("conversion"); @@ -229,11 +234,13 @@ public class ConversationDataSource implements PagedDataSource records = new LinkedList<>(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index f9bfcf5d39..2d24be0805 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -112,6 +112,7 @@ import org.thoughtcrime.securesms.conversation.ui.edit.EditMessageHistoryDialog; import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog; import org.thoughtcrime.securesms.conversation.v2.AddToContactsContract; import org.thoughtcrime.securesms.conversation.v2.BubbleLayoutTransitionListener; +import org.thoughtcrime.securesms.conversation.v2.ConversationDialogs; import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -712,7 +713,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } Log.d(TAG, "Initializing adapter for " + recipient.getId()); - ConversationAdapter adapter = new ConversationAdapter(requireContext(), this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get(), colorizer); + ConversationAdapter adapter = new ConversationAdapter(requireContext(), this, GlideApp.with(this), locale, selectionClickListener, this.recipient.get().hasWallpaper(), colorizer); adapter.setPagingController(conversationViewModel.getPagingController()); list.setAdapter(adapter); setInlineDateDecoration(adapter); @@ -1016,7 +1017,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect try (InputStream stream = PartAuthority.getAttachmentStream(requireContext(), textSlide.getUri())) { String body = StreamUtil.readFullyAsString(stream); - return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.getMessageRecord(), body) + return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.getMessageRecord(), body, message.getThreadRecipient()) .getDisplayBody(requireContext()); } catch (IOException e) { Log.w(TAG, "Failed to read text slide data."); @@ -1911,16 +1912,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void onChatSessionRefreshLearnMoreClicked() { - new AlertDialog.Builder(requireContext()) - .setView(R.layout.decryption_failed_dialog) - .setPositiveButton(android.R.string.ok, (d, w) -> { - d.dismiss(); - }) - .setNeutralButton(R.string.ConversationFragment_contact_us, (d, w) -> { - startActivity(AppSettingsActivity.help(requireContext(), 0)); - d.dismiss(); - }) - .show(); + ConversationDialogs.INSTANCE.displayChatSessionRefreshLearnMoreDialog(requireContext()); } @Override @@ -1932,34 +1924,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient) { - if (recipient.isGroup()) { - throw new AssertionError("Must be individual"); - } - - AlertDialog dialog = new AlertDialog.Builder(requireContext()) - .setView(R.layout.safety_number_changed_learn_more_dialog) - .setPositiveButton(R.string.ConversationFragment_verify, (d, w) -> { - SimpleTask.run(getLifecycle(), () -> { - return ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(recipient.getId()); - }, identityRecord -> { - if (identityRecord.isPresent()) { - startActivity(VerifyIdentityActivity.newIntent(requireContext(), identityRecord.get())); - }}); - d.dismiss(); - }) - .setNegativeButton(R.string.ConversationFragment_not_now, (d, w) -> { - d.dismiss(); - }) - .create(); - dialog.setOnShowListener(d -> { - TextView title = Objects.requireNonNull(dialog.findViewById(R.id.safety_number_learn_more_title)); - TextView body = Objects.requireNonNull(dialog.findViewById(R.id.safety_number_learn_more_body)); - - title.setText(getString(R.string.ConversationFragment_your_safety_number_with_s_changed, recipient.getDisplayName(requireContext()))); - body.setText(getString(R.string.ConversationFragment_your_safety_number_with_s_changed_likey_because_they_reinstalled_signal, recipient.getDisplayName(requireContext()))); - }); - - dialog.show(); + ConversationDialogs.INSTANCE.displaySafetyNumberLearnMoreDialog(ConversationFragment.this, recipient); } @Override public void onJoinGroupCallClicked() { @@ -1988,15 +1953,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect @Override public void onInMemoryMessageClicked(@NonNull InMemoryMessageRecord messageRecord) { - if (messageRecord instanceof InMemoryMessageRecord.NoGroupsInCommon) { - boolean isGroup = ((InMemoryMessageRecord.NoGroupsInCommon) messageRecord).isGroup(); - new MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Signal_MaterialAlertDialog) - .setMessage(isGroup ? R.string.GroupsInCommonMessageRequest__none_of_your_contacts_or_people_you_chat_with_are_in_this_group - : R.string.GroupsInCommonMessageRequest__you_have_no_groups_in_common_with_this_person) - .setNeutralButton(R.string.GroupsInCommonMessageRequest__about_message_requests, (d, w) -> CommunicationActions.openBrowserLink(requireContext(), getString(R.string.GroupsInCommonMessageRequest__support_article))) - .setPositiveButton(R.string.GroupsInCommonMessageRequest__okay, null) - .show(); - } + ConversationDialogs.INSTANCE.displayInMemoryMessageDialog(requireContext(), messageRecord); } @Override @@ -2111,7 +2068,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect } @Override - public void onScheduledIndicatorClicked(@NonNull View view, @NonNull MessageRecord messageRecord) { + public void onScheduledIndicatorClicked(@NonNull View view, @NonNull ConversationMessage conversationMessage) { } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 4266b89e3d..80e7f8ae33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -2324,7 +2324,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo private class ScheduledIndicatorClickListener implements View.OnClickListener { public void onClick(final View view) { if (eventListener != null && batchSelected.isEmpty()) { - eventListener.onScheduledIndicatorClicked(view, (messageRecord)); + eventListener.onScheduledIndicatorClicked(view, (conversationMessage)); } else { passthroughClickListener.onClick(view); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java index 54a3fa80c5..db2c1e8b1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationMessage.java @@ -17,11 +17,13 @@ import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.Mention; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.MessageRecordUtil; import java.security.MessageDigest; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * A view level model used to pass arbitrary message related information needed @@ -33,18 +35,21 @@ public class ConversationMessage { @Nullable private final SpannableString body; @NonNull private final MultiselectCollection multiselectCollection; @NonNull private final MessageStyler.Result styleResult; + @NonNull private final Recipient threadRecipient; private final boolean hasBeenQuoted; private ConversationMessage(@NonNull MessageRecord messageRecord, @Nullable CharSequence body, @Nullable List mentions, boolean hasBeenQuoted, - @Nullable MessageStyler.Result styleResult) + @Nullable MessageStyler.Result styleResult, + @NonNull Recipient threadRecipient) { - this.messageRecord = messageRecord; - this.hasBeenQuoted = hasBeenQuoted; - this.mentions = mentions != null ? mentions : Collections.emptyList(); - this.styleResult = styleResult != null ? styleResult : MessageStyler.Result.none(); + this.messageRecord = messageRecord; + this.hasBeenQuoted = hasBeenQuoted; + this.mentions = mentions != null ? mentions : Collections.emptyList(); + this.styleResult = styleResult != null ? styleResult : MessageStyler.Result.none(); + this.threadRecipient = threadRecipient; if (body != null) { this.body = SpannableString.valueOf(body); @@ -119,6 +124,10 @@ public class ConversationMessage { return MessageRecordUtil.isScheduled(messageRecord); } + @NonNull public Recipient getThreadRecipient() { + return threadRecipient; + } + /** * Factory providing multiple ways of creating {@link ConversationMessage}s. */ @@ -135,7 +144,8 @@ public class ConversationMessage { @NonNull MessageRecord messageRecord, @NonNull CharSequence body, @Nullable List mentions, - boolean hasBeenQuoted) + boolean hasBeenQuoted, + @NonNull Recipient threadRecipient) { SpannableString styledAndMentionBody = null; MessageStyler.Result styleResult = MessageStyler.Result.none(); @@ -157,7 +167,8 @@ public class ConversationMessage { styledAndMentionBody != null ? styledAndMentionBody : mentionsUpdate != null ? mentionsUpdate.getBody() : body, mentionsUpdate != null ? mentionsUpdate.getMentions() : null, hasBeenQuoted, - styleResult); + styleResult, + threadRecipient); } /** @@ -166,8 +177,8 @@ public class ConversationMessage { * database operations to query for mentions and then to resolve mentions to display names. */ @WorkerThread - public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord) { - return createWithUnresolvedData(context, messageRecord, messageRecord.getDisplayBody(context)); + public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Recipient threadRecipient) { + return createWithUnresolvedData(context, messageRecord, messageRecord.getDisplayBody(context), threadRecipient); } /** @@ -176,10 +187,10 @@ public class ConversationMessage { * database operations to query for mentions and then to resolve mentions to display names. */ @WorkerThread - public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, boolean hasBeenQuoted) { + public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, boolean hasBeenQuoted, @NonNull Recipient threadRecipient) { List mentions = messageRecord.isMms() ? SignalDatabase.mentions().getMentionsForMessage(messageRecord.getId()) : null; - return createWithUnresolvedData(context, messageRecord, messageRecord.getDisplayBody(context), mentions, hasBeenQuoted); + return createWithUnresolvedData(context, messageRecord, messageRecord.getDisplayBody(context), mentions, hasBeenQuoted, threadRecipient); } /** @@ -188,11 +199,11 @@ public class ConversationMessage { * database operations to query for mentions and then to resolve mentions to display names. */ @WorkerThread - public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull CharSequence body) { + public static @NonNull ConversationMessage createWithUnresolvedData(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull CharSequence body, @NonNull Recipient threadRecipient) { boolean hasBeenQuoted = SignalDatabase.messages().isQuoted(messageRecord); List mentions = SignalDatabase.mentions().getMentionsForMessage(messageRecord.getId()); - return createWithUnresolvedData(context, messageRecord, body, mentions, hasBeenQuoted); + return createWithUnresolvedData(context, messageRecord, body, mentions, hasBeenQuoted, threadRecipient); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index b4ee6fae7d..0befe95964 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -163,6 +163,7 @@ import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryChanged import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryResultsController; import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryViewModel; import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel; +import org.thoughtcrime.securesms.conversation.v2.ConversationDialogs; import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.DraftTable.Draft; @@ -3948,15 +3949,7 @@ public class ConversationParentFragment extends Fragment .forMessageRecord(requireContext(), messageRecord) .show(getChildFragmentManager()); } else if (messageRecord.hasFailedWithNetworkFailures()) { - new MaterialAlertDialogBuilder(requireContext()) - .setMessage(R.string.conversation_activity__message_could_not_be_sent) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.conversation_activity__send, (dialog, which) -> { - SignalExecutors.BOUNDED.execute(() -> { - MessageSender.resend(requireContext(), messageRecord); - }); - }) - .show(); + ConversationDialogs.INSTANCE.displayMessageCouldNotBeSentDialog(requireContext(), messageRecord); } else { MessageDetailsFragment.create(messageRecord, recipient.getId()).show(getChildFragmentManager(), null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java index 87b0429bc0..cd0af2c354 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java @@ -203,7 +203,7 @@ public class ConversationRepository { try (InputStream stream = PartAuthority.getAttachmentStream(context, textSlide.getUri())) { String body = StreamUtil.readFullyAsString(stream); - return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, body); + return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, body, message.getThreadRecipient()); } catch (IOException e) { Log.w(TAG, "Failed to read text slide data."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesBottomSheet.kt index ee69928f12..f3c033bbe4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesBottomSheet.kt @@ -92,7 +92,7 @@ class ScheduledMessagesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment val colorizer = Colorizer() - messageAdapter = ConversationAdapter(requireContext(), viewLifecycleOwner, GlideApp.with(this), Locale.getDefault(), ConversationAdapterListener(), conversationRecipient, colorizer).apply { + messageAdapter = ConversationAdapter(requireContext(), viewLifecycleOwner, GlideApp.with(this), Locale.getDefault(), ConversationAdapterListener(), conversationRecipient.hasWallpaper(), colorizer).apply { setCondensedMode(ConversationItemDisplayMode.CONDENSED) setScheduledMessagesMode(true) } @@ -147,24 +147,23 @@ class ScheduledMessagesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment return callback } - private fun showScheduledMessageContextMenu(view: View, messageRecord: MessageRecord) { + private fun showScheduledMessageContextMenu(view: View, conversationMessage: ConversationMessage) { SignalContextMenu.Builder(view, requireCoordinatorLayout()) .offsetX(12.dp) .offsetY(12.dp) .preferredVerticalPosition(SignalContextMenu.VerticalPosition.ABOVE) - .show(getMenuActionItems(messageRecord)) + .show(getMenuActionItems(conversationMessage)) } - private fun getMenuActionItems(messageRecord: MessageRecord): List { - val message = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), messageRecord) - val canCopy = message.multiselectCollection.toSet().any { it !is Attachments && messageRecord.body.isNotEmpty() } + private fun getMenuActionItems(message: ConversationMessage): List { + val canCopy = message.multiselectCollection.toSet().any { it !is Attachments && message.messageRecord.body.isNotEmpty() } val items: MutableList = ArrayList() - items.add(ActionItem(R.drawable.symbol_trash_24, resources.getString(R.string.conversation_selection__menu_delete), action = { handleDeleteMessage(messageRecord) })) + items.add(ActionItem(R.drawable.symbol_trash_24, resources.getString(R.string.conversation_selection__menu_delete), action = { handleDeleteMessage(message.messageRecord) })) if (canCopy) { items.add(ActionItem(R.drawable.symbol_copy_android_24, resources.getString(R.string.conversation_selection__menu_copy), action = { handleCopyMessage(message) })) } - items.add(ActionItem(R.drawable.symbol_send_24, resources.getString(R.string.ScheduledMessagesBottomSheet_menu_send_now), action = { handleSendMessageNow(messageRecord) })) - items.add(ActionItem(R.drawable.symbol_calendar_24, resources.getString(R.string.ScheduledMessagesBottomSheet_menu_reschedule), action = { handleRescheduleMessage(messageRecord) })) + items.add(ActionItem(R.drawable.symbol_send_24, resources.getString(R.string.ScheduledMessagesBottomSheet_menu_send_now), action = { handleSendMessageNow(message.messageRecord) })) + items.add(ActionItem(R.drawable.symbol_calendar_24, resources.getString(R.string.ScheduledMessagesBottomSheet_menu_reschedule), action = { handleRescheduleMessage(message.messageRecord) })) return items } @@ -214,14 +213,14 @@ class ScheduledMessagesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment try { PartAuthority.getAttachmentStream(requireContext(), textSlide.uri!!).use { stream -> val body = StreamUtil.readFullyAsString(stream) - return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.messageRecord, body) + return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.messageRecord, body, message.threadRecipient) .getDisplayBody(requireContext()) } } catch (e: IOException) { Log.w(TAG, "Failed to read text slide data.") } } - return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.messageRecord).getDisplayBody(requireContext()) + return ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(requireContext(), message.messageRecord, message.threadRecipient).getDisplayBody(requireContext()) } private fun deleteMessage(messageId: Long) { @@ -249,8 +248,8 @@ class ScheduledMessagesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment callback.getConversationAdapterListener().onQuoteClicked(messageRecord) } - override fun onScheduledIndicatorClicked(view: View, messageRecord: MessageRecord) { - showScheduledMessageContextMenu(view, messageRecord) + override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) { + showScheduledMessageContextMenu(view, conversationMessage) } override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesRepository.kt index e9b6c481ba..efadb9563a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ScheduledMessagesRepository.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.DatabaseObserver import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.recipients.Recipient /** * Handles retrieving scheduled messages data to be shown in [ScheduledMessagesBottomSheet] and [ConversationParentFragment] @@ -32,6 +33,7 @@ class ScheduledMessagesRepository { @WorkerThread private fun getScheduledMessagesSync(context: Context, threadId: Long): List { var scheduledMessages: List = SignalDatabase.messages.getScheduledMessagesInThread(threadId) + val threadRecipient: Recipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(threadId)) val attachmentHelper = ConversationDataSource.AttachmentHelper() @@ -42,7 +44,7 @@ class ScheduledMessagesRepository { scheduledMessages = attachmentHelper.buildUpdatedModels(ApplicationDependencies.getApplication(), scheduledMessages) val replies: List = scheduledMessages - .map { ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it) } + .map { ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it, threadRecipient) } return replies } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt index 32e3a9b978..56a9757ae4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/drafts/DraftRepository.kt @@ -112,7 +112,8 @@ class DraftRepository( } } ?: return@fromCallable null - ConversationMessageFactory.createWithUnresolvedData(context, messageRecord) + val threadRecipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(messageRecord.threadId)) + ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, threadRecipient) } } @@ -120,20 +121,21 @@ class DraftRepository( return Maybe.fromCallable { val messageId = MessageId.deserialize(serialized) val messageRecord: MessageRecord = SignalDatabase.messages.getMessageRecordOrNull(messageId.id) ?: return@fromCallable null + val threadRecipient: Recipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(messageRecord.threadId)) if (messageRecord.hasTextSlide()) { val textSlide = messageRecord.requireTextSlide() if (textSlide.uri != null) { try { PartAuthority.getAttachmentStream(context, textSlide.uri!!).use { stream -> val body = StreamUtil.readFullyAsString(stream) - return@fromCallable ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, body) + return@fromCallable ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, body, threadRecipient) } } catch (e: IOException) { Log.e(TAG, "Failed to load text slide", e) } } } - ConversationMessageFactory.createWithUnresolvedData(context, messageRecord) + ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, threadRecipient) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt index 7967b9ea16..bb6cc368b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/mutiselect/forward/MultiselectForwardFragmentArgs.kt @@ -125,7 +125,7 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor( if (textSlideUri != null) { PartAuthority.getAttachmentStream(context, textSlideUri).use { val body = StreamUtil.readFullyAsString(it) - val msg = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, mediaMessage, body) + val msg = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, mediaMessage, body, conversationMessage.threadRecipient) builder.withDraftText(msg.getDisplayBody(context).toString()) } } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt index 2a2709dfda..01ba25143f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt @@ -72,7 +72,7 @@ class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() { val colorizer = Colorizer() - messageAdapter = ConversationAdapter(requireContext(), viewLifecycleOwner, GlideApp.with(this), Locale.getDefault(), ConversationAdapterListener(), conversationRecipient, colorizer).apply { + messageAdapter = ConversationAdapter(requireContext(), viewLifecycleOwner, GlideApp.with(this), Locale.getDefault(), ConversationAdapterListener(), conversationRecipient.hasWallpaper(), colorizer).apply { setCondensedMode(ConversationItemDisplayMode.CONDENSED) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt index 5094a10125..6672f47a51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesRepository.kt @@ -58,6 +58,7 @@ class MessageQuotesRepository { val reactionHelper = ConversationDataSource.ReactionHelper() val attachmentHelper = ConversationDataSource.AttachmentHelper() + val threadRecipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(originalRecord.threadId)) reactionHelper.addAll(replyRecords) attachmentHelper.addAll(replyRecords) @@ -77,7 +78,7 @@ class MessageQuotesRepository { replyRecord } } - .map { ConversationMessageFactory.createWithUnresolvedData(application, it) } + .map { ConversationMessageFactory.createWithUnresolvedData(application, it, threadRecipient) } if (originalRecord.isPaymentNotification) { originalRecord = SignalDatabase.payments.updateMessageWithPayment(originalRecord) @@ -99,7 +100,7 @@ class MessageQuotesRepository { .buildUpdatedModels(ApplicationDependencies.getApplication(), listOf(originalRecord)) .get(0) - val originalMessage: ConversationMessage = ConversationMessageFactory.createWithUnresolvedData(application, originalRecord, false) + val originalMessage: ConversationMessage = ConversationMessageFactory.createWithUnresolvedData(application, originalRecord, false, threadRecipient) return replies + originalMessage } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt index 3d0ffff11c..8a1cecd80e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.conversation.ConversationAdapter import org.thoughtcrime.securesms.conversation.ConversationBottomSheetCallback import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode +import org.thoughtcrime.securesms.conversation.ConversationMessage import org.thoughtcrime.securesms.conversation.colors.Colorizer import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart @@ -68,7 +69,7 @@ class EditMessageHistoryDialog : FixedRoundedCornerBottomSheetDialogFragment() { GlideApp.with(this), Locale.getDefault(), ConversationAdapterListener(), - conversationRecipient, + conversationRecipient.hasWallpaper(), colorizer ).apply { setCondensedMode(ConversationItemDisplayMode.EXTRA_CONDENSED) @@ -119,7 +120,7 @@ class EditMessageHistoryDialog : FixedRoundedCornerBottomSheetDialogFragment() { private inner class ConversationAdapterListener : ConversationAdapter.ItemClickListener by requireListener().getConversationAdapterListener() { override fun onQuoteClicked(messageRecord: MmsMessageRecord) = Unit - override fun onScheduledIndicatorClicked(view: View, messageRecord: MessageRecord) = Unit + override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) = Unit override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) = Unit override fun onItemClick(item: MultiselectPart) = Unit override fun onItemLongClick(itemView: View, item: MultiselectPart) = Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryRepository.kt index 6cd20f11cb..f0a15ce0c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryRepository.kt @@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.conversation.ConversationMessage import org.thoughtcrime.securesms.database.DatabaseObserver import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.recipients.Recipient object EditMessageHistoryRepository { @@ -41,8 +42,14 @@ object EditMessageHistoryRepository { fetchAttachments() } + if (records.isEmpty()) { + return emptyList() + } + + val threadRecipient: Recipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(records[0].threadId)) + return attachmentHelper .buildUpdatedModels(context, records) - .map { ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it) } + .map { ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it, threadRecipient) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationDialogs.kt index a48700b11d..a8cd6d4a1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationDialogs.kt @@ -2,8 +2,21 @@ package org.thoughtcrime.securesms.conversation.v2 import android.content.Context import android.content.DialogInterface +import android.widget.TextView +import androidx.core.app.DialogCompat +import androidx.fragment.app.Fragment import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.signal.core.util.concurrent.SignalExecutors +import org.signal.core.util.concurrent.SimpleTask import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity +import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord.NoGroupsInCommon +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.sms.MessageSender +import org.thoughtcrime.securesms.util.CommunicationActions +import org.thoughtcrime.securesms.verify.VerifyIdentityActivity /** * Centralized object for displaying dialogs to the user from the @@ -20,4 +33,83 @@ object ConversationDialogs { .setPositiveButton(R.string.ok) { d: DialogInterface, w: Int -> d.dismiss() } .show() } + + fun displayChatSessionRefreshLearnMoreDialog(context: Context) { + MaterialAlertDialogBuilder(context) + .setView(R.layout.decryption_failed_dialog) + .setPositiveButton(android.R.string.ok) { d, _ -> d.dismiss() } + .setNeutralButton(R.string.ConversationFragment_contact_us) { d, _ -> + context.startActivity(AppSettingsActivity.help(context, 0)) + d.dismiss() + } + .show() + } + + fun displaySafetyNumberLearnMoreDialog(fragment: Fragment, recipient: Recipient) { + check(!recipient.isGroup) + val dialog = MaterialAlertDialogBuilder(fragment.requireContext()) + .setView(R.layout.safety_number_changed_learn_more_dialog) + .setPositiveButton(R.string.ConversationFragment_verify) { d, _ -> + SimpleTask.run( + fragment.lifecycle, + { ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecord(recipient.id) }, + { identityRecord -> + identityRecord.ifPresent { + fragment.startActivity(VerifyIdentityActivity.newIntent(fragment.requireContext(), identityRecord.get())) + } + d.dismiss() + } + ) + } + .setNegativeButton(R.string.ConversationFragment_not_now) { d, _ -> d.dismiss() } + .create() + + dialog.setOnShowListener { + val title: TextView = DialogCompat.requireViewById(dialog, R.id.safety_number_learn_more_title) as TextView + val body: TextView = DialogCompat.requireViewById(dialog, R.id.safety_number_learn_more_body) as TextView + + title.text = fragment.getString( + R.string.ConversationFragment_your_safety_number_with_s_changed, + recipient.getDisplayName(fragment.requireContext()) + ) + + body.text = fragment.getString( + R.string.ConversationFragment_your_safety_number_with_s_changed_likey_because_they_reinstalled_signal, + recipient.getDisplayName(fragment.requireContext()) + ) + } + + dialog.show() + } + + fun displayInMemoryMessageDialog(context: Context, messageRecord: MessageRecord) { + if (messageRecord is NoGroupsInCommon) { + val isGroup = messageRecord.isGroup + MaterialAlertDialogBuilder(context, R.style.ThemeOverlay_Signal_MaterialAlertDialog) + .setMessage( + if (isGroup) { + R.string.GroupsInCommonMessageRequest__none_of_your_contacts_or_people_you_chat_with_are_in_this_group + } else { + R.string.GroupsInCommonMessageRequest__you_have_no_groups_in_common_with_this_person + } + ) + .setNeutralButton(R.string.GroupsInCommonMessageRequest__about_message_requests) { _, _ -> + CommunicationActions.openBrowserLink(context, context.getString(R.string.GroupsInCommonMessageRequest__support_article)) + } + .setPositiveButton(R.string.GroupsInCommonMessageRequest__okay, null) + .show() + } + } + + fun displayMessageCouldNotBeSentDialog(context: Context, messageRecord: MessageRecord) { + MaterialAlertDialogBuilder(context) + .setMessage(R.string.conversation_activity__message_could_not_be_sent) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.conversation_activity__send) { _, _ -> + SignalExecutors.BOUNDED.execute { + MessageSender.resend(context, messageRecord) + } + } + .show() + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index b9a65d290d..5259d28505 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.conversation.v2 +import android.annotation.SuppressLint import android.app.ActivityOptions import android.content.Intent import android.net.Uri @@ -14,6 +15,7 @@ import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat +import androidx.core.view.doOnNextLayout import androidx.fragment.app.viewModels import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -26,6 +28,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.kotlin.subscribeBy +import io.reactivex.rxjava3.schedulers.Schedulers import org.greenrobot.eventbus.EventBus import org.signal.core.util.ThreadUtil import org.signal.core.util.concurrent.LifecycleDisposable @@ -33,16 +36,20 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.MainActivity import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.badges.gifts.flow.GiftFlowActivity import org.thoughtcrime.securesms.badges.gifts.viewgift.received.ViewReceivedGiftBottomSheet import org.thoughtcrime.securesms.badges.gifts.viewgift.sent.ViewSentGiftBottomSheet import org.thoughtcrime.securesms.components.ScrollToPositionDelegate import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager +import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalFragment +import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState import org.thoughtcrime.securesms.contactshare.Contact import org.thoughtcrime.securesms.contactshare.ContactUtil import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity +import org.thoughtcrime.securesms.conversation.BadDecryptLearnMoreDialog import org.thoughtcrime.securesms.conversation.ConversationAdapter import org.thoughtcrime.securesms.conversation.ConversationIntents import org.thoughtcrime.securesms.conversation.ConversationIntents.ConversationScreenType @@ -56,9 +63,13 @@ import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer import org.thoughtcrime.securesms.conversation.mutiselect.ConversationItemAnimator import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectItemDecoration import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart +import org.thoughtcrime.securesms.conversation.quotes.MessageQuotesBottomSheet +import org.thoughtcrime.securesms.conversation.ui.edit.EditMessageHistoryDialog +import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupCallViewModel import org.thoughtcrime.securesms.conversation.v2.groups.ConversationGroupViewModel import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord +import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.Quote @@ -72,6 +83,9 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ProjectionRecycler import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange import org.thoughtcrime.securesms.groups.ui.GroupErrors +import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment +import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog +import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment import org.thoughtcrime.securesms.groups.v2.GroupBlockJoinRequestResult import org.thoughtcrime.securesms.invites.InviteActions import org.thoughtcrime.securesms.linkpreview.LinkPreview @@ -79,6 +93,7 @@ import org.thoughtcrime.securesms.longmessage.LongMessageFragment import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory.create import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity +import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment import org.thoughtcrime.securesms.mms.AttachmentManager import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.notifications.v2.ConversationId @@ -86,6 +101,7 @@ import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientExporter import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet @@ -98,7 +114,9 @@ import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.ContextUtil import org.thoughtcrime.securesms.util.DrawableUtil import org.thoughtcrime.securesms.util.FullscreenHelper +import org.thoughtcrime.securesms.util.SignalLocalMetrics import org.thoughtcrime.securesms.util.WindowUtil +import org.thoughtcrime.securesms.util.doAfterNextLayout import org.thoughtcrime.securesms.util.fragments.requireListener import org.thoughtcrime.securesms.util.hasGiftBadge import org.thoughtcrime.securesms.util.visible @@ -140,6 +158,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) ) private val conversationTooltips = ConversationTooltips(this) + private val colorizer = Colorizer() private lateinit var conversationOptionsMenuProvider: ConversationOptionsMenu.Provider private lateinit var layoutManager: SmoothScrollingLinearLayoutManager @@ -150,6 +169,8 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) private lateinit var adapter: ConversationAdapter private lateinit var recyclerViewColorizer: RecyclerViewColorizer + private var animationsAllowed = false + private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy { override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) { ScrollToPositionDelegate.JumpToPositionStrategy.performScroll(recyclerView, layoutManager, position, smooth) @@ -157,32 +178,20 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + SignalLocalMetrics.ConversationOpen.start() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { registerForResults() + disposables.bindTo(viewLifecycleOwner) + FullscreenHelper(requireActivity()).showSystemUI() conversationOptionsMenuProvider = ConversationOptionsMenu.Provider(ConversationOptionsMenuCallback(), disposables) markReadHelper = MarkReadHelper(ConversationId.forConversation(args.threadId), requireContext(), viewLifecycleOwner) - FullscreenHelper(requireActivity()).showSystemUI() - - layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true) - binding.conversationItemRecycler.setHasFixedSize(false) - binding.conversationItemRecycler.layoutManager = layoutManager - binding.conversationItemRecycler.addOnScrollListener(ScrollListener()) - - binding.scrollToBottom.setOnClickListener { - scrollToPositionDelegate.resetScrollPosition() - } - - binding.scrollToMention.setOnClickListener { - scrollToNextMention() - } - - val layoutTransitionListener = BubbleLayoutTransitionListener(binding.conversationItemRecycler) - viewLifecycleOwner.lifecycle.addObserver(layoutTransitionListener) - - recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler) - recyclerViewColorizer.setChatColors(args.chatColors) + initializeConversationThreadUi() val conversationToolbarOnScrollHelper = ConversationToolbarOnScrollHelper( requireActivity(), @@ -190,29 +199,10 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) viewModel::wallpaperSnapshot ) conversationToolbarOnScrollHelper.attach(binding.conversationItemRecycler) - - disposables.bindTo(viewLifecycleOwner) - disposables += viewModel.recipient - .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy(onSuccess = { - onFirstRecipientLoad(it) - }) - presentWallpaper(args.wallpaper) - disposables += viewModel.recipient - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy(onNext = this::onRecipientChanged) + presentActionBarMenu() - disposables += viewModel.markReadRequests - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy(onNext = markReadHelper::onViewsRevealed) - - disposables += viewModel.scrollButtonState - .subscribeBy(onNext = this::presentScrollButtons) - - EventBus.getDefault().registerForLifecycle(groupCallViewModel, viewLifecycleOwner) - presentGroupCallJoinButton() + observeConversationThread() } override fun onResume() { @@ -227,75 +217,57 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } } - override fun onPause() { - super.onPause() - ApplicationDependencies.getMessageNotifier().clearVisibleThread() - } - - private fun registerForResults() { - addToContactsLauncher = registerForActivityResult(AddToContactsContract()) {} - } - - private fun onFirstRecipientLoad(recipient: Recipient) { - Log.d(TAG, "onFirstRecipientLoad") - - val colorizer = Colorizer() - adapter = ConversationAdapter( - requireContext(), - viewLifecycleOwner, - GlideApp.with(this), - Locale.getDefault(), - ConversationItemClickListener(), - recipient, - colorizer - ) - - scrollToPositionDelegate = ScrollToPositionDelegate( - binding.conversationItemRecycler, - adapter::canJumpToPosition, - adapter::getAdapterPositionForMessagePosition - ) - - binding.conversationItemRecycler.itemAnimator = ConversationItemAnimator( - isInMultiSelectMode = adapter.selectedItems::isNotEmpty, - shouldPlayMessageAnimations = { - scrollToPositionDelegate.isListCommitted() && binding.conversationItemRecycler.scrollState == RecyclerView.SCROLL_STATE_IDLE - }, - isParentFilled = { - binding.conversationItemRecycler.canScrollVertically(1) || binding.conversationItemRecycler.canScrollVertically(-1) - } - ) - - ConversationAdapter.initializePool(binding.conversationItemRecycler.recycledViewPool) - adapter.setPagingController(viewModel.pagingController) - adapter.registerAdapterDataObserver(DataObserver(scrollToPositionDelegate)) - viewLifecycleOwner.lifecycle.addObserver(LastSeenPositionUpdater(adapter, layoutManager, viewModel)) - binding.conversationItemRecycler.adapter = adapter - giphyMp4ProjectionRecycler = initializeGiphyMp4() - - val multiselectItemDecoration = MultiselectItemDecoration( - requireContext() - ) { viewModel.wallpaperSnapshot } - - binding.conversationItemRecycler.addItemDecoration(multiselectItemDecoration) - viewLifecycleOwner.lifecycle.addObserver(multiselectItemDecoration) - + private fun observeConversationThread() { + var firstRender = true disposables += viewModel .conversationThreadState - .doOnSuccess { - scrollToPositionDelegate.requestScrollPosition( - position = it.meta.getStartPosition(), - smooth = false, - awaitLayout = false - ) + .subscribeOn(Schedulers.io()) + .doOnSuccess { state -> + SignalLocalMetrics.ConversationOpen.onDataLoaded() + binding.conversationItemRecycler.doOnNextLayout { + layoutManager.scrollToPositionWithOffset( + adapter.getAdapterPositionForMessagePosition(state.meta.getStartPosition()), + binding.conversationItemRecycler.height + ) + } } .flatMapObservable { it.items.data } .observeOn(AndroidSchedulers.mainThread()) .subscribeBy(onNext = { + SignalLocalMetrics.ConversationOpen.onDataPostedToMain() + adapter.submitList(it) { scrollToPositionDelegate.notifyListCommitted() + + binding.conversationItemRecycler.doAfterNextLayout { + SignalLocalMetrics.ConversationOpen.onRenderFinished() + + if (firstRender) { + firstRender = false + doAfterFirstRender() + animationsAllowed = true + } + } } }) + } + + private fun doAfterFirstRender() { + Log.d(TAG, "doAfterFirstRender") + + EventBus.getDefault().registerForLifecycle(groupCallViewModel, viewLifecycleOwner) + viewLifecycleOwner.lifecycle.addObserver(LastSeenPositionUpdater(adapter, layoutManager, viewModel)) + + disposables += viewModel.recipient + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy(onNext = this::onRecipientChanged) + + disposables += viewModel.markReadRequests + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy(onNext = markReadHelper::onViewsRevealed) + + disposables += viewModel.scrollButtonState + .subscribeBy(onNext = this::presentScrollButtons) disposables += viewModel .nameColorsMap @@ -305,7 +277,26 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) adapter.notifyItemRangeChanged(0, adapter.itemCount) }) - presentActionBarMenu() + presentGroupCallJoinButton() + + binding.scrollToBottom.setOnClickListener { + scrollToPositionDelegate.resetScrollPosition() + } + + binding.scrollToMention.setOnClickListener { + scrollToNextMention() + } + + adapter.registerAdapterDataObserver(DataObserver(scrollToPositionDelegate)) + } + + override fun onPause() { + super.onPause() + ApplicationDependencies.getMessageNotifier().clearVisibleThread() + } + + private fun registerForResults() { + addToContactsLauncher = registerForActivityResult(AddToContactsContract()) {} } private fun onRecipientChanged(recipient: Recipient) { @@ -364,6 +355,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) binding.conversationWallpaper.visible = chatWallpaper != null binding.scrollToBottom.setWallpaperEnabled(chatWallpaper != null) binding.scrollToMention.setWallpaperEnabled(chatWallpaper != null) + adapter.onHasWallpaperChanged(chatWallpaper != null) } private fun presentChatColors(chatColors: ChatColors) { @@ -428,6 +420,58 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) private fun getVoiceNoteMediaController() = requireListener().voiceNoteMediaController + private fun initializeConversationThreadUi() { + layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true) + binding.conversationItemRecycler.setHasFixedSize(false) + binding.conversationItemRecycler.layoutManager = layoutManager + binding.conversationItemRecycler.addOnScrollListener(ScrollListener()) + + adapter = ConversationAdapter( + requireContext(), + viewLifecycleOwner, + GlideApp.with(this), + Locale.getDefault(), + ConversationItemClickListener(), + args.wallpaper != null, + colorizer + ) + + scrollToPositionDelegate = ScrollToPositionDelegate( + binding.conversationItemRecycler, + adapter::canJumpToPosition, + adapter::getAdapterPositionForMessagePosition + ) + + ConversationAdapter.initializePool(binding.conversationItemRecycler.recycledViewPool) + adapter.setPagingController(viewModel.pagingController) + + binding.conversationItemRecycler.adapter = adapter + giphyMp4ProjectionRecycler = initializeGiphyMp4() + + val multiselectItemDecoration = MultiselectItemDecoration( + requireContext() + ) { viewModel.wallpaperSnapshot } + + binding.conversationItemRecycler.addItemDecoration(multiselectItemDecoration) + viewLifecycleOwner.lifecycle.addObserver(multiselectItemDecoration) + + val layoutTransitionListener = BubbleLayoutTransitionListener(binding.conversationItemRecycler) + viewLifecycleOwner.lifecycle.addObserver(layoutTransitionListener) + + recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler) + recyclerViewColorizer.setChatColors(args.chatColors) + + binding.conversationItemRecycler.itemAnimator = ConversationItemAnimator( + isInMultiSelectMode = adapter.selectedItems::isNotEmpty, + shouldPlayMessageAnimations = { + animationsAllowed && scrollToPositionDelegate.isListCommitted() && binding.conversationItemRecycler.scrollState == RecyclerView.SCROLL_STATE_IDLE + }, + isParentFilled = { + binding.conversationItemRecycler.canScrollVertically(1) || binding.conversationItemRecycler.canScrollVertically(-1) + } + ) + } + private fun initializeGiphyMp4(): GiphyMp4ProjectionRecycler { val maxPlayback = GiphyMp4PlaybackPolicy.maxSimultaneousPlaybackInConversation() val holders = GiphyMp4ProjectionPlayerHolder.injectVideoViews( @@ -553,7 +597,15 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) { - // TODO [alex] - ("Not yet implemented") + context ?: return + activity ?: return + val recipientId = viewModel.recipientSnapshot?.id ?: return + + MessageQuotesBottomSheet.show( + childFragmentManager, + MessageId(messageRecord.id), + recipientId + ) } override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) { @@ -614,7 +666,16 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onMessageWithErrorClicked(messageRecord: MessageRecord) { - // TODO [alex] - ("Not yet implemented") + val recipientId = viewModel.recipientSnapshot?.id ?: return + if (messageRecord.isIdentityMismatchFailure) { + SafetyNumberBottomSheet + .forMessageRecord(requireContext(), messageRecord) + .show(childFragmentManager) + } else if (messageRecord.hasFailedWithNetworkFailures()) { + ConversationDialogs.displayMessageCouldNotBeSentDialog(requireContext(), messageRecord) + } else { + MessageDetailsFragment.create(messageRecord, recipientId).show(childFragmentManager, null) + } } override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) { @@ -654,55 +715,73 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) { - // TODO [alex] -- ("Not yet implemented") + GroupsV1MigrationInfoBottomSheetDialogFragment.show(parentFragmentManager, membershipChange) } override fun onChatSessionRefreshLearnMoreClicked() { - // TODO [alex] -- ("Not yet implemented") + ConversationDialogs.displayChatSessionRefreshLearnMoreDialog(requireContext()) } override fun onBadDecryptLearnMoreClicked(author: RecipientId) { - // TODO [alex] -- ("Not yet implemented") + val isGroup = viewModel.recipientSnapshot?.isGroup ?: return + val recipientName = Recipient.resolved(author).getDisplayName(requireContext()) + BadDecryptLearnMoreDialog.show(parentFragmentManager, recipientName, isGroup) } override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) { - // TODO [alex] -- ("Not yet implemented") + ConversationDialogs.displaySafetyNumberLearnMoreDialog(this@ConversationFragment, recipient) } override fun onJoinGroupCallClicked() { - // TODO [alex] -- ("Not yet implemented") + val activity = activity ?: return + val recipient = viewModel.recipientSnapshot ?: return + CommunicationActions.startVideoCall(activity, recipient) } override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) { - // TODO [alex] -- ("Not yet implemented") + GroupLinkInviteFriendsBottomSheetDialogFragment.show(requireActivity().supportFragmentManager, groupId) } + @SuppressLint("NotifyDataSetChanged") override fun onEnableCallNotificationsClicked() { - // TODO [alex] -- ("Not yet implemented") + EnableCallNotificationSettingsDialog.fixAutomatically(requireContext()) + if (EnableCallNotificationSettingsDialog.shouldShow(requireContext())) { + EnableCallNotificationSettingsDialog.show(childFragmentManager) + } else { + adapter.notifyDataSetChanged() + } } override fun onPlayInlineContent(conversationMessage: ConversationMessage?) { - // TODO [alex] - ("Not yet implemented") + adapter.playInlineContent(conversationMessage) } override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) { - // TODO [alex] - ("Not yet implemented") + ConversationDialogs.displayInMemoryMessageDialog(requireContext(), messageRecord) } override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) { - // TODO [alex] - ("Not yet implemented") + if (groupId != null) { + GroupDescriptionDialog.show(childFragmentManager, groupId, description, isMessageRequestAccepted) + } } override fun onChangeNumberUpdateContact(recipient: Recipient) { - // TODO [alex] - ("Not yet implemented") + startActivity(RecipientExporter.export(recipient).asAddContactIntent()) } override fun onCallToAction(action: String) { - // TODO [alex] - ("Not yet implemented") + if ("gift_badge" == action) { + startActivity(Intent(requireContext(), GiftFlowActivity::class.java)) + } } override fun onDonateClicked() { - // TODO [alex] - ("Not yet implemented") + requireActivity() + .supportFragmentManager + .beginTransaction() + .add(DonateToSignalFragment.Dialog.create(DonateToSignalType.ONE_TIME), "one_time_nav") + .commitNow() } override fun onBlockJoinRequest(recipient: Recipient) { @@ -728,7 +807,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) InviteActions.inviteUserToSignal( requireContext(), recipient, - {}, // TODO [alex] -- append to compose + binding.conversationInputPanel.embeddedTextEditor::appendInvite, this@ConversationFragment::startActivity ) } @@ -738,17 +817,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onSendPaymentClicked(recipientId: RecipientId) { - disposables += viewModel.recipient - .firstOrError() - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy { - AttachmentManager.selectPayment(this@ConversationFragment, it) - } + val recipient = viewModel.recipientSnapshot ?: return + AttachmentManager.selectPayment(this@ConversationFragment, recipient) } - override fun onScheduledIndicatorClicked(view: View, messageRecord: MessageRecord) { - // TODO [alex] -- ("Not yet implemented") - } + override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) = Unit override fun onUrlClicked(url: String): Boolean { return CommunicationActions.handlePotentialGroupLinkUrl(requireActivity(), url) || @@ -793,7 +866,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } override fun onEditedIndicatorClicked(messageRecord: MessageRecord) { - // TODO [alex] -- ("Not yet implemented") + if (messageRecord.isOutgoing) { + EditMessageHistoryDialog.show(childFragmentManager, messageRecord.toRecipient.id, messageRecord.id) + } else { + EditMessageHistoryDialog.show(childFragmentManager, messageRecord.fromRecipient.id, messageRecord.id) + } } override fun onItemClick(item: MultiselectPart?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index 246a6f118a..0679b9d573 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase.Companion.threads import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.util.SignalLocalMetrics import kotlin.math.max class ConversationRepository(context: Context) { @@ -52,6 +53,7 @@ class ConversationRepository(context: Context) { */ fun getConversationThreadState(threadId: Long, requestedStartPosition: Int): Single { return Single.fromCallable { + SignalLocalMetrics.ConversationOpen.onMetadataLoadStarted() val recipient = threads.getRecipientForThreadId(threadId)!! val metadata = oldConversationRepository.getConversationData(threadId, recipient, requestedStartPosition) val messageRequestData = metadata.messageRequestData @@ -70,8 +72,10 @@ class ConversationRepository(context: Context) { ConversationThreadState( items = PagedData.createForObservable(dataSource, config), meta = metadata - ) - } + ).apply { + SignalLocalMetrics.ConversationOpen.onMetadataLoaded() + } + }.subscribeOn(Schedulers.io()) } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 0bf228b270..c107faa2f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -10,6 +10,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.processors.PublishProcessor +import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.BehaviorSubject import io.reactivex.rxjava3.subjects.Subject import org.signal.paging.ProxyPagingController @@ -67,7 +68,9 @@ class ConversationViewModel( init { disposables += repository.observeRecipientForThread(threadId) - .subscribeBy(onNext = _recipient::onNext) + .subscribeBy(onNext = { + _recipient.onNext(it) + }) disposables += repository.getConversationThreadState(threadId, requestedStartingPosition) .subscribeBy(onSuccess = { @@ -75,7 +78,7 @@ class ConversationViewModel( _conversationThreadState.onNext(it) }) - disposables += _conversationThreadState.firstOrError().flatMapObservable { threadState -> + disposables += conversationThreadState.flatMapObservable { threadState -> Observable.create { emitter -> val controller = threadState.items.controller val messageUpdateObserver = DatabaseObserver.MessageObserver { @@ -98,7 +101,7 @@ class ConversationViewModel( ApplicationDependencies.getDatabaseObserver().unregisterObserver(conversationObserver) } } - }.subscribe() + }.subscribeOn(Schedulers.io()).subscribe() disposables += scrollButtonStateStore.update( repository.getMessageCounts(threadId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageResolveer.kt b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageResolveer.kt index 33da4876f6..994352d5e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageResolveer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/longmessage/LongMessageResolveer.kt @@ -5,8 +5,10 @@ import android.net.Uri import org.signal.core.util.StreamUtil import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.conversation.ConversationMessage +import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.PartAuthority +import org.thoughtcrime.securesms.recipients.Recipient import java.io.IOException const val TAG = "LongMessageResolver" @@ -21,11 +23,12 @@ fun readFullBody(context: Context, uri: Uri): String { } fun MmsMessageRecord.resolveBody(context: Context): ConversationMessage { + val threadRecipient: Recipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(threadId)) val textSlide = slideDeck.textSlide val textSlideUri = textSlide?.uri return if (textSlide != null && textSlideUri != null) { - ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, this, readFullBody(context, textSlideUri)) + ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, this, readFullBody(context, textSlideUri), threadRecipient) } else { - ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, this) + ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, this, threadRecipient) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsRepository.java index 71331a2d4f..bcddf58f3d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsRepository.java @@ -131,7 +131,8 @@ public final class MessageDetailsRepository { } } - return new MessageDetails(ConversationMessageFactory.createWithUnresolvedData(context, messageRecord), recipients); + Recipient threadRecipient = Objects.requireNonNull(SignalDatabase.threads().getRecipientForThreadId(messageRecord.getThreadId())); + return new MessageDetails(ConversationMessageFactory.createWithUnresolvedData(context, messageRecord, threadRecipient), recipients); } private @Nullable NetworkFailure getNetworkFailure(MessageRecord messageRecord, Recipient recipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt index fd4f1cb91b..9a467e8ad4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingRepository.kt @@ -112,10 +112,10 @@ class StoriesLandingRepository(context: Context) { hasReplies = messageRecords.any { SignalDatabase.messages.getNumberOfStoryReplies(it.id) > 0 }, hasRepliesFromSelf = messageRecords.any { SignalDatabase.messages.hasSelfReplyInStory(it.id) }, isHidden = sender.shouldHideStory(), - primaryStory = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, messageRecords[primaryIndex]), + primaryStory = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, messageRecords[primaryIndex], sender), secondaryStory = if (sender.isMyStory) { messageRecords.drop(1).firstOrNull()?.let { - ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it) + ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it, sender) } } else { null diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesRepository.kt index 19196eb5df..249874541a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/my/MyStoriesRepository.kt @@ -42,7 +42,7 @@ class MyStoriesRepository(context: Context) { return MyStoriesState.DistributionSet( label = recipient.getDisplayName(context), stories = messageRecords.map { - ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it) + ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, it, recipient) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt index b3a43873e5..fb38ba748e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageRepository.kt @@ -86,7 +86,7 @@ open class StoryViewerPageRepository(context: Context, private val storyViewStat replyCount = SignalDatabase.messages.getNumberOfStoryReplies(record.id), dateInMilliseconds = record.dateSent, content = getContent(record as MmsMessageRecord), - conversationMessage = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, record), + conversationMessage = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, record, recipient), allowsReplies = record.storyType.isStoryWithReplies, hasSelfViewed = storyViewStateCache.getOrPut(record.id, if (record.isOutgoing) true else record.viewedReceiptCount > 0) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt index cbe83f63d1..4a7d74d78a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyDataSource.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.recipients.Recipient class StoryGroupReplyDataSource(private val parentStoryId: Long) : PagedDataSource { override fun size(): Int { @@ -36,11 +37,12 @@ class StoryGroupReplyDataSource(private val parentStoryId: Long) : PagedDataSour } private fun readRowFromRecord(record: MmsMessageRecord): ReplyBody { + val threadRecipient: Recipient = requireNotNull(SignalDatabase.threads.getRecipientForThreadId(record.threadId)) return when { record.isRemoteDelete -> ReplyBody.RemoteDelete(record) MessageTypes.isStoryReaction(record.type) -> ReplyBody.Reaction(record) else -> ReplyBody.Text( - ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(), record) + ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(ApplicationDependencies.getApplication(), record, threadRecipient) ) } } diff --git a/app/src/main/res/layout/conversation_input_panel.xml b/app/src/main/res/layout/conversation_input_panel.xml index 3579221241..e37d1becde 100644 --- a/app/src/main/res/layout/conversation_input_panel.xml +++ b/app/src/main/res/layout/conversation_input_panel.xml @@ -8,8 +8,7 @@ android:background="@color/signal_background_primary" android:clipChildren="false" android:clipToPadding="false" - android:orientation="vertical" - tools:viewBindingIgnore="true"> + android:orientation="vertical">